見出し画像

Datomic Cloud を半年運用した感想

こんにちは、株式会社トレタで Clojure を書いている鄧(でん)です。
2021 年 7 月より新規事業をして飲食店向けのセルフオーダーシステムを開発しており、そのバックエンドに Datomic CloudClojure を採用しました。

採用理由は幾つかありますが、主に履歴、監査ログ、変更通知周りの機能要件がマッチしていると考えており、現状 Datomic と親和性が最も高い Clojure も共に採用しました。

良かったところ、気に入ったところ

直感的なデータモデリングができる
データモデリングをする際には意外とデータベース自体の制限や思想に影響されることがあると思います。例えば MySQL や PostgreSQL などのリレーショナルデータベース(RDB)に慣れている人であればリレーショナルな情報を表現するときに 1:1、1:n、n:1、n:n の関係性をテーブル構造として表現しますが、Neo4j などの Graph DB を使いうときのマインドセットは全く異なるもので、Node と Edge で関係性を表現しますが、その中で Datomic の使い勝手は RDB と Graph DB の間(ちょっとだけ RDB 寄り)だと感じております。

特徴としては EAV を使って関連性を表現するので、項目を作る際に柔軟に :db.cardinality/one あるいは :db.cardinality/many を選択することができます。後から必要に応じて 1:1 から 1:n の関係性に変更することも可能で、RDB みたいに大掛かりなマイグレーションプロセスは不要です。

ただし :db.cardinality/many から :db.cardinality/one に変更するときはトレードオフがあるので要注意です

履歴テーブルを作らなくてもいい
今まで RDB で業務システムを作った際には変更履歴が必要かどうか、物理削除にするべきか論理削除にするべきなのかなど、常にユースケースを熟慮する必要がありますが、常に一発で当てることが現実的ではなく、後から後悔することもあるでしょう。例えば僕がトレタに入社した直後など、カスタマーサクセス(CS)のオペレーションをデータサイエンティストとしてサポートしていたこともあり、変更履歴がない理由で調査ができなかったり、分析が出来なかったことが多くありました。

Datomic Cloud を使い始めてからは良くも悪くも物理削除がなく、過去の変更履歴を取得する必要があったら専用の API があり、過去の情報に対してクエリーが書けるのはある種の特殊能力と感じるほど強力です。最新の値を上書きすると勝手に変更履歴が残るのでデータベースを設計する際に履歴テーブルとの整合性を考えなくてもいいのも非常に楽です。

ただし、最新の状態と変更履歴に対して同時に検索する場合はちょっとやり方を考えないといけないです。あと、EU に対してサービスを展開する場合は GDPR 対応などを考慮して設計しないといけないです。

変更通知を作るのが簡単
飲食店向けのシステムですので、オーダーが確定したら(DB 内のトランザクション)キッチンプリンター(独立した外部システム)から必ず伝票を出してほしいなど、整合性や到達保証が必要なユースケースがあります。

飲食店のドメイン知識ですが、ホールとキッチンのやりとりは基本伝票で行われるので、伝票が印刷されないとキッチンはオーダーがされたことに気付かない場合があります。ネットワークの性能はお店のインフラやその日の状況によって安定性が大きく変わるので。ネットワークパケットが遅延することや帰り道のパケットがロストすることも考慮しなければなりません。

オーダーしたものが届かないとクレーム化する場合があるので、キッチンプリンターの伝票とはいえかなり厳密な整合性と到達保証が求められます。

RDB で厳密にやろうとすると CDC と kafka など、exactly-once-delivery 向けのシステムを組み合わせないといけないのですが、Datomic Cloud の場合はそもそも DB 内で履歴を永続化してくれているのでその上で到達保証を担保できるようなプロトコルを構築すると比較的に小規模な開発で事足ります。

監査ログ
トレタではセルフオーダーも兼ねて最後にご自身が頼んだ分や同伴者の分もまとめてお会計することが可能になっておりますが、如何せんお金に関わる部分ですので慎重に設計する必要があり、誰がどのシステムを使って何をオーダーしたのか、誰がどのシステムを使って幾ら返金したのかなどなど、トランザクションに対しての情報を永続化する必要があります。

一般的には DB と併用して別途監査ログを出力することが多いと思いますが、監査ログと DB の整合性や横断した検索基盤を作る場合はまた大掛かりなシステム構築が必要でしょう。

Datomic の設計思想として DB に対してのトランザクション自体が一つのレコードとして保存されるので、トレタではトランザクションメタデータの中に監査ログ用の情報を合わせて格納しております。しかもトランザクションメタデータはトランザクション内の差分データ(datum)とリレーショナルに紐づいているので、つまり、いつ誰がどのシステムを使って、何を変更したのか、変更前の状態、変更後の状態など、監査ログとして必要な情報が DB によって整合性と検索が担保された状態で永続化されており、必要に応じてリレーショナルに検索することができます。

幸いこのプロジェクトでは今まで監査ログを使わないと解決できない課題は発生しておりませんが、定期的に社内外の監査を受ける際にはものすごく助っております。

これから改善してほしいところ

以下の情報は Datomic Cloud バージョン 781-9041 に基づいたものです。その後のリリースによって改善されているものもありますのご注意下さい。

ソート機能が無い
管理画面などの UI でよく見かける表現として、ユーザーがまず日付を選択してその日のレコードを登録順に表示するもがあります。

SELECT id, ...
FROM table
WHERE ...
ORDER BY created_at
LIMIT 100;

RDB ではよくこのようななクエリーを発行して先着順で取り出すことがありますが、Datomic にはその ORDER BY 機能が現状提供されて無いです。一応 raw-index-access という機能があり、Index のソート順でイテレーションできますが、この場合他の絞り込み条件は自動的に適応されないのでアプリケーションロジック内で頑張る必要があり、Index の組み方によってはパフォーマンスが著しく劣化する場合があります。

現状公式による解決案はまだないのですが、システム的にうまく Composite Tuples で独自に index 化することによってある程度回避することができます。

Query Optimizer がない
Datomic には基本 Query Optimizer という概念が存在せず、絞り込み検索は where 区の順番のまま実行されます。手書きでクエリーを作っている場合はまだ最適化することができますが、GraphQL など外部の検索条件によって動的にクエリーを生成する場合は、ヒューリスティックなど黒魔術的な方法でクエリーを作る必要があり、場合によっては最適なパフォーマンスが出せない場合があります。

Ions には若干コールドスタートがある
781-9041 時点では一部コールドスタートが確認されており、Autoscaling などによる起動時に最初のリクエストが遅くなる、あるいはタイムアウトする問題が発生しております。

公式に報告したところ、最新のバージョンでは解決済みらしいですが、現時点のトレタではまだ検証中です(2021-12-12 現在)。

We think that the timeout errors that are manifesting during scaling are because the NLB is trying to talk to a node before the networking infrastructure is in place for new nodes. Also, when scaling down the NLB is still pointed at nodes which are no longer healthy, but the networking infrastructure still exists. ALBs do not rely on the NLB low level routing and we suspect that this higher layer will result in fewer timeout
errors overall.
(… skipped for brevity …)
However, specifically in this case, we suspect that our changes to API Gateway present in 936-9118 will have an impact on the errors you are seeing. In this version we moved from using an NLB to using an ALB.

Datomic Support

Analytics が枯れてない
Datomic に対してのクエリーは Datalog というちょっと特殊な DSL を使いmすが、分析においては SQL が公用語になっております。Datomic の Analytics 機能を使うと read-only とはいえ SQL が使えるようになるので、一般的な分析ツールを使って DB 内のデータをリアルタイムでみることはとても魅力的です。実際トレタのプロダクトでもとても重宝しております。

内部的には Trino(元 Presto)を利用しているとのことですが、初期の頃は自ら Trino のクラスターを管理する必要がなく、Datomic の Bastion インスタンス内でホスティングされたものの、最新版ではアーキテクチャーが大きく変わり Bastion 自体不要になりました。これによって Trino のクラスター管理はユーザーに放り出されたのですが、突然 Trino のクラスターを管理する必要が出てきたことによってオペレーション負荷が若干増えるかもしれません。

これから改善したいところ

多少改善要望はあるものの、全体的には十分満足しており、ユースケースが合うアプリケーションであれば十分楽しい開発体験とパフォーマンスを発揮してくれると考えております。

一つ若干気になっている事としてはやはりコミュニティーの人口が限られているのでライブラリーと公開されている情報量が限られております。公式のコミュニティーも英語メインだったりしますので、個人的には国内のコミュニティーがもっと増えてきたら楽しくなると考えております。

こちらによく使うリンクや資料をまとめましたのでよかったらご参考ください。できれば今後も少しつつ情報を発信していけたらと考えておりますので、よろしくお願いいたします。最後に、Datomic とはいえ得手不得手はありますので、十分検証した上でご利用頂けると嬉しいです。

サポートが必要な場合はこちら

拙作のライブラリたち

  • duct.module.datomic duct モジュール

  • duct.handler.datomic duct handler を作ってくれる便利ツール

  • protomic Datomic の非同期処理(core.async)を Superlifter などと繋げやすくするために promise(promesa)ベースに変更してくれる便利ツール

以前 Ikuru さんと対談させていただいた際の録画


この記事が気に入ったらサポートをしてみませんか?