PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Read Committed е задължително за Postgres-съвместими разпределени SQL бази данни

В SQL бази данни нивата на изолация са йерархия за предотвратяване на аномалии при актуализиране. Тогава хората смятат, че колкото по-високо е, толкова по-добре и че когато базата данни предоставя Serializable, няма нужда от Read Committed. Въпреки това:

  • Read Committed е по подразбиране в PostgreSQL . Последствието е, че повечето приложения го използват (и използват SELECT ... FOR UPDATE), за да предотвратят някои аномалии
  • Може да се сериализира не се мащабира с песимистично заключване. Разпределените бази данни използват оптимистично заключване и трябва да кодирате тяхната логика за повторни транзакции

С тези две разпределена SQL база данни, която не осигурява изолация на Read Committed, не може да претендира за съвместимост с PostgreSQL, тъй като стартирането на приложения, които са били изградени за PostgreSQL по подразбиране, е невъзможно.

YugabyteDB започна с идеята "колкото по-високо, толкова по-добре" и Read Committed използва прозрачно "Snapshot Isolation". Това е правилно за нови приложения. Въпреки това, когато мигрирате приложения, създадени за Read Committed, където не искате да внедрите логика за повторен опит при сериализиращи се откази (SQLState 40001) и очаквате базата данни да го направи вместо вас. Можете да превключите към Read Committed с **yb_enable_read_committed_isolation** gflag.

Забележка:GFlag в YugabyteDB е глобален конфигурационен параметър за базата данни, документиран в справка за yb-tserver. Параметрите на PostgreSQL, които могат да бъдат зададени от ysql_pg_conf_csv GFlag засяга само YSQL API, но GFlags покрива всички слоеве на YugabyteDB

В тази публикация в блога ще демонстрирам реалната стойност на нивото на изолация Read Committed:няма не е необходимо да се кодира логика за повторен опит защото на това ниво YugabyteDB може да го направи сам.

Стартирайте 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=

Read Committed е нивото на изолация по подразбиране чрез съвместимост с 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

Ще стартирам следната актуализация, като задам нивото на изолация по подразбиране на Read Committed (за всеки случай - но това е по подразбиране):

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 е невярно и в този случай нивото на изолация Read Committed на транзакционния слой на YugabyteDB се връща към по-строгата изолация на моментни снимки (в този случай READ COMMITTED и READ UNCOMMITTED на YSQL използват изолация на моментни снимки).

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 милисекунди поради повторни опити.

повторен опит за отмяна

Един общ повторен опит изчаква експоненциално време между всеки опит, до максимум. Това е, което е внедрено в YugabyteDB и 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()")'

Версии

Това беше внедрено в YugabyteDB 2.13 и аз използвам 2.13.1 тук. Той все още не е внедрен при изпълнение на транзакцията от команди DO или ANALYZE, но работи за процедури. Можете да следвате и коментирате проблем #12254, ако го искате в DO или ANALYZE.

https://github.com/yugabyte/yugabyte-db/issues/12254

В заключение

Внедряването на логиката за повторен опит в приложението не е фатално, а избор в YugabyteDB. Разпределената база данни може да доведе до грешки при рестартиране поради изкривяване на часовника, но все пак трябва да я направи прозрачна за SQL приложенията, когато е възможно.

Ако искате да предотвратите всички аномалии на транзакциите (вижте тази като пример), можете да стартирате в Serializable и да обработвате изключението 40001. Не се заблуждавайте от идеята, че изисква повече код, защото без него трябва да тествате всички условия на състезанието, което може да е по-голямо усилие. В Serializable базата данни гарантира, че имате същото поведение, отколкото при серийно изпълнение, така че вашите модулни тестове са достатъчни, за да гарантират коректността на данните.

Въпреки това, със съществуващо приложение PostgreSQL, използващо нивото на изолация по подразбиране, поведението се потвърждава от години на работа в производство. Това, което искате, е да не избягвате възможните аномалии, защото приложението вероятно ги заобикаля. Искате да мащабирате, без да променяте кода. Тук YugabyteDB предоставя ниво на изолация Read Committed, което не изисква допълнителен код за обработка на грешки.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Задайте празните низове ('') на NULL в цялата база данни

  2. PostgreSQL е най-добрата база данни в света

  3. Как да проверите състоянието на PostgreSQL сървъра Mac OS X

  4. За предимствата на сортираните пътища

  5. Как да отстраня грешки в запаметените процедури на postgresql?