アプリケーション開発における重要な考え方として、「データ指向設計(Data Oriented Design)」という概念があります。

「データ指向設計」は2009年ごろから有名になった言葉で、その名のとおりデータに注目して設計するという概念です。具体的には、データのメモリへの配置の仕方、データの読み込み方や書き出し方などに着目して設計を実施します。

本記事では、Martin Kleppmann著の『データ指向アプリケーションデザイン』の内容を参考に、データ指向アプリケーションの設計の基本概念や具体的な設計の仕方などを紹介します。

データ指向アプリケーションデザインとは

「データ指向」とは、書籍『データ指向アプリケーションデザイン』のために作り出された新しい訳語です。

そしてデータ指向アプリケーションデザインとは、「データの量」「複雑さ」「変化の速度」を中心に考えてシステム設計することを指します。

現在の分散システムのデザインにおいてデータの扱いは非常に重要です。実際にデータ指向設計を実施すると、より高速なデータ処理が可能になります。

本書には、高速なデータ処理を実現するために必要となるさまざまな基礎知識が記載されています。またそれぞれの基礎知識やデータ関連のキーワードは丁寧に説明されているので、分散データシステム設計の百科事典といえるでしょう。

一方、システムの課題や使用するアプリケーションに適した選択肢の発見を手助けするツールとしても活躍します。

【分散データベース】

  • レプリケーション
  • パーティション
  • トランザクション

【データセットの取り出しや結合】

  • バッチ処理
  • ストリーム処理

上記のような内容をわかりやすく解説されています。

データを作るところから始めよう

データがないとアプリケーションの作成は始められません。以下の2つのパターンをもとに、データをどのようにして作るかを考えます。

  • データ量が少ない場合
  • データ量が多い場合

データ量が少ない場合

データ量が少ない場合、データの「表現のしやすさ」や「生成しやすさ」が重視されます。

一般にデータ量が少ない場合のデータフォーマットは、

  • テキストデータフォーマット
  • バイナリーデータフォーマット

の2種類です。

テキストデータフォーマットは、CSVやJSON、XMLなどのテキストエディタでも手軽に記述できるため、データの作成でよく使われます。

しかしテキストである性質上、正確なデータ型をコンパクトに表現したい場合には不向きでしょう。

そこで使われるのが、MessagePackやThrift、ProtocolBuffers、Apache Avroなどのバイナリーデータフォーマットです。データとして完結している自己記述型(self-describing)のフォーマットが特徴です。

例外として、Protocol Buffersはデータ自体にスキーマを含まないため、節約指向であるといえます。

データ量が多い場合

データ量が多くなる場合、データ基盤では

  • 省メモリ
  • 圧縮しやすさ
  • ストレージへの格納しやすさ

が重視されます。

たとえば、テーブル型のデータの場合には、スキーマ(各カラム名、型)とレコード表現を分離し、データを節約して表現しています。

一般にデータ量が多い場合のデータフォーマットは、以下の2種類です。

  • 行指向フォーマット
  • 列指向フォーマット

DBMSでよく使われる「行指向フォーマット」は、行ごとにデータを分解して保存しているため、レコード単位で更新しやすい特性があります。

一方、「列指向フォーマット」とは、列ごとにデータを分解して保存するフォーマットです。

最近ではこちらが主流になってきています。なぜなら縦方向に分解することで、同じ型のデータを集めておくとgzipなどが非常にコンパクトに圧縮できるからです。また、同じ型のデータがキャッシュに乗りやすく、SIMD演算にも活用しやすいでしょう。

分析クエリに特化したフォーマットといえます。

一方で、ひとつのフォーマットを複数のブロックで表現するため、更新するのが大変です。欠点を理解したうえで、どちらのフォーマットを選択するか決めましょう。

データが変化するスピード

列指向データフォーマットのような変化の少ないデータの場合、ログデータを列にして保存し、ストレージをコンパクトにしたり分析クエリの実行速度を速くしたりします。

分析クエリの速度を上げたい場合、データを分散する常套手段があるでしょう。そのほかにもデータを分散させる方法はいくつか挙げられます。

たとえばパーティショニング(別名:シャーディング)と呼ばれる方法があります。

パーティショニングは、データを時間範囲やキーの範囲で分割して別の領域に保存することです。ユーザーがターゲットにしている部分だけにアクセスしたり、別の領域への並列アクセスを可能にしたりします。

また、パーティショニングのほかにレプリケーションと呼ばれる方法もあります。レプリケーションは、データのコピーを複数のマシンに配備し、複数台のノードで別々の箇所のディスクを読めるようにして高速化する方法です。

ディスク上のインデックス構造:B-Tree

データが頻繁に更新される場合に、ディスク上の更新対象のデータを高速で見つけられるよう、インデックス構造というデータ構造が存在します。

インデックス構造のなかでもB-Treeは、ディスク上のページに次のページへのルックアップテーブルを格納する構造です。

RDBMSでは、CREATE INDEX命令(My SQLでインデックスを作成する命令文)を実行すると、ひとつのB-Treeが作成されます。検索を速くする目的で何度もCREATE INDEX命令を実行すると、その度に複数のB-Treeインデックスを更新する必要があるため、注意しましょう。

B-Treeでは、空き領域に次々とデータを挿入できます。ページ数が増えても木の高さがあまり高くならない性能を生かし、ディスクへのランダムアクセス数を減らすことにも役立ちます。

たとえば各ノードに100個のエントリーが含まれている場合、B-Treeインデックスを利用すると、1億個のデータを投入したとしても、木は4個の高さにしかなりません。したがって、1億件のエントリーから対象のデータを見つけるには、4回ディスクをルックアップすればよいことになります。

非常に優秀なデータ構造といえるでしょう。

Log-structured Merge(LSM)Tree

B-Treeよりもさらに強く書き込みできるデータ構造として、Log-structured Marge(LSM)Treeがあります。

LSM Treeではレコードを追加する操作と、裏側でレコードを整形してマージする操作を分離しているのが特徴です。

まずはメモリにデータを投入しメモリ上でデータを並び替えます。バックグラウンドでは、ソート済みのデータをマージソートし、次のレイヤーに保存します。

これによりランダムアクセスをメモリ上で吸収できるでしょう。バックグラウンドのストレージにデータを書き出す際には、シーケンシャルアクセスで書き出すことが可能です。

24時間365日動き続けるサービスの設計

アプリケーションを1年同じ状態のまま動かし続けるのは不可能であり、適宜更新が必要です。

アプリケーションをデプロイするときは、マシンのプログラムを一度止めて次のバージョンのアップグレードをしなければいけないため、リクエストが一度止まります。

その状況では、クラッシュしたり通信が遮断されたりするケースがあります。リスクを防止するためにも、クライアント側ではリトライ処理を実装するようにしましょう。

難しいのは、クライアント側でレスポンスを送り、サーバーでは正しく処理されたものの、ネットワーク障害により、レスポンスがクライアントに正常に到達しない場合です。 クライアントがリトライすると、サーバー上には同じデータが2個登録されてしまいます。

このような問題が起こらないようなシステム設計を「冪等性を保った設計」といいます。リクエストにユニークなID(UUID)をつけて対処するケースがあります。

ComputeとStorageの分離

近年では常に配置し続けられるように、Compute(クエリの実行)とStorageを分離する設計が標準的です。

クエリエンジンとストレージを別々にスケールしたり、ローリングアップグレードしたりなどもできます。

たとえば、SnowflakeやAmazon Redshiftは、ComputeとStorageを完全に分離したデザインです。

Redshiftは、Redshiftネイティブのストレージだけでなく、S3にあるデータもクエリできるようデザインされています。ComputeをGPUにして超並列クエリとして実行することも可能です。

トランザクション処理

トランザクション処理は、「データベースの一貫性を保つために、不可分な操作や関連する処理をひとつの処理単位にまとめて管理する」仕組みを指します。

データを頻繁に更新するときに必要となる技術で、データベースの花形技術といえるでしょう。

たとえば、分離のもっとも重要な考え方として、「直列化可能性(トランザクションがひとつずつ実行されたのと変わらない状態を維持すること)」という性質があります。

この性質を持たせるのがトランザクションの基本です。

データの複雑さ:「テーブル」の意味の変化

従来ではデータベースのテーブルは、「RDMBSにある最新のデータ(snapshot)」のことしか意味していませんでした。

しかし現代では、

  • 時間とともに変化するデータ
  • そこから派生するデータ(導出データ、derived data)

をあらわすようになっています。

導出データはひとつのデータから数千もの数が生成されるため、クエリで生成されるデータ同士の依存関係やデータの履歴の管理が必要です。

そのため最近だと、dbtや新しいテーブルフォーマット(Delta LakeやApache Iceberg、 Apache Hudi など)が、テーブルの更新履歴やsnapshotを管理するために開発されました。

バッチ処理・分散バッチ処理

バッチ処理は、「一定量のデータを集め、一括処理するための処理方法」を指します。Unixコマンドのデータ処理に非常に近いでしょう。

UNIX哲学では、簡易な機能を持つさまざまなコマンドをパイプでつないで、データ処理します。 実はこのデータ処理の裏側ではコマンドごとにプロセスが起動し、プロセス間でストリーム処理をしています。

そして、バッチ処理を複数ノードで実行するために生まれたのが分散バッチ処理です。

ストリーム処理とは

ストリーム処理は、SQLと似たものをデータの入力側に送り、常にクエリの実行を続けます。あるいはデータの変更を捉えて、細かい単位のバッチで処理したり(マイクロバッチ)、差分データ(CDC;Change Data Capture)を使って、変更部分に対して処理したりします。

また、遅れてやってくるデータ(late arrival)の処理を埋め合わせる必要があります。

基本的には、データがとられた時間と、データがシステムに見えた時間を2次元のwatermark(ドキュメント ファイルや画像ファイルのコンテンツに重ね合わせるロゴやテキスト)で、管理して処理する仕組みです。

データモデルの多様性とその歴史

昔はネットワークモデル(CODASYL)や階層モデルなどがありましたが、最終的にはリレーショナルモデル(RDBMS)が勝ち残りました。さらにJSON・XMLなどさまざまなデータ形式がRDBMSに取り込まれています。RDBMSの一人勝ちの状態です。 

結局のところ、データベースでもっとも重視されるのは品質です。ユーザーはトランザクションができないデータベースを使うのは困難です。しっかりとトランザクションをサポートしているRDBMSが強くなったのには、品質が関係しています。

またインピーダンスミスマッチ(アプリケーションが使いたい表現と、データベースに格納されている形態がマッチしない状況)の歴史から学んだ良い事例として、GraphQLがあります。

GraphQLはRDBMSやSQLと干渉しないアプローチをとっています。

RDBMSやSQLを利用しつつ、アプリケーションの求める形でデータを取得するサポートを実装しています。

インターフェースとしてのSQLの価値

SQLの使われ方も年々変わってきています。

今まで、SQLはRDBMS専用のインターフェースでした。

ところが現代では、さまざまなデータベースで操作できるようになっています。たとえばGoogle F1(VLDB2018)は、SQLでGoogle社内の多様なストレージへアクセスできます。

そこで使われる共通SQLコンパイラは、オープンソースのZetaSQLで作られており、BigQueryの実装にも使われています。

また、Meta(Facebook)も共通SQLエンジンを作ることにより、その裏側でSparkやPrestoのデータ処理を統合するVelox処理エンジンを実装しています。

Trino Distributed SQL Engineは、ストレージ実装をなにも持たず、データソースへのコネクタを提供して分散SQL(処理部分)だけをオープンソース化しています。

具体例:ツイート配信の実装(Fanout Service)

初期のTwitterは、ツイートをグローバルなデータベースに格納し、ユーザーがタイムラインを取得するたびに、データベースから検索するという非常に読み込み負荷の高い実装でした。

そこで、ツイート時にフォロワーのタイムラインキャッシュに対してツイートを送る方式に変更した結果、「書き込みの負荷は増えたが読み込み負荷が2桁減った」という事例があります。

またファンアウトサービスがクラッシュしていて、イーロン・マスク氏の95%のツイートが配信されていなかったという事例もあります。

この事例において、

  • 信頼性の面:一部サービスがクラッシュしていても、サービスは落ちていないのはなぜか
  • メンテナンス性の面:数千人規模のエンジニアを解雇しても、引き継ぎがちゃんとできているのか
  • スケーラビリティの面:数億回の読み書きする実装はどうなっているのか、性能調整はどうなっているのか

といった考察ができます。

SLO:システムのパフォーマンスをどう測るのか?

性能を測るときに大事なのがSLOの概念です。

システムの平均値を見るだけでは重要な部分が見えないため、95、99パーセンタイルの値の遅延を見る必要があります。AmazonのSLOでは p99.9のレスポンスタイムを一定値以下に抑えるようにしています。

100ms遅くなると、売上が1%下がると言われており、Amazonのように売上が莫大な場合、1%でも640億円と大きな額となってしまいます。

しかし、p99.99 まで改善しようとするとコストに見合わないと判断し、p99.9としているようです。

データ指向アプリケーションデザインの理解を深める方法

本書の理解を深めるための方法をまとめました。

  1. 全体像を理解しつつ一つひとつの分野を深く理解する
  2. 実際にデータベースを作ってみる
  3. 普段使っていないデータベース・データエンジンに触れる

全体像を理解しつつ一つひとつの分野を深く理解する

まずは本書で全体像を理解することが大切です。

全体像がわからないと、自分にみえている範囲の外に自分の知らない未知の世界がどのくらいあるのかわかりません。データ指向設計を進めていくことに、不安を感じてしまう方もいるでしょう。

また各々のフェーズのサイズが感覚的にわからないと、高いハードルに感じてしまうというケースは多いはずです。

一方、データベースを作ろうと思うとわからない要素が多くあるため、そのうちのひとつだけを作ってみるのがいいのではないでしょうか。CSVをクエリできるSQLエンジンのように、簡単なところから一つひとつ進んでいくのがオススメのアプローチです。

実際にデータベースを作ってみる

コードを書くのが好きな人やデータベースが好きな人は、実際に手を動かして簡単なデータベースから作ってみると良いでしょう。

最近では、DuckDBのようにあえて分散させないことで、簡単に作れるデータベースが出てきています。

実際に作ってみようとすると、B-Treeやカラムナ・ストレージなどを実装するうえで、本書のほとんどの知識が必要になるので、手を動かしながら効率的に学ぶことができます。

普段使っていないデータベース・データエンジンに触れる

普段使っていないデータベースやデータエンジンに触れ、それぞれの性質を比較することも、効果的に本書の理解を深めるのに役立つでしょう。

たとえばRDSMS一つとっても、MySQLを中心に使っている人は、別のデータベース(PostgreSQL)やBig QueryやAthenaのような分析系エンジンを使ってみると、

  • 何が違うのか
  • 何を大事にしているのか
  • 性能的な特性がどう違うのか

などポイントがよくわかってくるでしょう。

実際、実際の使い勝手や性能特性などの大事なものは、細部に宿っていることが多いと感じます。

それらの特性の違いを把握したうえで、どのような技術によって裏から支えられているのかを知ると、理解が一段と深まるでしょう。

データ指向アプリケーションデザインの技術は現場でどのように活かせるのか

データ基盤を使う観点からすると、データのパイプラインをいかに動かし続けるかという観点が大事です。

ほかにも、データを1回だけ書くexact onceという観点や、そのためにデータをクリンナップしてから冪等に書く観点も大事になりつつあります。

また、データをつくるという要素技術だったスナップショットやCDC(変更データキャプチャ)が、データベースの中だけの話ではなく、データ分析基盤を作る時の要素技術として必要になるケースがよくあります。

そういう意味では、本書の内容すべてを拾っていこうとしなくても、それぞれの要素技術が必要になるでしょう。一度立ち止まって本書の内容を振り返り、頭の中でより明確に整理できれば、現実の仕事に応用できるでしょう。

まとめ

『データ指向アプリケーションデザイン』の内容、また本書の応用方法を紹介しました。

現在、分散データシステムの設計をするうえで、データをどのように扱うかは非常に重要な要素です。アプリケーション開発を考えている人はぜひ一度読んでみてはいかがでしょうか。

また、弊社が提供しているデータ分析基盤構築サービスtrocco®は、データのETLをメイン機能とし、そのほかにデータマネジメントをサポートする機能や、エラーデータをチェックするデータチェック機能なども備えています。

非エンジニアにも使いやすいETL機能だけでなく、エンジニアチームによるデータマネジメントもトータルでサポートできます。

別途ツールを導入することなく、大規模なデータ基盤でも高いレベルのデータマネジメントを実現できるでしょう。

trocco®では、クレジットカード不要のフリープランをご案内しています。データ統合やデータマネジメントにご興味がある方はぜひこの機会に一度お試しください。

trocco® ライター

trocco®ブログの記事ライター データマネジメント関連、trocco®の活用記事などを広めていきます!