オンライン取引やeコマース業界の発展に伴い、業務システムにおけるホットスポットへの同時アクセス負荷が課題となっています。短期間に特定口座の残高が大量に更新されたり、セールイベントで人気商品が瞬時に売り切れたりするのが、その典型的なシナリオです。ホットスポット更新の本質は、データベース内の同一行データの特定フィールド(残高、在庫など)に対して、短時間に高並行で変更を加えることです。リレーショナルデータベースでは、トランザクションの一貫性を保つため、データ行の更新には「ロック取得→更新→ログ書き込み・コミット→ロック解放」というプロセスが必要であり、このプロセスは実質的に直列実行されます。したがって、ホットスポット行更新機能を向上させる鍵は、ロックの保持時間を可能な限り短縮することにあります。
学術界では早くから「早期行ロック解放(Early Lock Release)」(ELR)の方式が提案されてきましたが、ELRの異常処理シナリオが非常に複雑であるため、業界では成熟した実装例はほとんどありません。OceanBaseデータベースは、この問題に対して継続的に取り組み、分散アーキテクチャに基づく実装方法を提案し、同様の業務シナリオにおける単一行の並行更新能力を向上させました。これは、OceanBaseデータベースの「スケーラブルなOLTP」における重要な機能の一つです。
本記事では、高並行な単一行更新のシナリオを構築し、OceanBaseデータベースのELR機能の使用方法と効果を比較します。高並行負荷シナリオでの検証となるため、少なくとも本例と同等のノード仕様で体験・検証することを推奨し、より良い効果を得られます。OceanBaseデータベースのELRの設計と実装原理については、複雑であるため、本記事では詳細な議論は行いません。
本例では、16コア128GB構成のノードを1台使用します。以下、手順を追ってOceanBaseデータベースのELR機能を体験します。
ステップ1:テストテーブルを作成し、テストデータを挿入する
まず、テストデータベースにテーブルを作成し、テストデータを挿入します。
CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
);
INSERT INTO sbtest1 VALUES(1,0,'aa','aa');
この例では、UPDATE sbtest1 SET k=k+1 WHERE id=1 のようなステートメントを使用して、主キー検索方式で k 列に対する同時更新を行います。より多くのデータを挿入してテストすることも可能ですが、単一行に対する同時負荷テストであるため、全体の結果には基本的に影響しません。
ステップ2:同時更新シナリオを構築する
この例では、Pythonのマルチスレッドを使用して同時更新をシミュレートします。50個のスレッドを同時に起動し、各スレッドがid=1の行データのkフィールド値を+1します。ご自身の環境でも、以下のスクリプト ob_elr.py を直接使用してテストできます。スクリプト内のデータベース接続情報を変更するだけで使用可能です。
#!/usr/bin/env python3
from concurrent.futures import ThreadPoolExecutor
import pymysql
import time
import threading
# database connection info
config = {
'user': 'root@test',
'password': '****',
'host': 'xxx.xxx.xxx.xxx',
'port': 2881,
'database': 'test'
}
# parallel thread and updates in each thread
parallel = 50
batch_num = 2000
# update query
def update_elr():
update_hot_row = ("update sbtest1 set k=k+1 where id=1")
cnx = pymysql.connect(**config)
cursor = cnx.cursor()
for i in range(0,batch_num):
cursor.execute(update_hot_row)
cursor.close()
cnx.close()
start=time.time()
with ThreadPoolExecutor(max_workers=parallel) as pool:
for i in range(parallel):
pool.submit(update_elr)
end = time.time()
elapse_time = round((end-start),2)
print('Parallel Degree:',parallel)
print('Total Updates:',parallel*batch_num)
print('Elapse Time:',elapse_time,'s')
print('TPS on Hot Row:' ,round(parallel*batch_num/elapse_time,2),'/s')
ステップ3:デフォルト設定でテストを実行する
比較の参考として、まずELRをデフォルトで有効化しない状態でテストします。テストマシン上で直接 ob_elr.py スクリプトを実行します。
この例では、並列数50、合計更新回数100000回を採用します。
./ob_elr.py
実行完了後、テストスクリプトは実行時間とTPSを出力します:
[root@obce00 ~]# ./ob_elr.py
Parallel Degree: 50
Total Updates: 100000
Elapse Time: 54.5 s
TPS on Hot Row: 1834.86 /s
テスト結果は以下のとおりです:
ELRを有効化しないデフォルト設定では、この例のテスト環境において、単一行の同時更新TPSは1834.86/sでした。
ステップ4:OceanBaseデータベースのELR設定を有効にする
次に、OceanBaseデータベースのホットスポット行機能を有効にします。まず、rootユーザーでクラスタのsysテナントにログインする必要があります。
[root@obce00 ~]# obclient -h127.0.0.1 -P2881 -uroot@sys -Doceanbase -A -p -c
その後、以下の2つのパラメータを設定します。enable_early_lock_releaseパラメータの適用範囲は、特定のテナントを指定することも、すべてのテナントを指定することもできます。すなわち、tenant=allです。
ALTER SYSTEM SET _max_elr_dependent_trx_count = 1000;
ALTER SYSTEM SET enable_early_lock_release=true tenant= test;
ステップ5:OceanBaseデータベースのELRを有効にしてテストを実行する
ホットスポット行機能を有効にした後、再度テストを実行します。実行前に、テーブルsbtetのid=1のレコードを確認します。kフィールドの値は100000です。これは、先ほどデフォルト設定で100000回の更新が実行されたためです。
SELECT * FROM sbtest1 WHERE id=1;
+----+--------+----+-----+
| id | k | c | pad |
+----+--------+----+-----+
| 1 | 100000 | aa | aa |
+----+--------+----+-----+
1 row in set
次に、テストマシンで再びob_elr.pyを実行します。引き続き50の並列数で、合計100000回の更新を行います。
./ob_elr.py
実行完了後、テストスクリプトは実行時間とTPSを出力します:
[root@obce00 ~]# ./ob_elr.py
Parallel Degree: 50
Total Updates: 100000
Elapse Time: 12.16 s
TPS on Hot Row: 8223.68 /s
テスト結果は以下のとおりです:
OceanBaseデータベースでELRの早期ロック解放機能を有効にすると、単一行更新のTPSは8223.68/sに達し、デフォルト設定と比較して約4.5倍向上しました。
同時に、テーブルsbtet1のk値が200000であることが確認できます。これは、今回も100000回更新されたことを意味します。
SELECT * FROM sbtest1 WHERE id=1;
+----+--------+----+-----+
| id | k | c | pad |
+----+--------+----+-----+
| 1 | 200000 | aa | aa |
+----+--------+----+-----+
1 row in set
この例では、単一行の並列更新の場面のみを紹介しましたが、OceanBaseデータベースのELR機能は、複数ステートメントのトランザクションの並列更新もサポートしています。ステートメント数やシナリオの違いに応じて、同様に顕著な性能向上が得られます。
さらに、OceanBaseデータベースのELRは、複数地域に展開されネットワーク遅延が高いシナリオにも適用できます。例えば、単一のトランザクションがデフォルトのシナリオで30msかかる場合、並列処理でELRを有効にすると、TPSスループットが約100倍向上することがあります。
OceanBaseデータベースのログプロトコルはMulti-Paxosを基盤として構築されており、2PCコミットプロセスが最適化されているため、ELRを有効にした後でも、ノードのダウンや再起動、リーダー切り替えが発生した場合でも、トランザクションの一貫性を保証できます。これらの実験を構築して体験してみることもできます。