Turso Cloudの書き込み性能ベンチマークを取った

Turso Cloudとは

TursoはSQLiteベースのエッジデータベースサービスである。現状はSQLiteのフォークであるlibSQLで動いている。SQLiteの互換性を保ちつつ、HTTP経由でのアクセスやレプリケーションなどの機能が追加されている。

なお、TursoはデータベースエンジンをRustでフルリライトしている最中で、今後アーキテクチャが変わるので、今回のベンチマークはあくまで現時点のlibSQLベースの環境に対するものである。

書き込み性能がどの程度出るのか気になったのでベンチマークを取った。

検証環境

  • ランタイム: Node.js 24 + TypeScript (tsx)
  • クライアント: @libsql/client v0.15.15
  • DBリージョン: aws-ap-northeast-1 (東京)
  • 実行元: 同リージョン内のマシン (NixOS)
  • 並行度: 10 / 50 / 100 / 500 の4段階
  • 各並行度あたりのリクエスト数: 並行度 x 10

検証シナリオ

3つのシナリオを用意した。

シナリオ1: 単純INSERT

同一テーブルに対して各ワーカーが独立した行をINSERTする。行の競合なし。

シナリオ2: 読み書き混在 (70% SELECT / 30% INSERT)

全体の70%がランダムな1行のSELECT、30%が新規行のINSERT。

シナリオ3: 競合UPDATE (同一行)

全ワーカーが同じ1行に対して UPDATE count = count + 1 を実行する。指数バックオフ付きリトライ(最大3回)あり。最終的なcount値の整合性チェックも行った。

結果

シナリオ1: 単純INSERT

並行度 リクエスト数 成功 エラー 平均(ms) p50(ms) p95(ms) p99(ms) RPS
10 100 100 0 183.43 154.60 344.88 435.19 52.56
50 500 500 0 776.53 793.78 855.49 982.32 61.43
100 1,000 1,000 0 1,499.42 1,557.80 1,623.64 1,636.17 63.17
500 5,000 5,000 0 7,655.55 7,971.56 8,401.13 8,471.13 62.14

シナリオ2: 読み書き混在 (70% SELECT / 30% INSERT)

並行度 リクエスト数 成功 エラー 平均(ms) p50(ms) p95(ms) p99(ms) RPS
10 100 100 0 46.67 8.84 189.09 247.49 187.52
50 500 500 0 206.48 157.99 461.85 491.81 214.35
100 1,000 1,000 0 477.52 437.39 752.83 795.24 192.48
500 5,000 5,000 0 2,198.74 2,284.70 2,705.18 2,842.33 214.17

シナリオ3: 競合UPDATE (同一行)

並行度 リクエスト数 成功 エラー 平均(ms) p50(ms) p95(ms) p99(ms) RPS
10 100 100 0 158.40 158.39 211.13 215.03 60.17
50 500 500 0 763.83 789.59 869.18 942.44 61.73
100 1,000 1,000 0 1,498.44 1,561.71 1,633.04 1,637.01 63.54
500 5,000 5,000 0 7,281.38 7,653.49 7,778.10 7,865.66 65.34

整合性チェック (シナリオ3)

並行度 成功数 最終count値 一致
10 100 100 true
50 500 500 true
100 1,000 1,000 true
500 5,000 5,000 true

データから読み取れること

書き込みRPSは約60-65で頭打ち

INSERT・競合UPDATEどちらのシナリオでも、RPSは並行度に関係なく約60-65の範囲に収まった。並行度を10から500に上げてもスループットは変わらず、レイテンシだけが増加した。

60 writes/sec = 3,600 writes/min = 約520万 writes/day。

読み書き混在だとRPSは約190-214

読み書き混在シナリオでは約190-214 RPSが出た。書き込みのみのシナリオの約3倍の値である。

全シナリオでエラーゼロ

全シナリオ・全並行度でエラーは一切発生しなかった。500並行で同一行をUPDATEし続けてもエラーは出なかった。

整合性の欠損なし

競合UPDATEの整合性チェックでは、全並行度で最終count値が成功数と完全に一致した。書き込みの欠損は発生していない。

レイテンシは並行度に比例して増加

書き込みのみのシナリオでは以下のとおり。

  • 並行度10: 平均 ~160-180ms
  • 並行度50: 平均 ~760-780ms
  • 並行度100: 平均 ~1,500ms
  • 並行度500: 平均 ~7,300-7,700ms

NixOSを最新にして、日本語入力にfcitx5を使うようにした

手順

今回はNixOS 22.11から23.05を飛ばして23.11にアップグレードする。

基本的に毎回そうだがリリースノートを読んで、/etc/nixos/configuration.nixを変更する。

通常の場合、NixOSのマニュアルに書いてあるとおり、チャンネルを新バージョンに切り替えてから、 nixos-rebuild switch --upgradeを行うが、今回はリリースノートにnixos-rebuild bootを使う旨が書いてあるのでnixos-rebuild boot --upgradeでアップグレードを行い、その後、再起動をする。(switchとbootの違いはヘルプ参照)

日本語入力

fcitx4を使っていたが、NixOS 23.05から削除されたのでfcitx5に移行する。

NixOSのwikiに書いてあるとおりに以下のように設定した。

i18n.inputMethod = {
    enabled = "fcitx5";
    fcitx5.addons = with pkgs; [
        fcitx5-mozc
        fcitx5-gtk
    ];
};

しかし、私はデスクトップマネージャーを使わずにi3を使っており、fcitx5自動起動せず、 自分で起動しないと日本語入力が有効にならない状況だったので、 services.xserver.desktopManager.runXdgAutostartIfNone = true;を追加した。

これにより、期待通り動作するようになった。

私のようにデスクトップマネージャーを使ってない人はこの設定が必要になるかと思われる。

オプションの詳細はNixOS Search - Optionsで確認ができる。

services = {
  xserver = {
    ...(その他の設定)
    desktopManager = {
      ...(その他の設定)
      runXdgAutostartIfNone = true;
    };
  };
};

PureScript packageのアップデートが自動でpackage-setsに反映されるようになってた

プロローグ

2年弱ほど前にv0.14.x対応をしてからしばらくPureScriptから離れていたが、急にやる気が出てv0.15.xを対応していた。 しばらく離れていたのでおそらくv0.15.xで動かなくなったのであろうライブラリが全てpackage-setsからいなくなっていて、 あ〜、もっかい追加しなきゃな〜めんどくさいなぁと思っていた。 (注: 現在主流のspagoというパッケージ管理兼ビルドツールでデフォで使えるライブラリにするには素手でpackage-setsで管理されてるdhallに変更を加えてPRを送る必要があった)

そんな気持ちでアップデート作業をしていたところ、なんか勝手にpackage-setsに反映されていることに気づいた。

Registry

purescript/registryというものがつくられ、そこを中央registryにしような!みたいな流れがありつつ、そのregistryによって何かが動作するみたいなことは長い間何もなかったものの、時を経て、どうやら現在は以下のように動いているようである。

  • 新しくライブラリつくった時はregistryに情報を追加してね
  • そうしといてくれれば、ドキュメントのpursuitへのもアップロードしとくし、package-setsにライブラリを追加しといてあげるよ
  • 一度registryに登録しといてくれれば、タグのリリースあったら勝手に諸々更新されるよ

purescript/registryからはライブラリは消えてなかったので、 確かになんもしてなくても勝手にpursuitへのアップロードはされてて、package-setsにも追加されていた。 非常に少しずつではあるが便利にはなってきてはいるな〜という感じではあった。

ただ、なんだかよくわからないけど、package-setsへの追加だけ失敗しているライブラリが一部あった。 追加されてないライブラリを見ると、direct dependenciesが全て入ってないライブラリ(要するに任意のライブラリに依存Aがあり、Aは依存a', b'をもつ時に、任意のライブラリがa'を直接参照していたら、依存にAが入っていれば問題なくビルドはできるけど、a'も直接実装で参照されてるからa'も依存としていれろ的なアレである。現在のspagoとかはそれをいれないとエラーになる。)が追加されてなかったので、そういう理由かと思ったが、後日見ると、direct dependenciesが全て入ってなくてもpackage-setsに追加されていたので、 結果的になにが理由だったのかは謎だった。 なんにせよ、v0.15.x対応がされているのにpackage-setsに入ってないものはありそうだなぁという感じだった。 というか実際に他人がオーナーのライブラリでもそういうのがあったので、対応した。

定期実行はpurescript/registrygithub actionを通して、purescript/registry-devのコードを実行していたので、それを読めば謎はわかるだろうが、めんどくさいから読んでいない。

package-sets

元々使われていたpurescript/package-setsは今も生きているが、purescript/registryにもpackage-setsというディレクトリができていてそこで管理がなされているので、そのうち、完全に移行がされるような雰囲気がある。(ちなみに、文中のpackage-setsに追加されてないライブラリが一部ある、みたいなトラブルが起こったのは前者の昔からある方である。)

現状のspagoはpurescript/package-setsのほうを使っている。

エピローグ

ゆっくりではあるものの、esmodule対応とか、パッケージ周りの整備が知らない間に結構進んだなという小学生並みの感想を残してフィニッシュです。

近況

2021/5から会社員に戻りました。 仕事のオファーはなんだかんだ尽きないし、フリーランスは続けられそうでは有りまして、 しばらく会社員に戻るつもりはなかったものの、かくかくしかじかで会社員に戻りました。

どんな会社かと言うと、SOELUという会社で、オンラインのヨガを提供している会社です。 この会社はフリーランスとして関わっていたのですが、そこに晴れて正社員として関わることになりました。

決め手をあげると(生々しくないやつを除いて)、

  • 週4勤務でいいよってなった
    • 一見私にのっぴきならない事情がありそうですが、残りの1日で趣味とかやりたいという話をしました。
    • こういった事由でも少なくともきちんと検討はしてもらえる柔軟さがあります。
  • 採用活動にも少し関わっていて、続けざまにいい人材がとれた
  • 分野内では弊社はイニシアチブがある

などのギョーム的な良い流れがあったことに加えて、 社内にポーカーをやる人がいて、スプリント終わりにトナメが開催される、という点も良かったです。 私はポーカーが大好きなので、ギョームには関係ないですが、QoLがあがるため、この点も非常に魅力的でした。

仕事自体もビジネス的にもシステム的にもやることがたくさんあるので暇しなそうなところも良かったです。

そんなSOELUですが、正社員のネイティブアプリのエンジニアが不足していまして、たぶんネイティブアプリのエンジニアは特に歓迎されると思います。

転職を考えている方はノリで一回弊社に話を聞きに来ても良いかもしれません。

そういうわけで、とりあえず元気にやっています。

正社員になったので、小規模企業共済をどうするかなど未だに悩んでいますが、 とりあえずはしばらくサラリーマンとしてやっていきつつ、宝くじでも買おうかなという所存です。

NixOSで各言語のパッケージをNix経由での管理にせずに開発環境を作る

NixOSでは共有ライブラリの管理方法が他のlinuxディストリビューションと違い、/nix/storeに置いてある。 よって、例えば無邪気にnpm installでいれたcliがnot found扱いとなる。 なのでベーシックなやりかたとしては、Nixを使って各言語のパッケージも管理するというのがあって、 patchELFでpatchをあててinterpreterとrpathをセットし直すという形でderivationを書いたり、あるいは、そういった形でNixのパッケージとして公開されている。

ただ、Nixで各言語のパッケージを管理したくない気持ちというのはまぁあって、普通にnpmで管理して、雑にnpm installして動く、みたいなのがいいとおもうこともあるはずだ。

今回はelectronをリポジトリローカルなモジュールとして管理したかったというシチュエーションだったので、この条件を満たすshell.nixをつくった。

{ pkgs ? import <nixpkgs> {} }:

with pkgs;

let
  sharedDeps = atomEnv.packages ++ [
    libdrm
    libxkbcommon
    mesa
  ];

in
  (buildFHSUserEnv {
    name = "electron-test-env";
    targetPkgs = pkgs: sharedDeps ++ [
      nodejs-12_x
    ];
  }).env

やっていることとしてはnodeの依存とelectronの実行に必要な共有ライブラリの依存が入った環境をbuildFHSUserEnvを使って用意している。

shell.nixを用意する場合、mkDerivationmkShellを使うと思うが、その場合は、共有ライブラリの検索のためのコード(LD_LIBRARY_PATHの設定、あるいは、rpathのパッチ)とELFのインタープリタのパッチのコードが必要になってしまう。

しかし、buildFHSUserEnvを使うと、FHSに準拠した形で環境を用意してくれるので、必要なライブラリを依存に入れるnixファイルを書くだけで済む。

こういったshell.nixをプロジェクトルートにでも置いて、nix-shellを呼び出すだけで、あとは各言語のパッケージ管理ライブラリのみでパッケージを管理できる。(このnix-shellで起動したshellの中なら、無邪気にnpm installしたcliも動く。今回の場合はelectronのcliが問題なく動く。

結果的にはほとんどDockerfileを書いているのに近い感覚で開発環境を作ることができる。

すげえ便利なので、NixOSユーザーはbuildFHSUserEnvを使ってみると良いでしょう。

なお、詳細はNixOSのマニュアルにあるので、使う前は要チェックです。

ULIDのPureScript実装作りました

背景

ULIDのPureScript実装がないのでそのうちつくろうという意思だけ持っていたものの、その後半年以上経過し、気づいたら他の人に先に作られてた。 しかし、それはnpm packageのulidに依存しており、こんなのまでnpm packageに依存するのは嫌なので、やっぱり自分でPureScriptで書くことにした。

ULIDとは

端的に言えばソートできるUUIDみたいなもんです。 ULIDには時刻を表す部分とランダムに生成される部分があるからです。 その性質上RDBで用いる時にフラグメントしないです。 詳しくはこちら

成果物

purescript-simple-ulid

このブログを書く時点ではpackage-setsにPR出している最中なので、package-setsに入ってないかもしれません。

補足

Monotonic ULIDはつくってません。

名指しでやれ

自分のアウトプットを人にチェックしてほしいとき、というのは誰にでもあるし、仕事をしていれば日常的に観測できる事象である。

私はプログラマをしているが、プログラマの場合であれば、例えば、本番のデータを修正するスクリプトを他人にチェックしてもらうとか、単純にPullRequestをレビューしてもらう等だ。

この時に、不特定多数に対してチェックを呼びかけるというケースを観測することがある。 例えば、slackでのやりとりであれば、@channel等の不特定多数にたいするメンションのみを用いたやりとり、Pull Requestであれば、複数の人間をレビュアーに設定する、等である。

こういった不特定多数にチェックを求める行為をする癖がある人がいたらすぐにやめたほうがいい。

理由は明確で、チェックしないで無視する、あるいは、適当にチェックしてレスを返すという流れを生む確率があがるし、実際みんな基本無視で、レスをしていてもめっちゃ適当、という事例を見ているからだ。

他の人が見てる、自分が返さなくていいという前提になってしまうので、無視しやすいし、無視しなかったとしても真剣さのないチェックになって適当にレスを返しやすい。

きちんとチェックをしてもらいたい意思があるなら、多数にお願いをすること自体はいいが、それだけでは絶対に駄目で、必ず生贄を一人だけ指名する必要がある。「あなたがメインのレビュアーです」「あなたがチェックしないなら先に進みません」ということが相手に伝わらないといけない。

チェックをお願いされた側は、直接指名は自分一人だけなので「ちゃんとしなきゃ」と考えやすくなる、少なくとも無視はされなくなる。

人にやってほしいチェックがあったら、必ず名指しで生贄を捧げろ。もちろん生贄はちゃんとチェックしてくれそうな人、できそうな人を選びましょう。