PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Baca Berkomitmen adalah suatu keharusan untuk database SQL terdistribusi yang kompatibel dengan Postgres

Dalam database SQL, tingkat isolasi adalah hierarki pencegahan anomali pembaruan. Kemudian, orang berpikir bahwa semakin tinggi semakin baik, dan bahwa ketika database menyediakan Serializable, tidak perlu Read Committed. Namun:

  • Baca Dikomit adalah default di PostgreSQL . Konsekuensinya adalah sebagian besar aplikasi menggunakannya (dan menggunakan SELECT ... FOR UPDATE) untuk mencegah beberapa anomali
  • Dapat dibuat serial tidak skala dengan penguncian pesimis. Basis data terdistribusi menggunakan penguncian optimis, dan Anda perlu mengkodekan logika percobaan ulang transaksi

Dengan keduanya, database SQL terdistribusi yang tidak menyediakan isolasi Read Committed tidak dapat mengklaim kompatibilitas PostgreSQL, karena menjalankan aplikasi yang dibuat untuk default PostgreSQL tidak mungkin.

YugabyteDB dimulai dengan ide "semakin tinggi semakin baik" dan Read Committed secara transparan menggunakan "Snapshot Isolation". Ini benar untuk aplikasi baru. Namun, saat memigrasikan aplikasi yang dibuat untuk Read Committed, di mana Anda tidak ingin menerapkan logika coba lagi pada kegagalan serial (SQLState 40001), dan mengharapkan database melakukannya untuk Anda. Anda dapat beralih ke Read Committed dengan **yb_enable_read_committed_isolation** gflag.

Catatan:GFlag di YugabyteDB adalah parameter konfigurasi global untuk database, didokumentasikan dalam referensi yb-tserver. Parameter PostgreSQL, yang dapat disetel oleh ysql_pg_conf_csv GFlag hanya menyangkut YSQL API tetapi GFlags mencakup semua lapisan YugabyteDB

Dalam posting blog ini saya akan mendemonstrasikan nilai sebenarnya dari tingkat isolasi Read Committed:tidak perlu mengkode logika coba lagi karena, pada level ini, YugabyteDB dapat melakukannya sendiri.

Mulai YugabyteDB

Saya memulai database node tunggal YugabyteDB untuk demo sederhana ini:

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

Saya secara eksplisit tidak mengatur GFlag untuk menunjukkan perilaku default. Ini adalah version 2.13.0.0 build 42 .

Saya memeriksa gflags terkait yang telah dibaca

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=

Baca Berkomitmen adalah tingkat isolasi default, dengan kompatibilitas PostgreSQL:

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

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

Saya membuat tabel sederhana:

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

Saya akan menjalankan pembaruan berikut, menyetel tingkat isolasi default ke Read Committed (untuk berjaga-jaga - tetapi ini adalah default):

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

Ini akan memperbarui satu baris.
Saya akan menjalankan ini dari beberapa sesi, di baris yang sama:

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

Pada sesi ditemukan Transaction ... expired or aborted by a conflict . Jika Anda menjalankan hal yang sama beberapa kali, Anda mungkin juga mendapatkan Operation expired: Transaction aborted: kAborted , All transparent retries exhausted. Query error: Restart read required atau All transparent retries exhausted. Operation failed. Try again: Value write after transaction start . Semuanya adalah ERROR 40001 yang merupakan kesalahan serialisasi yang mengharapkan aplikasi untuk mencoba lagi.

Di Serializable, seluruh transaksi harus dicoba ulang, dan ini umumnya tidak mungkin dilakukan secara transparan oleh database, yang tidak tahu apa lagi yang dilakukan aplikasi selama transaksi. Misalnya, beberapa baris mungkin sudah dibaca, dan dikirim ke layar pengguna atau file. Basis data tidak dapat mengembalikannya. Aplikasi harus menangani itu.

Saya telah menyetel \Timing on untuk mendapatkan waktu yang telah berlalu dan, saat saya menjalankan ini di laptop saya, tidak ada waktu yang signifikan untuk jaringan server-klien:

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

Sebagian besar pembaruan kurang dari 5 milidetik di sini. Tapi ingat bahwa program gagal pada 40001 cepat jadi ini adalah beban kerja satu sesi yang normal di laptop saya.

Secara default yb_enable_read_committed_isolation salah dan dalam hal ini tingkat isolasi Read Committed dari lapisan transaksional YugabyteDB jatuh kembali ke Isolasi Snapshot yang lebih ketat (dalam hal ini READ COMMITTED dan READ UNCOMMITTED dari YSQL menggunakan Isolasi Snapshot).

yb_enable_read_committed_isolation=true

Sekarang ubah pengaturan ini, yang harus Anda lakukan ketika Anda ingin kompatibel dengan aplikasi PostgreSQL Anda yang tidak menerapkan logika coba lagi.

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=

Menjalankan sama seperti di atas:

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

Saya tidak mendapatkan kesalahan sama sekali, dan kedua sesi telah memperbarui baris yang sama selama 60 detik.

Tentu saja, itu tidak tepat pada saat yang sama karena database harus mencoba lagi banyak transaksi, yang terlihat dalam waktu yang telah berlalu:

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

Sementara sebagian besar transaksi masih kurang dari 10 milidetik, beberapa sampai 120 milidetik karena percobaan ulang.

coba mundur lagi

Percobaan ulang umum menunggu jumlah waktu eksponensial antara setiap percobaan ulang, hingga maksimum. Inilah yang diimplementasikan di YugabyteDB dan 3 parameter berikut, yang dapat diatur pada tingkat sesi, mengontrolnya:

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.

Dengan database lokal saya, transaksi menjadi singkat dan saya tidak perlu menunggu terlalu lama. Saat menambahkan set retry_min_backoff to 10; ke update1.sql saya waktu yang telah berlalu tidak terlalu meningkat oleh logika coba lagi ini:

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

Restart transparan. Jika Anda ingin melihat alasan restart, atau alasan mengapa itu tidak mungkin, Anda bisa memasukkannya ke log dengan 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()")'

Versi

Ini diimplementasikan di YugabyteDB 2.13 dan saya menggunakan 2.13.1 di sini. Itu belum diimplementasikan saat menjalankan transaksi dari perintah DO atau ANALYZE, tetapi berfungsi untuk prosedur. Anda dapat mengikuti dan mengomentari masalah #12254 jika Anda menginginkannya di LAKUKAN atau ANALISIS.

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

Kesimpulan

Menerapkan logika coba lagi dalam aplikasi bukanlah kematian tetapi pilihan di YugabyteDB. Basis data terdistribusi dapat meningkatkan kesalahan mulai ulang karena kemiringan jam, tetapi masih perlu membuatnya transparan untuk aplikasi SQL bila memungkinkan.

Jika Anda ingin mencegah semua anomali transaksi (lihat yang ini sebagai contoh), Anda dapat menjalankan di Serializable dan menangani pengecualian 40001. Jangan terkecoh dengan gagasan bahwa itu membutuhkan lebih banyak kode karena, tanpanya, Anda perlu menguji semua kondisi balapan, yang mungkin membutuhkan upaya yang lebih besar. Di Serializable, database memastikan bahwa Anda memiliki perilaku yang sama dengan menjalankan secara serial sehingga pengujian unit Anda cukup untuk menjamin kebenaran data.

Namun, dengan aplikasi PostgreSQL yang ada, menggunakan tingkat isolasi default, perilaku divalidasi oleh tahun berjalan dalam produksi. Yang Anda inginkan bukanlah menghindari kemungkinan anomali, karena aplikasi mungkin mengatasinya. Anda ingin melakukan scale-out tanpa mengubah kode. Di sinilah YugabyteDB menyediakan tingkat isolasi Read Committed yang tidak memerlukan kode penanganan kesalahan tambahan.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. INSERT dengan nama tabel dinamis dalam fungsi pemicu

  2. Menghitung Jumlah Kumulatif di PostgreSQL

  3. java.math.BigInteger tidak dapat dilemparkan ke java.lang.Integer

  4. Dapatkan nama tabel sumber baris saat menanyakan induk yang diwarisinya

  5. Alternatif pgAdmin - ClusterControl GUI Manajemen Database PostgreSQL