sql >> データベース >  >> RDS >> PostgreSQL

Read Committedは、Postgres互換の分散SQLデータベースには必須です

    SQLデータベースでは、分離レベルは更新異常防止の階層です。次に、人々は、高いほど良いと考え、データベースがSerializableを提供する場合、ReadCommittedは必要ないと考えます。ただし:

    • ReadCommittedはPostgreSQLのデフォルトです 。その結果、アプリケーションの大部分がいくつかの異常を防ぐためにそれを使用しています(そしてSELECT ... FOR UPDATEを使用しています)
    • シリアル化可能 悲観的なロックではスケーリングしません。分散データベースは楽観的ロックを使用しており、トランザクション再試行ロジックをコーディングする必要があります

    これら2つでは、PostgreSQLのデフォルト用にビルドされたアプリケーションを実行することは不可能であるため、ReadCommitted分離を提供しない分散SQLデータベースはPostgreSQLの互換性を主張できません。

    YugabyteDBは「高いほど良い」という考えから始まり、ReadCommittedは「スナップショットアイソレーション」を透過的に使用しています。これは、新しいアプリケーションに適しています。ただし、読み取りコミット用に構築されたアプリケーションを移行する場合、シリアル化可能な障害(SQLState 40001)に再試行ロジックを実装したくない場合は、データベースがそれを実行することを期待します。 **yb_enable_read_committed_isolation**を使用してReadCommittedに切り替えることができます gflag。

    注:YugabyteDBのGFlagは、データベースのグローバル構成パラメーターであり、yb-tserverリファレンスに記載されています。 ysql_pg_conf_csvで設定できるPostgreSQLパラメータ GFlagはYSQLAPIのみに関係しますが、GFlagsはすべてのYugabyteDBレイヤーをカバーします

    このブログ投稿では、ReadCommitted分離レベルの真の価値をデモします。再試行ロジックをコーディングする必要はありません なぜなら、このレベルでは、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
    

    次の更新を実行し、デフォルトの分離レベルを読み取りコミットに設定します(念のため-ただし、これがデフォルトです)。

    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
    

    これにより、1行が更新されます。
    同じ行の複数のセッションからこれを実行します。

    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 。これらはすべてERROR40001であり、アプリケーションが再試行することを期待するシリアル化エラーです。

    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で失敗したことを忘れないでください すぐに、これは私のラップトップでの通常の1セッションのワークロードです。

    デフォルトでは、yb_enable_read_committed_isolation はfalseであり、この場合、YugabyteDBのトランザクションレイヤーの読み取りコミット分離レベルは、より厳密なスナップショット分離にフォールバックします(この場合、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()")'
    

    バージョン

    これはYugabyteDB2.13で実装されており、ここでは2.13.1を使用しています。 DOまたはANALYZEコマンドからトランザクションを実行する場合はまだ実装されていませんが、プロシージャに対しては機能します。 DOまたはANALYZEで必要な場合は、問題#12254をフォローしてコメントすることができます。

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

    結論

    アプリケーションに再試行ロジックを実装することは致命的ではなく、YugabyteDBでの選択です。分散データベースでは、クロックスキューが原因で再起動エラーが発生する可能性がありますが、可能な場合はSQLアプリケーションに対して透過的にする必要があります。

    すべてのトランザクションの異常を防止したい場合(例としてこれを参照)、Serializableで実行し、40001例外を処理できます。より多くのコードが必要であるという考えに惑わされないでください。コードがないと、すべての競合状態をテストする必要があり、これはより大きな労力になる可能性があります。 Serializableでは、データベースにより、シリアルで実行する場合と同じ動作が保証されるため、データの正確性を保証するために単体テストで十分です。

    ただし、既存のPostgreSQLアプリケーションでは、デフォルトの分離レベルを使用して、本番環境で何年も実行することで動作が検証されます。アプリケーションがおそらくそれらを回避するので、あなたが望むのは起こりうる異常を回避することではありません。コードを変更せずにスケールアウトしたい。これは、YugabyteDBが追加のエラー処理コードを必要としないReadCommitted分離レベルを提供する場所です。


    1. MySQL初心者向けのDevOpsデータベース用語集

    2. SQL Server Express Editionでストアドプロシージャを毎日実行するにはどうすればよいですか?

    3. 外部キーがある場合とない場合の参照の違いは何ですか

    4. Java/jspでMSSQLServerストアドプロシージャを実行して、テーブルデータを返すにはどうすればよいですか?