読んだので個人的なメモを残す。
exhaustiveなメモではない。
- 🟢 1~4章「データ指向アプリケーションデザイン」第 I 部 (データシステムの基礎) を読んだ
- 5~9章「データ指向アプリケーションデザイン」第 II 部 (分散データ) を読んだ
- 10~12章「データ指向アプリケーションデザイン」第 III 部 (導出データ) を読んだ
1章 信頼性、スケーラビリティ、メンテナンス性に優れたアプリケーション
- reliability
- = resilient = fault tolerant 耐障害性
- = コンポーネントでfaultが発生してもシステム全体は動き続ける(コンポーネントはソフトウェア、ハードウェアどちらもありうる)
- scalability
- システムの成長に対して無理のない方法で対応可能
- 負荷パラメータを色々紹介していた。
- 負荷を測るメトリクスを色々紹介(latency, response timeの99%tileなど。)
- maintainability
- ❶運用性 = システムの健全性が上手く可視かされ、システムを効率的に管理する方法が用意されていること
- ❷単純性 = 偶発的な複雑性を取り除く。例えばabstractionが有効。
- ❸進化性 = 本書では、データシステムにおけるアジリティのこと。
2章 データモデルとクエリ言語
データモデルを目的に応じて使い分けよう。
アプリケーションエンジニア視点でのそれぞれのデータベースの違い。
- リレーショナルモデル
- 結合やassociationのサポートに優れている
- ドキュメントモデル
- データが自己完結している。jsonとかxml。
- 履歴書のように、データとしてほぼそれだけで完結している。複数のデータをがっちゃんこして作る必要がない
- スキーマが柔軟
- ローカリティから来る優れたパフォーマンス
- グラフ型データモデル
- あらゆるもの同士に関係が存在する。
- グラフ型データモデルではクエリ1つにつき辿らなければいけないエッジの数は未定。
- ❶プロパティグラフモデル
- レコードは、nodeとedgeの二種類 p.53
- クエリ言語は Cypherなど
- (SQLでも、再帰共通テーブル式を用いて、グラフクエリを書くことができるが、クエリが長々しくなる。👉 このユースケースに対するデータモデルとしてリレーショナルモデルが向いてない)
- ❷グラフトリプルストアモデル
- レコードはノードとエッジの二種類 p.58。
- ノードはタイプを指定するのみ。
- エッジは二種類。Sが始点のノードに相当。
- SVO(VCはSノードのプロパティのKey valueに相当)
- SVC(Vはエッジのラベル、Cは別のノードに相当)
- クエリ言語はSPARQLなど。クエリを書く際に、SVOを辿るのかSVCを辿るのかを意識せずに書くことができる。
- ❸Datalog 👉 読み飛ばした。
- 全文検索のデータモデル 👉 後で取り上げる
3章 ストレージと抽出
3.1 データベースを駆動するデータ構造
追加のデータ構造としてインデックス(メモリ上、ディスク上を問わない)をメタデータとして持つことで、
- 書き込み時のオーバーヘッドはかかるが
- 読み取りは高速になる
以下インデックスをしようするデータベースのデータ構造を紹介する。
なお、ハッシュインデックスとLSMツリーは、log structured ストレージエンジン。Bツリーはページ指向のストレージエンジンである。
ハッシュインデックス (P.76)
シンプルなキーバリューストア。
- データ構造
- メモリ上にマップをもつ。valueはディスク上の値が入っている位置。
- ディスク内のログファイル。これはセグメントごとに分けられていて複数存在する。バックグラウンドスレッドで複数のログファイルのマージ&コンパクションを行う。
- データ挿入時にはメモリ上のマップの更新とログファイルへの書き込みが行われる。
- クエリ時には、セグメントを新しい順に見ていく
- 👎️Keyの数が多すぎると、メモリから溢れてしまう
- 👎️レンジに対するクエリの効率は良くない
- 👎️サーバー再起動時にハッシュインデックスを作り直す時に時間がかかる
LSMツリー (log structured merge tree)
キーでソートされたキーバリューストア。
- データ構造
- ①メモリ上のmemtable (最新のデータ収納場所)
- ②ディスク上にmemtableのバックアップのログファイル
- ③ディスク上のセグメントファイルごとにSS tableを保持する。
- ④メモリ上の(疎な)インデックス。Key -> SS tableの位置のマップ。
- ③と④に関しては、バックグラウンドで、複数のセグメントがマージとコンパクションされる(コンパクションは size-tiered compactionとleveled compactionの二種類)
- クエリ時は、
memtable → 新しいSS table →古い SS table →....
の順で探索する。なお、存在しないKeyの探索を行わないために、追加でbloom filterを用意する。 - データの挿入は、
- メモリ上のmemtable(sorted map)に追加される
- ディスク上のバックアップ用のログファイルへ追記される
- なお、memtableのサイズが閾値を超えたら、memtableは新しいSS tableへとダンプされる。バックアップのログファイルは削除される。
- 👍レンジクエリは効率よく実行できる。(各SS table内でレコードがソートされているので)←SS tableのインデックスはソートされているのでO(logN)でキーの場所へたどり着ける??
- 👍キーの数が増えてもメモリから溢れない。全てのキーをメモリ上のインデックスにおく必要がないため。
Bツリー
TODO: 読む。
3.2 トランザクション処理と分析処理
- OLTPデータベース
- 高い可用性と低いレイテンシーが求められる
- OLAPデータベース = DWH
- 用途はリードオンリー
- リレーショナルが一般的
- DWHはスタースキーマと呼ばれる
- fact table
- 1row=1イベント
- terabyte - gigabyte
- 巨大かつ1度にアクセスされるのは数カラムなので、列指向ストレージを使うことが多い。
- dimension tables
- 数百万行とか
- factテーブルの周りに星のように位置しているイメージ。
- fact table
3.3 列指向ストレージ
columnar storageで使われているテクニックを紹介する。
- columnの圧縮
- (値が頻繁に繰り返されていたら圧縮のよい兆候である。)
- bitmap encodingを使うことで、(カラムに入っているデータの種類数) 個のbitmap (1つあたり1bit ×レコード数) で値を表現できる。つまり1つのカラムをn個のビットフラグに分割していると考えられる。
- 👉 And検索やOr検索は、ファルタリングしたい値に相当するbitflagのみを辿っていくだけで良い。
- レコードをソートして保存する
- あるカラムだけをソートしてもダメ。全てのカラムのレコードをソートする必要がある。
- 👉 複数のカラムに関してソートした結果をそれぞれ持っておくためには、それぞれにデータをコピーする必要がある。
- 👍 結果的にレプリケーションの役割も果たせる
- レコードの挿入
- 圧縮されたカラムに対するinplaceの書き込みは不可能
- 圧縮なしなら、これを可能にするデータ構造は LSMツリー
- 集計
- materialized view を使う。これはディスク上にキャッシュをもつview tableと考えられる。
- materialized view の一例として、data cube (OLAP cube)がある。これはN次元テーブルである。(P.108)
- ①このテーブルのそれぞれの次元は1つのdimension tableに相当する
- ②それぞれのセルはfactテーブルの集計値。それぞれのdimensionでそのセルに相当する値を満たす集合の集計値。
- 👉 特定の集計クエリが高速化できる。
4章 エンコーディングと進化
データエンコーディングに
- 後方互換性がある = 新しい仕組みでエンコーディングされたデータを古い仕組みでデコードできる
- 前方互換性がある = 古い仕組みでエンコーディングされたデータを新しい仕組みでデコードできる
4.1 データエンコードのフォーマット
(前方/後方)互換性を考える上では以下それぞれが互換性を壊さないかを考える必要がある。
- カラムの追加・削除
- カラムのデータ型の変更
- カラム名の変更
エンコードの互換性は、デプロイの選択肢に影響を及ぼすという点でシステムデザインに影響がある。またアプリケーションの進化性にも関わる。
JSON
- human readable
- これをバイナリエンコーディングする方法としてMessagePackがある。
- 👎データ型が一意に決定できない。
Thrift
二種類ある。
- BinaryProtocol
- フィールドのデータ型 + フィールドタグ + 実データ
- 整数型は固定長
- ConpactProtocol
- フィールドのデータ型 + フィールドタグ + 実データ
- 整数型は可変長
Protocol Buffer
- フィールドのデータ型 + フィールドタグ + 実データ
- 整数型は固定長
- requiredか optionalかはデータのエンコーディングに影響しない。アプリケーションレイヤーでエラーを吐くためだけに使われる。
- repeated型のエンコーディングは、同じフィールドタグを複数回現れるだけ。→ リストのネストはサポートできない
- 互換性
- フィールドタグの前方互換性は新しく項目するフィールドをrequiredにしない限り保たれる。
- フィールドタグの後方互換性は保たれる。
- データ型の互換性は保たれる保証無し。
Avro
エンコーディングされたデータにはフィールドやフィールドの型を示すデータは含まれない。writerが使用したスキーマあるいはそのバージョンがメタデータとして含まれるので、読み取り側はreaderスキーマをwriterスキーマと照らし合わせ、それぞれのフィールドを読み取るか無視するか、どのフィールド型として読み取るかを決定する。
- reader側が持ってないフィールドに関してはnullではなくデフォルト値が代入される。
- 互換性
- フィールドの互換性を守るため、追加や削除できるのはデフォルト値を持っているフィールドのみ。
- データ型はAvroが型変換できる限りで互換性が保たれる?
- フィールド名の変更は後方互換性は保たれるが、前方互換性は保たれない。後方互換性を保つためには、リーダーのスキーマにフィールドのエイリアスを持たせる。
- 用途の一つとしては、RDBのスキーマからAvroスキーマの自動生成。~これはdocumentとしての役割も持てる。
4.2 データフローの形態
以下三種類ある。
- ①データベース経由のデータフロー
- ②サービス経由のデータフロー
- ③メッセージパッシング経由のデータフロー
①③では、「アプリケーション層において、デコード時に未知のフィールドを喪失して、それを再びエンコードする」ことでフィールドの値を喪失することがないように注意が必要である。