オンライン取引やeコマース業界の発展に伴い、業務システムにおける特定データ(「ホットスポット」)への同時アクセスによる負荷が課題となっています。短期間に特定の口座残高が大量に更新されたり、セールイベントで人気商品の在庫が瞬時に減ったりするのが、その典型的なシナリオです。このような「ホットスポット行」の更新とは、本質的には、データベースの同じ行の特定フィールド(例:残高や商品在庫など)に対して、短時間に大量の更新リクエストが集中する状況を指します。リレーショナルデータベースでは、トランザクションの整合性を保つため、行データを更新する際に「ロック取得 → 更新 → ログ書き込み・コミット → ロック解放」という手順を踏みます。この処理は実質的に直列実行となるため、性能ボトルネックが発生します。したがって、ホット行の更新性能を高める鍵は、ロックの保持時間をできるだけ短くすることにあります。
学術的には早くから「早期ロック解放 (Early Lock Release)」(ELR)方式が提案されてきましたが、ELR の異常発生時の処理シナリオが非常に複雑なこともあり、業界では成熟した本番実装例がほとんどありません。OceanBaseデータベースは、この問題に対して継続的に研究を重ね、分散アーキテクチャに基づく実装方法を開発しました。これにより、同様の業務シナリオにおける単一行への同時更新性能を向上させ、OceanBaseが提供する「スケーラブルなOLTP」の重要な基幹技術の一つとなっています。
本記事では、単一行に対して多数の更新が同時に発生するシナリオを構築し、OceanBaseデータベースのELR機能の使用方法と、その効果を比較検証します。高い同時実行負荷のもとで検証するため、より良い結果を得るには、少なくとも本記事で用いるものと同等のスペックを持つノードで評価・検証することを推奨します。なお、ELRの設計や実装原理は非常に複雑であるため、この記事では詳しく触れません。
この例では、16コア、128GB構成のノードを使用し、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を有効化してテストする
ホットスポット行機能を有効にした後、再度テストを実行します。実行前に、テーブルsbtestの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倍向上します。
同時に、テーブルsbtest1のk値が200000であることが確認できます。つまり、今回の処理で100000回更新されました。
SELECT * FROM sbtest1 WHERE id=1;
+----+--------+----+-----+
| id | k | c | pad |
+----+--------+----+-----+
| 1 | 200000 | aa | aa |
+----+--------+----+-----+
1 row in set
この例では単一行の同時更新のシナリオのみを説明しましたが、OceanBaseデータベースのELR機能は、複数のSQLステートメントで構成されるトランザクションの同時更新もサポートしています。ステートメント数とシナリオの違いによって、同様に顕著なパフォーマンス向上が期待できます。
また、OceanBaseデータベースのELRは、複数地域にデプロイされた高ネットワーク遅延のシナリオにも適用できます。例えば、単一トランザクションがデフォルトのシナリオで30msの場合、並行処理でELRを有効にすると、TPSスループットが約100倍向上します。
OceanBaseデータベースのログプロトコルは、Multi-Paxosプロトコルをベースに構築されています。さらに、2PC(2フェーズコミット)のプロセスも最適化されているため、ELRを有効にしてもその堅牢性は損なわれません。ノードのクラッシュ、再起動、リーダーの切り替えといった障害が発生した場合でも、トランザクションの一貫性は確実に保証されます。これらの障害シナリオについても、ご自身で再現・検証いただくことが可能です。