SQL डेटाबेस में, आइसोलेशन स्तर अद्यतन विसंगति निवारण का एक पदानुक्रम है। फिर, लोग सोचते हैं कि उच्चतर बेहतर है, और जब कोई डेटाबेस Serializable प्रदान करता है तो पढ़ने के लिए प्रतिबद्ध होने की कोई आवश्यकता नहीं है। हालांकि:
- पढ़ें प्रतिबद्ध PostgreSQL में डिफ़ॉल्ट है . इसका परिणाम यह है कि कुछ विसंगतियों को रोकने के लिए अधिकांश एप्लिकेशन इसका उपयोग कर रहे हैं (और SELECT ... FOR UPDATE का उपयोग करें)
- धारावाहिक निराशावादी लॉकिंग के साथ स्केल नहीं करता है। वितरित डेटाबेस आशावादी लॉकिंग का उपयोग करते हैं, और आपको उनके लेन-देन पुनः प्रयास तर्क को कोड करने की आवश्यकता होती है
उन दोनों के साथ, एक वितरित SQL डेटाबेस जो रीड कमिटेड आइसोलेशन प्रदान नहीं करता है, वह PostgreSQL संगतता का दावा नहीं कर सकता है, क्योंकि PostgreSQL डिफ़ॉल्ट के लिए बनाए गए एप्लिकेशन चलाना असंभव है।
युगाबाइटडीबी ने "उच्चतर बेहतर" विचार के साथ शुरुआत की और रीड कमिटेड पारदर्शी रूप से "स्नैपशॉट अलगाव" का उपयोग कर रहा है। यह नए अनुप्रयोगों के लिए सही है। हालाँकि, रीड कमिटेड के लिए बनाए गए अनुप्रयोगों को माइग्रेट करते समय, जहाँ आप क्रमिक विफलताओं (SQLState 40001) पर पुन:प्रयास तर्क को लागू नहीं करना चाहते हैं, और डेटाबेस से यह आपके लिए करने की अपेक्षा करते हैं। आप **yb_enable_read_committed_isolation**
के साथ प्रतिबद्ध पढ़ें पर स्विच कर सकते हैं gflag.
नोट:युगाबाइटडीबी में एक GFlag डेटाबेस के लिए एक वैश्विक कॉन्फ़िगरेशन पैरामीटर है, जिसे yb-tserver संदर्भ में प्रलेखित किया गया है। PostgreSQL पैरामीटर, जिसे ysql_pg_conf_csv
. द्वारा सेट किया जा सकता है GFlag केवल YSQL API से संबंधित है लेकिन GFlags सभी YugabyteDB परतों को कवर करता है
इस ब्लॉग पोस्ट में मैं रीड कमिटेड आइसोलेशन स्तर का वास्तविक मूल्य प्रदर्शित करूंगा:पुन:प्रयास तर्क को कोड करने की कोई आवश्यकता नहीं है क्योंकि, इस स्तर पर, YugabyteDB इसे स्वयं कर सकता है।
युगाबाइटडीबी प्रारंभ करें
मैं इस साधारण डेमो के लिए एक युगाबाइटडीबी सिंगल नोड डेटाबेस शुरू कर रहा हूं:
Franck@YB:~ $ docker run --rm -d --name yb \
-p7000:7000 -p9000:9000 -p5433:5433 -p9042:9042 \
yugabytedb/yugabyte \
bin/yugabyted start --daemon=false \
--tserver_flags=""
53cac7952500a6e264e6922fe884bc47085bcac75e36a9ddda7b8469651e974c
मैंने डिफ़ॉल्ट व्यवहार दिखाने के लिए स्पष्ट रूप से कोई GFlags सेट नहीं किया है। यह version 2.13.0.0 build 42
. है ।
मैं पढ़ने के लिए प्रतिबद्ध संबंधित gflags की जांच करता हूं
Franck@YB:~ $ curl -s http://localhost:9000/varz?raw | grep -E "\
(yb_enable_read_committed_isolation\
|ysql_output_buffer_size\
|ysql_sleep_before_retry_on_txn_conflict\
|ysql_max_write_restart_attempts\
|ysql_default_transaction_isolation\
)"
--yb_enable_read_committed_isolation=false
--ysql_max_write_restart_attempts=20
--ysql_output_buffer_size=262144
--ysql_sleep_before_retry_on_txn_conflict=true
--ysql_default_transaction_isolation=
प्रतिबद्ध पढ़ें PostgreSQL संगतता द्वारा डिफ़ॉल्ट अलगाव स्तर है:
Franck@YB:~ $ psql -p 5433 \
-c "show default_transaction_isolation"
default_transaction_isolation
-------------------------------
read committed
(1 row)
मैं एक साधारण तालिका बनाता हूं:
Franck@YB:~ $ psql -p 5433 -ec "
create table demo (id int primary key, val int);
insert into demo select generate_series(1,100000),0;
"
create table demo (id int primary key, val int);
insert into demo select generate_series(1,100000),0;
INSERT 0 100000
मैं निम्न अद्यतन चलाऊंगा, डिफ़ॉल्ट अलगाव स्तर को प्रतिबद्ध पढ़ने के लिए सेट कर रहा हूं (बस मामले में - लेकिन यह डिफ़ॉल्ट है):
Franck@YB:~ $ cat > update1.sql <<'SQL'
\timing on
\set VERBOSITY verbose
set default_transaction_isolation to "read committed";
update demo set val=val+1 where id=1;
\watch 0.1
SQL
यह एक पंक्ति को अपडेट करेगा।
मैं इसे एक ही पंक्ति में कई सत्रों से चलाऊंगा:
Franck@YB:~ $ timeout 60 psql -p 5433 -ef update1.sql >session1.txt &
Franck@YB:~ $ timeout 60 psql -p 5433 -ef update1.sql >session2.txt &
[1] 760
[2] 761
psql:update1.sql:5: ERROR: 40001: Operation expired: Transaction a83718c8-c8cb-4e64-ab54-3afe4f2073bc expired or aborted by a conflict: 40001
LOCATION: HandleYBStatusAtErrorLevel, pg_yb_utils.c:405
[1]- Done timeout 60 psql -p 5433 -ef update1.sql > session1.txt
Franck@YB:~ $ wait
[2]+ Exit 124 timeout 60 psql -p 5433 -ef update1.sql > session1.txt
सत्र का सामना करना पड़ा Transaction ... expired or aborted by a conflict
. यदि आप इसे कई बार चलाते हैं, तो आपको Operation expired: Transaction aborted: kAborted
, All transparent retries exhausted. Query error: Restart read required
या All transparent retries exhausted. Operation failed. Try again: Value write after transaction start
. वे सभी ERROR 40001 हैं जो क्रमांकन त्रुटियाँ हैं जो अनुप्रयोग से पुन:प्रयास करने की अपेक्षा करती हैं।
Serializable में, पूरे लेन-देन का पुन:प्रयास किया जाना चाहिए, और यह आमतौर पर डेटाबेस द्वारा पारदर्शी रूप से करना संभव नहीं है, जो यह नहीं जानता कि लेनदेन के दौरान एप्लिकेशन ने और क्या किया। उदाहरण के लिए, कुछ पंक्तियों को पहले ही पढ़ा जा चुका है, और उपयोगकर्ता स्क्रीन या फ़ाइल पर भेज दिया गया है। डेटाबेस उसे रोलबैक नहीं कर सकता। अनुप्रयोगों को इसे संभालना चाहिए।
मैंने \Timing on
. सेट किया है बीता हुआ समय पाने के लिए और, जैसा कि मैं इसे अपने लैपटॉप पर चला रहा हूं, क्लाइंट-सर्वर नेटवर्क महत्वपूर्ण समय नहीं है:
Franck@YB:~ $ awk '/Time/{print 5*int($2/5)}' session?.txt | sort -n | uniq -c
121 0
44 5
45 10
12 15
1 20
1 25
2 30
1 35
3 105
2 110
3 115
1 120
अधिकांश अपडेट यहां 5 मिलीसेकंड से कम थे। लेकिन याद रखें कि प्रोग्राम 40001
पर विफल हो गया जल्दी से तो यह मेरे लैपटॉप पर सामान्य एक-सत्र का कार्यभार है।
डिफ़ॉल्ट रूप से yb_enable_read_committed_isolation
गलत है और इस मामले में युगाबाइटडीबी की ट्रांजेक्शनल लेयर का रीड कमिटेड आइसोलेशन स्तर सख्त स्नैपशॉट आइसोलेशन पर वापस आ जाता है (जिस स्थिति में वाईएसक्यूएल के रीड कमिटेड और रीड अनकॉमिटेड स्नैपशॉट आइसोलेशन का उपयोग करते हैं)।
yb_enable_read_committed_isolation=true
अब इस सेटिंग को बदल रहे हैं, जो कि आपको तब करना चाहिए जब आप अपने PostgreSQL एप्लिकेशन के साथ संगत होना चाहते हैं जो किसी भी पुनः प्रयास तर्क को लागू नहीं करता है।
Franck@YB:~ $ docker rm -f yb
yb
[1]+ Exit 124 timeout 60 psql -p 5433 -ef update1.sql > session1.txt
Franck@YB:~ $ docker run --rm -d --name yb \
-p7000:7000 -p9000:9000 -p5433:5433 -p9042:9042 \
yugabytedb/yugabyte \
bin/yugabyted start --daemon=false \
--tserver_flags="yb_enable_read_committed_isolation=true"
fe3e84c995c440d1a341b2ab087510d25ba31a0526859f08a931df40bea43747
Franck@YB:~ $ curl -s http://localhost:9000/varz?raw | grep -E "\
(yb_enable_read_committed_isolation\
|ysql_output_buffer_size\
|ysql_sleep_before_retry_on_txn_conflict\
|ysql_max_write_restart_attempts\
|ysql_default_transaction_isolation\
)"
--yb_enable_read_committed_isolation=true
--ysql_max_write_restart_attempts=20
--ysql_output_buffer_size=262144
--ysql_sleep_before_retry_on_txn_conflict=true
--ysql_default_transaction_isolation=
ऊपर जैसा ही चल रहा है:
Franck@YB:~ $ psql -p 5433 -ec "
create table demo (id int primary key, val int);
insert into demo select generate_series(1,100000),0;
"
create table demo (id int primary key, val int);
insert into demo select generate_series(1,100000),0;
INSERT 0 100000
Franck@YB:~ $ timeout 60 psql -p 5433 -ef update1.sql >session1.txt &
Franck@YB:~ $ timeout 60 psql -p 5433 -ef update1.sql >session2.txt &
[1] 1032
[2] 1034
Franck@YB:~ $ wait
[1]- Exit 124 timeout 60 psql -p 5433 -ef update1.sql > session1.txt
[2]+ Exit 124 timeout 60 psql -p 5433 -ef update1.sql > session2.txt
मुझे कोई त्रुटि नहीं मिली, और दोनों सत्र 60 सेकंड के दौरान एक ही पंक्ति को अपडेट कर रहे हैं।
बेशक, यह ठीक उसी समय नहीं था जब डेटाबेस को कई लेन-देन का पुन:प्रयास करना पड़ा, जो बीता हुआ समय में दिखाई देता है:
Franck@YB:~ $ awk '/Time/{print 5*int($2/5)}' session?.txt | sort -n | uniq -c
325 0
199 5
208 10
39 15
11 20
3 25
1 50
34 105
40 110
37 115
13 120
5 125
3 130
जबकि अधिकांश लेन-देन अभी भी 10 मिलीसेकंड से कम हैं, कुछ जब 120 मिलीसेकंड तक पुनर्प्रयासों के कारण होते हैं।
बैकऑफ़ का पुनः प्रयास करें
एक सामान्य पुनर्प्रयास प्रत्येक पुनर्प्रयास के बीच अधिकतम समय तक घातांकीय समय की प्रतीक्षा करता है। यह वही है जो युगाबाइटडीबी में लागू किया गया है और निम्नलिखित 3 पैरामीटर, जिन्हें सत्र स्तर पर सेट किया जा सकता है, इसे नियंत्रित करता है:
Franck@YB:~ $ psql -p 5433 -xec "
select name, setting, unit, category, short_desc
from pg_settings
where name like '%retry%backoff%';
"
select name, setting, unit, category, short_desc
from pg_settings
where name like '%retry%backoff%';
-[ RECORD 1 ]---------------------------------------------------------
name | retry_backoff_multiplier
setting | 2
unit |
category | Client Connection Defaults / Statement Behavior
short_desc | Sets the multiplier used to calculate the retry backoff.
-[ RECORD 2 ]---------------------------------------------------------
name | retry_max_backoff
setting | 1000
unit | ms
category | Client Connection Defaults / Statement Behavior
short_desc | Sets the maximum backoff in milliseconds between retries.
-[ RECORD 3 ]---------------------------------------------------------
name | retry_min_backoff
setting | 100
unit | ms
category | Client Connection Defaults / Statement Behavior
short_desc | Sets the minimum backoff in milliseconds between retries.
मेरे स्थानीय डेटाबेस के साथ, लेन-देन कम हैं और मुझे इतना समय इंतजार नहीं करना पड़ता है। set retry_min_backoff to 10;
मेरे update1.sql
. पर इस पुनः प्रयास तर्क द्वारा बीता हुआ समय बहुत अधिक नहीं बढ़ाया गया है:
Franck@YB:~ $ awk '/Time/{print 5*int($2/5)}' session?.txt | sort -n | uniq -c
338 0
308 5
302 10
58 15
12 20
9 25
3 30
1 45
1 50
yb_debug_log_internal_restarts
पुनरारंभ पारदर्शी हैं। यदि आप पुनरारंभ करने का कारण देखना चाहते हैं, या यह क्यों संभव नहीं है, तो आप इसे yb_debug_log_internal_restarts=true
से लॉग इन करवा सकते हैं
# log internal restarts
export PGOPTIONS='-c yb_debug_log_internal_restarts=true'
# run concurrent sessions
timeout 60 psql -p 5433 -ef update1.sql >session1.txt &
timeout 60 psql -p 5433 -ef update1.sql >session2.txt &
# tail the current logfile
docker exec -i yb bash <<<'tail -F $(bin/ysqlsh -twAXc "select pg_current_logfile()")'
संस्करण
यह युगाबाइटडीबी 2.13 में लागू किया गया था और मैं यहां 2.13.1 का उपयोग कर रहा हूं। DO या ANALYZE कमांड से लेनदेन चलाते समय इसे अभी तक लागू नहीं किया गया है, लेकिन प्रक्रियाओं के लिए काम करता है। यदि आप इसे DO या ANALYZE में करना चाहते हैं तो आप #12254 अंक का अनुसरण और टिप्पणी कर सकते हैं।
https://github.com/yugabyte/yugabyte-db/issues/12254
निष्कर्ष में
एप्लिकेशन में रिट्री लॉजिक लागू करना घातक नहीं है बल्कि युगाबाइटडीबी में एक विकल्प है। एक वितरित डेटाबेस घड़ी की विषमता के कारण पुनरारंभ त्रुटियों को बढ़ा सकता है, लेकिन फिर भी जब संभव हो तो इसे SQL अनुप्रयोगों के लिए पारदर्शी बनाने की आवश्यकता होती है।
यदि आप सभी लेन-देन की विसंगतियों को रोकना चाहते हैं (इसे एक उदाहरण के रूप में देखें), तो आप Serializable में चला सकते हैं और 40001 अपवाद को संभाल सकते हैं। इस विचार से मूर्ख मत बनो कि इसके लिए अधिक कोड की आवश्यकता है, क्योंकि इसके बिना, आपको सभी दौड़ स्थितियों का परीक्षण करने की आवश्यकता है, जो एक बड़ा प्रयास हो सकता है। Serializable में, डेटाबेस सुनिश्चित करता है कि आपका व्यवहार क्रमानुसार चलने की तुलना में समान है ताकि आपके यूनिट परीक्षण डेटा की शुद्धता की गारंटी के लिए पर्याप्त हों।
हालाँकि, मौजूदा PostgreSQL एप्लिकेशन के साथ, डिफ़ॉल्ट आइसोलेशन स्तर का उपयोग करते हुए, व्यवहार को उत्पादन में चलने के वर्षों तक मान्य किया जाता है। आप जो चाहते हैं वह संभावित विसंगतियों से बचना नहीं है, क्योंकि एप्लिकेशन शायद उन्हें हल कर देगा। आप कोड को बदले बिना स्केल-आउट करना चाहते हैं। यहीं पर युगाबाइटडीबी रीड कमिटेड आइसोलेशन स्तर प्रदान करता है जिसके लिए किसी अतिरिक्त त्रुटि प्रबंधन कोड की आवश्यकता नहीं होती है।