0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

世界最速のデータベース「劔"Tsurugi"」をPythonで使ってみた

0
Posted at

はじめに

皆さんは、「劔"Tsurugi"」というデータベースをご存知ですか?
純国産のインメモリデータベースで、ハードウェアの性能に応じてデータベースの性能もにスケールさせることができる将来有望なデータベースです。

詳しくは公式サイトをご覧ください。

なぜ今なのか

2023年にNEDOのプレスリリースが出てからずっと気になっていたのですが、アプリケーションと連携させるためのSDKがJavaやCしかなく、Pythonしかさわれない貧弱エンジニアの私にとってなかなか手が出せませんでした。

しかしついに!

2026年4月6日にPythonのSDKが発表されました!
Python SDKが出るのも時間の問題だと思っていましたが、ついに使えるようになりました。
ということで、この記事を書くに至りました。

検証環境

今回はDocker等のコンテナを使わず、直接DBをインストールしました。
なお全ての計測は、1回のウォームアップののち、10回の計測を行いその平均値で評価を行いました。

以下の設定が素人考えであり、厳密な検証ができる状態になっているかは怪しいので、有識者の方はぜひご意見いただけますと幸いです

ハードウェア

  • Ubuntu 22.04.2 LTS
  • Intel(R) Xeon(R) Silver 4316 CPU @ 2.30GHz
  • DDR4-3200(Registered / Buffered ECC) 32GB x 16 = 512GB

PostgreSQLの設定

  • version: PostgreSQL 14.22

デフォルトだと、永続性を担保しておらず、その分高速で動いてしまうため有利となり、メモリ使用量については不利になるので以下を変更します

shared_buffers = 16GB
fsync = on
synchronous_commit = on

Tsurugiの設定

  • version: 1.10.0

同じく永続性の部分を明記します(デフォルトでSTOREDのようなのでしなくても良い)

[sql]
    commit_response=STORED

事前準備

テーブルの作成

今回はIoTのセンサーデータが来る想定で作成しました。

両者ともSQL文でテーブル作成が可能です。
TimezoneについてはTsurugi側がTIMESTAMPTZに対応していないため、TIMESTAMP型としています。

DROP TABLE IF EXISTS sensor_data;

CREATE TABLE IF NOT EXISTS sensor_data (
    id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    temperature DOUBLE PRECISION NOT NULL,
    humidity DOUBLE PRECISION NOT NULL,
    pressure DOUBLE PRECISION NOT NULL,
    created_at TIMESTAMP NOT NULL
);

単一接続でバッチINSERT

まずはシンプルに並列化をせず、多くのデータをバッチでINSERTするケースを検証しました。
ソースコードは本記事最後のGitHubにおいています。

今回は実用性を重視するために、PostgreSQL側は実際の運用で使用するような最適な実装(asyncpg)を用いています。Tsurugi側はasyncではないので正直相当不利ですが、実務で利用する上ではasyncpgはデフォルトのようなものなので、我慢してもらいましょう...

  • 1コミットあたりのデータ数 = 100
  • コミット数 = 100

PostgreSQL

average      total=2.157006s  ops/sec=4,636.06  avg=0.2157ms
min          total=2.076781s  ops/sec=4,815.14  avg=0.2077ms
max          total=2.327186s  ops/sec=4,297.04  avg=0.2327ms

Tsurugi

average      total=1.517899s  ops/sec=6,588.05  avg=0.1518ms
min          total=1.343995s  ops/sec=7,440.51  avg=0.1344ms
max          total=1.680932s  ops/sec=5,949.08  avg=0.1681ms
実装 average total average ops/sec sec/op
PostgreSQL 2.16s 4,636.06 0.22ms
Tsurugi 1.52s 6,588.05 0.15ms

ぬおー早い!
同期/非同期でだいぶ不利ですが、それでもよく頑張ってくれました。

並列接続でバッチINSERT

準公式のTsurugi vs PostgreSQLのベンチマークでは、大量かつ複雑なトランザクションで比較を行なっており、Tsurugiの強みはそこにあります。

DBにおける"大量"は、複数のトランザクションが同時並行的に生じるという意味に近いです。
そこで並列化を行い、複数のコネクションからの多くのデータをバッチでINSERTするケースを検証しました。
同じくソースコードは本記事最後のGitHubにおいています。

  • 1コミットあたりのデータ数 = 100
  • コミット数 = 100
  • 並列数(ThreadPoolExecutor) = 32

PostgreSQL

average      total=19.982135s  ops/sec=16,014.30  avg=0.0624ms
min          total=19.171534s  ops/sec=16,691.41  avg=0.0599ms
max          total=22.303964s  ops/sec=14,347.23  avg=0.0697ms

Tsurugi

average      total=96.673199s  ops/sec=3,310.12  avg=0.3021ms
min          total=74.815632s  ops/sec=4,277.18  avg=0.2338ms
max          total=114.424572s  ops/sec=2,796.60  avg=0.3576ms
実装 average total average ops/sec sec/op
PostgreSQL 19.98s 16,014.30 0.06ms
Tsurugi 96.67s 3,310.12 0.30ms

残念なことに、並列数を上げると能力が上がるはずのTsurugiが大きく遅くなってしまいました。
おそらくですが、ここで同期/非同期の差が出てきてしまったのではないかと思われます。
そもそも同期型のTsurugi SDKをThread Pool Executorで無理やり回しているので、その部分で相当無理が出てしまったのではないだろうかと思われます。
計測時間も大きく揺らいでいることからも、Python自体の能力が相当足を引っ張ってしまっており、うまく性能が引き出せませんでした。

FastAPIで呼び出す

発想を変えて、そもそもDBはREST APIの内部で呼び出すよね、ってことで先ほどの並列をFastAPIを介することで実現してみました。
つまり1トランザクションを1REST APIに入れ、そのAPIエンドポイントを並列に叩くということです。

しかし結局同期であることは変わらないので、FastAPIの強みであるasync defはTsurugiでは使えません。
実際にFastAPIで実装し、k6でベンチマークを取ってみました。

  • 1コミットあたりのデータ数 = 100
  • コミット数 = 100

まずは実装確認でVUs(=worker)を1にして検証。Tsurugiのほうが若干早いはず。

VUs = 1

PostgreSQL

http_req_duration..............: avg=1.89s min=1.6s med=1.89s max=2.28s p(90)=2.16s p(95)=2.22s
  { expected_response:true }...: avg=1.89s min=1.6s med=1.89s max=2.28s p(90)=2.16s p(95)=2.22s
http_req_failed................: 0.00%  0 out of 16
http_reqs......................: 16     0.527219/s

Tsurugi

http_req_duration..............: avg=1.7s min=1.43s med=1.57s max=2.87s p(90)=1.93s p(95)=2.16s
  { expected_response:true }...: avg=1.7s min=1.43s med=1.57s max=2.87s p(90)=1.93s p(95)=2.16s
http_req_failed................: 0.00%  0 out of 18
http_reqs......................: 18     0.58755/s

うまくいってそうですね。では並列化します。

VUs = 32

PostgreSQL

http_req_duration..............: avg=2.6s min=1.52s med=2.87s max=4.16s p(90)=3.44s p(95)=3.59s
  { expected_response:true }...: avg=2.6s min=1.52s med=2.87s max=4.16s p(90)=3.44s p(95)=3.59s
http_req_failed................: 0.00%  0 out of 3699
http_reqs......................: 3699   12.219043/s

ここまで並列しても大きく遅くならないのはさすがですね...

Tsurugi

http_req_duration..............: avg=3.77s min=2s med=3.09s max=13.75s p(90)=4.48s p(95)=7.89s
  { expected_response:true }...: avg=3.77s min=2s med=3.09s max=13.75s p(90)=4.48s p(95)=7.89s
http_req_failed................: 0.00%  0 out of 2556
http_reqs......................: 2556   8.401758/s

やはりPostgreSQLに軍配が上がってしまいました。
先ほどのThreads型よりは大きく速度が上がっていますが、やはり同期/非同期がボトルネックになってしまっているんだと思います...

もともとJavaやCでの実装が想定されているでしょうから、有識者の方はぜひそちらで検証してください...
もしかするとこの記事を見て非同期APIが開発されると最高ですね!

おまけ

このまま終わるわけにはいかないので、準公式ベンチマークのこれを同じトランザクションにしてPythonで実装してみました。

当記事ではJavaを使用し、RTX/OCC/LTX等をうまく使ってTsurugiの最適化を行なっていますが、Pythonではおそらく難しいので、単純なトランザクションとして比較しています。
ソースコードも記事一番下のGitHubから。

  • ONLINE_WORKERS = 32
  • RUN_SECONDS = 300
  • ONLINE_SLEEP_SEC = 0.1
  • BATCH_INTERVAL_SEC = 10
  • MAX_RETRY = 3
  • CARD_ID_MIN = 1
  • CARD_ID_MAX = 100_000

PostgreSQL

elapsed=301.939s
{'online_success': 5429, 'online_fail': 0, 'batch_success': 0, 'batch_fail': 28}
online_tps=17.98 batch_success=0

Tsurugi

elapsed=315.067s
{'online_success': 18555, 'online_fail': 0, 'batch_success': 10, 'batch_fail': 14}
online_tps=58.89 batch_success=10

素晴らしいですね!
やはり複雑なトランザクションではPythonでもTsurugiに軍配が上がるとわかりました!

終わりに

PythonはCなどに比べて遅い言語と言われます(私は思っていません)が、それでも遅いなりに裏側でCやRustを使ってなんとか高速化しています。
今回のTsurugi Python APIも裏はRustです(Tsurugi-Tubakuro)

正直なところ、PythonでTsurugiのパフォーマンスが引き出せるのかとても不安でした。
今回のベンチマークも、結果的にはPythonの能力が足を引っ張っている結果となっていました。
しかし、そもそもWeb APIを作る上では不利なPythonですが、昨今の生成AIブームに乗っかって、REST APIをPythonで書くケースも増えてきました。

複雑でかつ多並列トランザクションにおいては、そんなPythonでもPostgreSQLに勝つことができるわけです。
全てにおいて最強ではないにせよ、純国産DBとしてTsurugiがDBスタンダードに肩を並べることを願っています。

メニーコア・メニーメモリは正義!

検証用ソースコード

本記事は生成AIを使用していませんが、検証用のソースコードはChatGPTに手伝ってもらいました🤖

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?