sql >> Database >  >> RDS >> PostgreSQL

Read Committed is een must voor Postgres-compatibele gedistribueerde SQL-databases

In SQL-databases zijn isolatieniveaus een hiërarchie van het voorkomen van updateafwijkingen. Dan denken mensen dat hoe hoger hoe beter, en dat wanneer een database Serializable biedt, Read Committed niet nodig is. Maar:

  • Read Committed is de standaardinstelling in PostgreSQL . Het gevolg is dat de meeste applicaties het gebruiken (en SELECT... FOR UPDATE gebruiken) om enkele afwijkingen te voorkomen
  • Serializeerbaar schaalt niet met pessimistische vergrendeling. Gedistribueerde databases gebruiken optimistische vergrendeling en u moet hun logica voor opnieuw proberen van transacties . coderen

Met deze twee kan een gedistribueerde SQL-database die geen Read Committed-isolatie biedt, geen aanspraak maken op PostgreSQL-compatibiliteit, omdat het onmogelijk is om applicaties uit te voeren die zijn gebouwd voor PostgreSQL-standaardinstellingen.

YugabyteDB begon met het "hoe hoger hoe beter" idee en Read Committed gebruikt transparant "Snapshot Isolation". Dit is correct voor nieuwe toepassingen. Wanneer u echter toepassingen migreert die zijn gebouwd voor Read Committed, waarbij u geen logica voor opnieuw proberen wilt implementeren op serialiseerbare fouten (SQLState 40001) en verwacht dat de database dit voor u doet. U kunt overschakelen naar Read Committed met de **yb_enable_read_committed_isolation** vlag.

Opmerking:een GFlag in YugabyteDB is een globale configuratieparameter voor de database, gedocumenteerd in yb-tserver-referentie. De PostgreSQL-parameters, die kunnen worden ingesteld door de ysql_pg_conf_csv GFlag heeft alleen betrekking op de YSQL API, maar GFlags dekt alle YugabyteDB-lagen

In deze blogpost zal ik de echte waarde van Read Committed-isolatieniveau demonstreren:het is het is niet nodig om een ​​logica voor opnieuw proberen te coderen omdat YugabyteDB het op dit niveau zelf kan.

YugabyteDB starten

Ik start een YugabyteDB-database met één knooppunt voor deze eenvoudige demo:

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

Ik heb expliciet geen GFlags ingesteld om het standaardgedrag te tonen. Dit is version 2.13.0.0 build 42 .

Ik controleer de read commit gerelateerde 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 is het standaard isolatieniveau, volgens PostgreSQL-compatibiliteit:

Franck@YB:~ $ psql -p 5433 \
-c "show default_transaction_isolation"

 default_transaction_isolation
-------------------------------
 read committed
(1 row)

Ik maak een eenvoudige tabel:

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

Ik zal de volgende update uitvoeren en het standaardisolatieniveau instellen op Read Committed (voor het geval dat - maar het is de standaard):

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

Hiermee wordt één rij bijgewerkt.
Ik zal dit uitvoeren vanuit meerdere sessies, op dezelfde rij:

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

Op sessie aangetroffen Transaction ... expired or aborted by a conflict . Als u hetzelfde meerdere keren uitvoert, krijgt u mogelijk ook Operation expired: Transaction aborted: kAborted , All transparent retries exhausted. Query error: Restart read required of All transparent retries exhausted. Operation failed. Try again: Value write after transaction start . Het zijn allemaal ERROR 40001 die serialisatiefouten zijn die verwachten dat de toepassing het opnieuw probeert.

In Serializable moet de hele transactie opnieuw worden geprobeerd, en dit is over het algemeen niet mogelijk om transparant te doen door de database, die niet weet wat de applicatie nog meer deed tijdens de transactie. Sommige rijen zijn bijvoorbeeld al gelezen en verzonden naar het gebruikersscherm of een bestand. De database kan dat niet terugdraaien. De applicaties moeten dat aan.

Ik heb \Timing on om de verstreken tijd te krijgen en aangezien ik dit op mijn laptop gebruik, is er geen significante tijd voor het client-servernetwerk:

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

De meeste updates waren hier minder dan 5 milliseconde. Maar onthoud dat het programma is mislukt op 40001 snel, dus dit is de normale werkbelasting van één sessie op mijn laptop.

Standaard yb_enable_read_committed_isolation is onwaar en in dit geval valt het Read Committed-isolatieniveau van de transactielaag van YugabyteDB terug naar de strengere Snapshot Isolation (in dat geval LEES COMMITTED en READ UNCOMMITTED van YSQL gebruik Snapshot Isolation).

yb_enable_read_committed_isolation=true

Verander nu deze instelling, wat u moet doen als u compatibel wilt zijn met uw PostgreSQL-toepassing die geen logica voor opnieuw proberen implementeert.

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=

Hetzelfde draaien als hierboven:

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

Ik heb helemaal geen foutmelding gekregen en beide sessies hebben gedurende 60 seconden dezelfde rij bijgewerkt.

Het was natuurlijk niet precies op hetzelfde moment dat de database veel transacties opnieuw moest proberen, wat zichtbaar is in de verstreken tijd:

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

Hoewel de meeste transacties nog steeds minder dan 10 milliseconden duren, duren sommige tot 120 milliseconden vanwege nieuwe pogingen.

probeer het opnieuw uit te schakelen

Een algemene nieuwe poging wacht een exponentiële hoeveelheid tijd tussen elke nieuwe poging, tot een maximum. Dit is wat is geïmplementeerd in YugabyteDB en de 3 volgende parameters, die op sessieniveau kunnen worden ingesteld, regelen het:

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.

Met mijn lokale database zijn transacties kort en hoef ik niet zo lang te wachten. Bij het toevoegen van set retry_min_backoff to 10; naar mijn update1.sql de verstreken tijd wordt niet te veel opgeblazen door deze logica voor opnieuw proberen:

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

De herstart is transparant. Als je de reden voor het opnieuw opstarten wilt zien, of de reden waarom het niet mogelijk is, kun je het laten loggen met 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()")'

Versies

Dit is geïmplementeerd in YugabyteDB 2.13 en ik gebruik hier 2.13.1. Het is nog niet geïmplementeerd bij het uitvoeren van de transactie vanuit de opdrachten DO of ANALYZE, maar werkt voor procedures. Je kunt probleem #12254 volgen en becommentariëren als je wilt in DOEN of ANALYSEREN.

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

Tot slot

Het implementeren van logica voor opnieuw proberen in de toepassing is geen fataliteit, maar een keuze in YugabyteDB. Een gedistribueerde database kan herstartfouten veroorzaken vanwege een scheve klok, maar moet deze waar mogelijk nog steeds transparant maken voor SQL-applicaties.

Als u alle anomalieën van transacties wilt voorkomen (zie deze als voorbeeld), kunt u in Serializable draaien en de 40001-uitzondering afhandelen. Laat u niet misleiden door het idee dat er meer code nodig is, want zonder code moet u alle raceomstandigheden testen, wat een grotere inspanning kan zijn. In Serializable zorgt de database ervoor dat u hetzelfde gedrag vertoont als serieel draaien, zodat uw unit-tests voldoende zijn om de juistheid van de gegevens te garanderen.

Met een bestaande PostgreSQL-toepassing, die het standaardisolatieniveau gebruikt, wordt het gedrag echter gevalideerd door jaren van productie. Wat u wilt, is niet dat u de mogelijke afwijkingen vermijdt, omdat de toepassing ze waarschijnlijk omzeilt. U wilt uitschalen zonder de code te wijzigen. Dit is waar YugabyteDB het Read Committed-isolatieniveau biedt waarvoor geen extra foutafhandelingscode nodig is.


  1. Oracle GROUP_CONCAT() Equivalent

  2. Databasetabellen maken met SQL

  3. Hoe de spring-boot-app te starten zonder afhankelijk te zijn van de database?

  4. SYS-wachtwoord wijzigen in RAC