PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Đọc cam kết là điều bắt buộc đối với cơ sở dữ liệu SQL phân tán tương thích với Postgres

Trong cơ sở dữ liệu SQL, các mức cô lập là một hệ thống phân cấp để ngăn chặn sự bất thường của bản cập nhật. Sau đó, mọi người nghĩ rằng càng cao càng tốt và khi cơ sở dữ liệu cung cấp Khả năng nối tiếp thì không cần có Đã cam kết đọc. Tuy nhiên:

  • Đã cam kết đọc là mặc định trong PostgreSQL . Hậu quả là phần lớn các ứng dụng đang sử dụng nó (và sử dụng SELECT ... FOR UPDATE) để ngăn chặn một số bất thường
  • Có thể nối tiếp hóa không mở rộng với khóa bi quan. Cơ sở dữ liệu phân tán sử dụng khóa lạc quan và bạn cần mã logic thử lại giao dịch của chúng

Với hai cơ sở dữ liệu SQL đó, cơ sở dữ liệu SQL phân tán không cung cấp tính năng cách ly Đã đọc được cam kết không thể yêu cầu khả năng tương thích với PostgreSQL, vì không thể chạy các ứng dụng được tạo cho mặc định PostgreSQL.

YugabyteDB bắt đầu với ý tưởng "càng cao càng tốt" và Read Cam kết đang sử dụng "Snapshot Isolation" một cách minh bạch. Điều này đúng cho các ứng dụng mới. Tuy nhiên, khi di chuyển các ứng dụng được xây dựng cho Đã cam kết đọc, nơi bạn không muốn triển khai logic thử lại đối với các lỗi có thể tuần tự hóa (SQLState 40001) và mong đợi cơ sở dữ liệu thực hiện điều đó cho bạn. Bạn có thể chuyển sang Đã cam kết đã đọc với **yb_enable_read_committed_isolation** gflag.

Lưu ý:GFlag trong YugabyteDB là tham số cấu hình chung cho cơ sở dữ liệu, được ghi lại trong tham chiếu yb-tserver. Các tham số PostgreSQL, có thể được đặt bởi ysql_pg_conf_csv GFlag chỉ quan tâm đến API YSQL nhưng GFlags bao gồm tất cả các lớp YugabyteDB

Trong bài đăng trên blog này, tôi sẽ giới thiệu giá trị thực của mức cô lập Đã cam kết Đọc:không cần không cần viết mã logic thử lại bởi vì, ở cấp độ này, YugabyteDB có thể tự làm điều đó.

Khởi động YugabyteDB

Tôi đang bắt đầu một cơ sở dữ liệu nút đơn YugabyteDB cho bản trình diễn đơn giản này:

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

Tôi rõ ràng không đặt bất kỳ thẻ GFlags nào để hiển thị hành vi mặc định. Đây là version 2.13.0.0 build 42 .

Tôi kiểm tra các gflags liên quan được cam kết đã đọc

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=

Đã cam kết đọc là mức cô lập mặc định, bởi khả năng tương thích của PostgreSQL:

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

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

Tôi tạo một bảng đơn giản:

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

Tôi sẽ chạy bản cập nhật sau, đặt mức cách ly mặc định thành Đã cam kết đọc (đề phòng - nhưng nó là mặc định):

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

Điều này sẽ cập nhật một hàng.
Tôi sẽ chạy điều này từ nhiều phiên, trên cùng một hàng:

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

Trong phiên gặp phải Transaction ... expired or aborted by a conflict . Nếu bạn chạy cùng một vài lần, bạn cũng có thể nhận được Operation expired: Transaction aborted: kAborted , All transparent retries exhausted. Query error: Restart read required hoặc All transparent retries exhausted. Operation failed. Try again: Value write after transaction start . Tất cả đều là LỖI 40001 là lỗi tuần tự hóa mong muốn ứng dụng thử lại.

Trong Serializable, toàn bộ giao dịch phải được thử lại và điều này thường không thể thực hiện một cách minh bạch bởi cơ sở dữ liệu, điều đó không biết ứng dụng đã làm gì khác trong giao dịch. Ví dụ:một số hàng có thể đã được đọc và được gửi đến màn hình người dùng hoặc một tệp. Cơ sở dữ liệu không thể khôi phục điều đó. Các ứng dụng phải xử lý điều đó.

Tôi đã đặt \Timing on để có được thời gian đã trôi qua và vì tôi đang chạy tính năng này trên máy tính xách tay của mình, không có thời gian đáng kể cho mạng máy khách-máy chủ:

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

Hầu hết các bản cập nhật đều dưới 5 mili giây ở đây. Nhưng hãy nhớ rằng chương trình không thành công vào 40001 nhanh chóng nên đây là khối lượng công việc một phiên bình thường trên máy tính xách tay của tôi.

Theo mặc định yb_enable_read_committed_isolation là false và trong trường hợp này, mức cách ly Đã đọc được cam kết của lớp giao dịch của YugabyteDB trở về mức Cách ly Ảnh chụp chặt chẽ hơn (trong trường hợp này, ĐỌC CAM KẾT và ĐỌC KHÔNG ĐƯỢC ĐỀ XUẤT của YSQL sử dụng Cách ly Ảnh chụp nhanh).

yb_enable_read_comiled_isolation =true

Bây giờ thay đổi cài đặt này, đó là những gì bạn nên làm khi bạn muốn tương thích với ứng dụng PostgreSQL của mình mà không triển khai bất kỳ logic thử lại nào.

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=

Chạy tương tự như trên:

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

Tôi không gặp lỗi gì cả và cả hai phiên đều cập nhật cùng một hàng trong 60 giây.

Tất nhiên, nó không chính xác cùng lúc vì cơ sở dữ liệu phải thử lại nhiều giao dịch, có thể nhìn thấy trong thời gian đã trôi qua:

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

Trong khi hầu hết các giao dịch vẫn dưới 10 mili giây, một số giao dịch có thể lên đến 120 mili giây do thử lại.

thử lại quá trình

Một lần thử lại thông thường sẽ đợi một khoảng thời gian theo cấp số nhân giữa mỗi lần thử lại, tối đa là tối đa. Đây là những gì được triển khai trong YugabyteDB và 3 tham số sau, có thể được đặt ở cấp phiên, sẽ kiểm soát nó:

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.

Với cơ sở dữ liệu cục bộ của tôi, các giao dịch diễn ra rất ngắn và tôi không phải đợi quá nhiều thời gian. Khi thêm set retry_min_backoff to 10; vào update1.sql của tôi thời gian đã trôi qua không bị tăng quá nhiều bởi logic thử lại này:

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

Các phần khởi động lại là trong suốt. Nếu bạn muốn xem lý do khởi động lại hoặc lý do không thể khởi động lại, bạn có thể đăng nhập bằng 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()")'

Các phiên bản

Điều này đã được triển khai trong YugabyteDB 2.13 và tôi đang sử dụng 2.13.1 ở đây. Nó chưa được triển khai khi chạy giao dịch từ các lệnh DO hoặc ANALYZE, nhưng hoạt động cho các thủ tục. Bạn có thể theo dõi và nhận xét vấn đề # 12254 nếu bạn muốn nó trong DO hoặc PHÂN TÍCH.

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

Kết luận

Việc thực hiện logic thử lại trong ứng dụng không phải là một điều nguy hiểm mà là một lựa chọn trong YugabyteDB. Cơ sở dữ liệu phân tán có thể gây ra lỗi khởi động lại do đồng hồ lệch, nhưng vẫn cần làm cho nó trong suốt với các ứng dụng SQL khi có thể.

Nếu bạn muốn ngăn chặn tất cả các bất thường của giao dịch (xem ví dụ này là một ví dụ), bạn có thể chạy trong Serializable và xử lý ngoại lệ 40001. Đừng để bị lừa bởi ý tưởng rằng nó yêu cầu nhiều mã hơn bởi vì nếu không có nó, bạn cần phải kiểm tra tất cả các điều kiện của cuộc đua, đó có thể là một nỗ lực lớn hơn. Trong Serializable, cơ sở dữ liệu đảm bảo rằng bạn có cùng hành vi với việc chạy nối tiếp để các bài kiểm tra đơn vị của bạn đủ để đảm bảo tính đúng đắn của dữ liệu.

Tuy nhiên, với một ứng dụng PostgreSQL hiện có, sử dụng mức cách ly mặc định, hành vi được xác thực theo năm chạy trong sản xuất. Những gì bạn muốn không phải là tránh những điều bất thường có thể xảy ra, bởi vì ứng dụng có thể giải quyết chúng. Bạn muốn mở rộng quy mô mà không cần thay đổi mã. Đây là nơi YugabyteDB cung cấp mức cách ly Đã đọc cam kết mà không yêu cầu mã xử lý lỗi bổ sung.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cách AT TIME ZONE hoạt động trong PostgreSQL

  2. Mẹo &Thủ thuật để Điều hướng Cộng đồng PostgreSQL

  3. chuyển đổi định dạng hình học Postgres sang WKT

  4. Heroku và Rails:Lỗi tải đá quý với Postgres, tuy nhiên nó được chỉ định trong GEMFILE

  5. Kiểm soát phiên bản PostgreSQL với Atlassian Bitbucket