※ 本記事は、Stephane Dupratによる”Lock-free reservation in 23c: scale your apps“を翻訳したものです。
2023年6月15日
前の記事では、私の同僚のUlrike Schwinnが23cの新しい「ロックフリーの予約」機能を紹介しました。スケーラビリティに関する考慮事項を入力する前に、この投稿(23cでのロックフリー予約: 開始方法)を読むことをお薦めします。Oracleデータベースでの概念とその実装を理解してから、さらに読むことが重要です。
カーディナリティの低い表を更新するトランザクションのコンテキストでは、通常、スケーラビリティの問題が発生し、同時実行性によって指数関数的に増加します。この製品が購入されるたびに製品の単位数を更新するeコマース・アプリケーションを想像してください。通常、大量の同時セッションでは、カーディナリティの低い表を更新して、特定の製品の単位数を減らそうとします。これにより、各コンカレント・セッションでストック表内の特定の行に対して排他ロックを取得する必要があるため、スケーラビリティの問題が発生します。これは、標準ロック機構で軽減することは容易ではありません。
ロックフリー予約機能により、「仕訳表」という概念が導入されます。行を更新するかわりに、仕訳表に新しい行を挿入し、更新する行をロックしないようにします。コミット時に、行を実際に更新するには排他ロックが必要です。ただし、コミット時のみであるため、ロックはすぐに解放されます。
このコンテキストでは、この新機能で得られるスケーラビリティの利点は何ですか。23c free databaseに対して、いくつかのテストを実行しましょう。
テスト1: Standardロック – OLTPモード
このテストでは、次のコードを使用します。:
--- Create a table: we will update concurrently the VAL column
create table T_COUNTER2
(
ID number primary key,
VAL NUMBER
);
-- Insert a couple of rows
insert into T_COUNTER2(ID,VAL) values (0,0),(1,0);
commit;
-- Create a procedure that will act as an online application
-- We add a sleep to simulate a think time
CREATE OR REPLACE PROCEDURE PC_UPD_COUNTER2_TT
IS
v_id PLS_INTEGER;
v_incr PLS_INTEGER;
BEGIN
v_id := mod(floor(dbms_random.value(1,1000000)),2);
v_incr := mod(floor(dbms_random.value(1,1000000)),2);
--
update T_COUNTER2
set VAL = VAL+v_incr
where ID = v_id;
--- Sleep 1s to simulate think time
DBMS_SESSION.SLEEP(1);
--
commit;
END PC_UPD_COUNTER2_TT;
/
ここでは、このPL/SQLプロシージャを使用して、いくつかの同時実行性を生成します。2つのSQL*Plusセッションを開き、それぞれのセッションで次のコードを実行します。:
BEGIN
for i in 1..200
LOOP
PC_UPD_COUNTER2_TT;
END LOOP;
END;
/
このコードの実行前後にAWRスナップショットを収集するように注意してください。次に、これら2つのスナップショットの間にAWRレポートを生成します。
これは、TOP 10フォアグラウンド・イベントのAWR出力です。:

DB時間の99%以上がイベント「enq: TX – row lock contention」に費やされており、スケーラビリティに問題があることがわかります。これは、標準の行ロック・メカニズムが原因です。行を更新するには、この行の排他ロックを取得する必要があります。
また、1秒の思考時間のために、このロックは各更新操作中に1秒保持されます。
テスト2: ロックフリー予約列の使用 – OLTPモード
次に、新しいロックフリー予約列を使用して同じユースケースを作成します。:
-- Create a table with a RESERVABLE column:
create table T_COUNTER
(
ID number primary key,
VAL NUMBER RESERVABLE
);
-- Insert a couple of rows:
insert into T_COUNTER(ID,VAL) values (0,0),(1,0);
commit;
-- Create a PL/SQL procedure, that will be used to simulate an online application
-- We add a sleep to simulate a think time
CREATE OR REPLACE PROCEDURE PC_UPD_COUNTER_TT
IS
v_id PLS_INTEGER;
v_incr PLS_INTEGER;
BEGIN
v_id := mod(floor(dbms_random.value(1,1000000)),2);
v_incr := mod(floor(dbms_random.value(1,1000000)),2);
--
update T_COUNTER
set VAL = VAL+v_incr
where ID = v_id;
--- Sleep 1s to simulate think time
DBMS_SESSION.SLEEP(1);
--
commit;
END PC_UPD_COUNTER_TT;
/
ここでは、このPL/SQLプロシージャを使用して、いくつかの同時実行性を生成します。2つのSQL*Plusセッションを開き、それぞれのセッションで次のコードを実行します。:
BEGIN
for i in 1..200
LOOP
PC_UPD_COUNTER_TT;
END LOOP;
END;
/
このコードの実行前後にAWRスナップショットを収集するように注意してください。次に、これら2つのスナップショットの間にAWRレポートを生成します。
これは、TOP 10フォアグラウンド・イベントのAWR出力です。:

「enq: TX – row lock contention」に対する待機がどのように消えたかを確認します。排他ロックはコミットでのみ必要であるため、アプリケーション・セッションは更新する行の排他ロックを保持していると考えられる時間を費やしません。
しかし、考える時間がなければどうなるでしょうか。
つまり、コードから「sleep」命令を削除して、あまり一般的でない「row by row」バッチをシミュレートするには、どうすればよいでしょうか。
テスト3: Standardロック – バッチ・モード
このテストでは、次のコードを使用します。:
--- Create a table: we will update concurrently the VAL column
create table T_COUNTER2
(
ID number primary key,
VAL NUMBER
);
-- Insert a couple of rows
insert into T_COUNTER2(ID,VAL) values (0,0),(1,0);
commit;
-- Create a procedure that will act as an row by row batch processing
CREATE OR REPLACE PROCEDURE PC_UPD_COUNTER2
IS
v_id PLS_INTEGER;
v_incr PLS_INTEGER;
BEGIN
v_id := mod(floor(dbms_random.value(1,1000000)),2);
v_incr := mod(floor(dbms_random.value(1,1000000)),2);
--
update T_COUNTER2
set VAL = VAL+v_incr
where ID = v_id;
--
commit;
END PC_UPD_COUNTER2;
/
ここでは、このPL/SQLプロシージャを使用して、いくつかの同時実行性を生成します。2つのSQL*Plusセッションを開き、それぞれのセッションで次のコードを実行します。:
BEGIN
for i in 1..100000
LOOP
PC_UPD_COUNTER2;
END LOOP;
END;
/
このコードの実行前後にAWRスナップショットを収集するように注意してください。次に、これら2つのスナップショットの間にAWRレポートを生成します。
これは、TOP 10フォアグラウンド・イベントのAWR出力です。:

イベント「enq: TX – row lock contention」に待機がある場合でも、DB時間の割合がスケーラビリティの問題を表していないことを確認します。思考時間がないので、排他ロックは非常に短い時間(平均で70マイクロ秒、テスト1では平均でほぼ1秒)を保持しているからです。
次に、予約可能な列でこのテストを繰り返します。
テスト4: ロックフリー予約列の使用 – バッチ・モード
この最後のテストでは、次のコードを使用します。:
-- Create a table with a RESERVABLE column:
create table T_COUNTER
(
ID number primary key,
VAL NUMBER RESERVABLE
);
-- Insert a couple of rows:
insert into T_COUNTER(ID,VAL) values (0,0),(1,0);
commit;
-- Create a PL/SQL procedure that will be used to simulate a row-by-row batch
CREATE OR REPLACE PROCEDURE PC_UPD_COUNTER
IS
v_id PLS_INTEGER;
v_incr PLS_INTEGER;
BEGIN
v_id := mod(floor(dbms_random.value(1,1000000)),2);
v_incr := mod(floor(dbms_random.value(1,1000000)),2);
--
update T_COUNTER
set VAL = VAL+v_incr
where ID = v_id;
--
commit;
END PC_UPD_COUNTER;
/
ここでは、このPL/SQLプロシージャを使用して、いくつかの同時実行性を生成します。2つのSQL*Plusセッションを開き、それぞれのセッションで次のコードを実行します。:
BEGIN
for i in 1..100000
LOOP
PC_UPD_COUNTER;
END LOOP;
END;
/
このコードの実行前後にAWRスナップショットを収集するように注意してください。次に、これら2つのスナップショットの間にAWRレポートを生成します。
これは、TOP10フォアグラウンド・イベントのAWR出力です:

この場合、「enq: TX – row lock contention」待機イベントに対する実際の軽減はありません。「enq: TX – row lock contention」イベントで待機している4.9秒(前のテストで1.6秒)が、前のテストで平均248マイクロ秒と70マイクロ秒で保持されているロックであることを確認します。また、このテストでは、CPU時間は116秒と21秒の間で大幅に増加しました。
まとめ
「オンライン・アプリケーション・テスト」(テスト1および2)中に、「enq: TX – row lock contention」待機イベントで大きな違いが見られます。標準行ロックを使用したDB時間の99%以上から、予約可能な列の使用時に上位10のフォアグラウンド待機イベントから消えます。
この場合、新しいロックフリーの予約メカニズムによってスケーラビリティの問題が解消されました。そのため、オンライン・アプリケーションが集中的に更新され、同時実行性が非常に低いカーディナリティ表を使用して更新すると、この新機能を利用する可能性が非常に高くなると言えます。
一方、ロックフリーの予約は、オンライン・ショッピング・カート・アプリケーションなどのOLTP同時実行性用に設計されており、前回のテストで示したrow-by-rowのバッチ処理用に設計されていないことに注意してください。
