『RDB技術者のためのNoSQLガイド』読書ノート

2017/03/20

RDB技術者のためのNoSQLガイド』を読んだので、学んだ点をまとめる。

第2章 イントロダクション

NoSQLにすると嬉しいこと・辛いこと

外部サービスのデータ格納などにおいて、NoSQLにすると、スキーマが変わってもとりあえず溜めておける。スキーマが変わってデータを取得できないというリスクを回避できる。

よくあるNoSQLの勘違い

音声、画像などのマルチメディアデータは一つのデータ容量が大きいため、NoSQLが扱うのは不得手。
全文検索機能もないため、テキストデータもNoSQLではなく自然言語処理や全文検索機能に優れたデータベースを利用すべき。

NoSQLが得なのは、上記のような非構造データではなく、JSONなどの構造を事前に定義できない「半構造データ」。

データベースの中のNoSQLの位置づけ

データベースの4つのエリア

  • RDB(OLTP): Online Transaction Processing. MySQL, PostgreSQLなど
  • RDB(DWH): Data Warehouse, Teradata, Oracle Exadata, Redshift, Big Queryなど
  • Hadoop(HDFS+MapReduce)
  • KVS/DocumentDB

データウェアハウスは、列指向(カラムナー)と呼ばれる性質を持っており、行ごとにデータを格納するOLTPよりも全量データに対する集計・抽出に特化している。列指向ではデータを列ごとに圧縮して格納し、ある列の値を連続して取得できるため、IOが少ない。
例えば、性別(男 or 女)を格納する列では、男のデータ領域に連続して、男の行を指すIDが格納されている。

第6章 Redis

データ型

Strings, Lists, Sets, Hashes, Sorted Setsといったのプログラミングで使う基本的なデータ型をもつ。

Redis Strings

最大512MB。
文字列、数値だけでなく、シリアライズされたオブジェクト、JPEGイメージなども格納できる。
数値の場合は、INCR, DECRコマンドで加算と減算ができる。

Redis Lists

LinkedList実装。
リストの左端、右端のどちらかから要素の追加、削除をおこなう。左端、右端に近いデータは高速にアクセスできる。
LPUSHコマンド、LRANGEコマンドなどが使える。

Redis Sets

Sets同士の部分集合計算ができる。
集合の共通部分の抽出や集合の併合が短時間で実行可能。

Redis Hashes

文字列のフィールドとバリューのマップ。
オブジェクトを表現するのに適している。

Redis Sorted Sets

各要素がスコアを持ち、スコアの大小によってソートされる。
ソート済みのため、追加・削除・更新、検索等のデータアクセスがSetsに比べて早い。

永続化

RedisはインメモリDBだが、スナップショットを取得する、あるいは更新差分のコマンドを記録することで永続化できる。

RDBファイル

スナップショットとしてバイナリファイルに書き出す。
書き出すタイミングは、redis.confにキーの追加・更新回数の閾値とそのチェック間隔を指定することで決定される。
save 300 100という設定だと、300秒毎にキーに100回以上更新があれば書き出すことになる。
スナップショットは、Redisが子プロセスをフォークして実行される。
デメリットは不意の停止に弱く、最大でチェック間隔分の書き込みが失われる。

AOF

AOFはサーバに対して発行されたコマンドを記録したファイル。
書き出しはfsyncによって行われる。書き出しのポリシーは3種類ある。

  • fsyncを行わない
  • 毎秒fsyncする(default)
  • クエリが発行される毎にfsyncする

AOF書き出しも、子プロセスをフォークして実行される。
Redisサーバの再起動時には、AOF内のコマンドが順次実行される。この特性を使って、Point In Time Recoveryのようなこともできる。
デメリットはファイルサイズが大きいこと。

部分的トランザクション

ロールバックが無いものの、トランザクションが使える。
MULTIコマンドとEXECコマンドを使う。MULTIコマンド以降入力されるクエリはRedisのキューに挿入され、EXECコマンドが実行されるとキューに格納した順に実行される。
独立性(Isolation)が保障されており、他のクライアントからの操作による影響は受けない。
ロールバックがないので、トランザクション中にエラーが発生した場合でも元に戻せるようにプログラムを組まなくてはいけない。

Redis Cluster

より多くのデータを扱えるとともに、読み込みと書き込みを分散できる。
プログラミング言語のクライアントを使用するか、redis-cliコマンドに-cオプションを使用することで、Redis Clusterに接続できる。

以下の特徴がある。

  • クライアントはどのノードにつないでもよい。ノードがキーに対応するハッシュスロットを保持していなければ、問い合わせを適切なノードにリダイレクトする。
  • redis-trib.rb reshard ip:portでノードの追加、削除(リシャーディング)がクラスタを止めずに行える。
  • Master Slave Replicationと併用できる。

Replication

Redis ClusterとReplicationと併用した場合、Masterが故障しても、一定時間後(node_timeout)にSlaveがMaster昇格してくれる。Master、Slaveともに全滅した場合は、Redis Clusterとしての操作を継続できなくなる。

データロスト

Replicationは非同期のため、データロストが発生し得る。WAITコマンドを利用すると、Slaveへの反映が終了するまでクライアントからの書き込みをブロックすることで、同期Replicationを実現できるが、パフォーマンスが低下する。
またネットワークの分断でもデータロストが発生し得る。あるMasterがClusterとネットワーク分断されても、タイムアウトまでクライアントがMasterに書き込み続けた場合、Slaveはその書き込み内容を受信できないので、タイムアウト後、内容が消失する。

リカバリ時の注意

Masterのデータを永続化していない場合、Masterが自動で再起動しないように設定しておく必要がある。
Masterが再起動すると、データが空になり、それがSlaveに伝播することでSlaveでも全データが消失してしまう。

レプリカマイグレーション

Master昇格によりSlaveがいなくなってしまった組に対して、複数Slaveを所持している組からSlaveを移転することができる。
マイグレーション対象のノード上で、CLUSTER REPLICATE node_idコマンドを実行する。

第7章 Cassandra

概要

スキーマ

Cassandraはスキーマレスのデータベースではない。スキーマ定義(構造化)でデータを取り扱う。アプリケーションから見ると、データが構造的に見えるので開発、運用しやすい。

アーキテクチャ

スケールアウトするマスターレス方式。マスターノードがない。
データベースを構成するすべてのノードにデータが均等に分散され、すべてのマシンで読み書きが可能。

  • ノードを追加することで水平にスケールする。
  • ノードの追加、削除には自動でデータ分散を均等に調整する。
  • データと機能の両方の冗長性を備え、SPOFがない。

トランザクション

ACID特性のうち、AIDを実現できている。書き込まれるデータはアトミックで、独立性、永続性がある。参照整合性や外部キーの考え方はない。
複数の処理を一つのトランザクションでまとめることはできない。
ロールバック、ロックという概念が存在しない。

データモデル

パーティションキー

データは必ず一意のパーティションキーを持つ。パーティションキーによってどのノードにデータを保持、処理するか決まる。
パーティションキーによってパーティション(行)が決まり、パーティションの中には入れ子で複数行を持つことができる。複数行持つのは、プライマリキーをパーティションキーとクラスタカラムの複合キーにした場合。プライマリキーがパーティションキーのみにした場合、パーティション内には1行しか存在しない。

読み書き

Cassandraへのデータ書き込みは、データの完全な永続性と高速なパフォーマンスを併せ持つ。
書き込みは、まずディスクのコミットログに記録される。同データがmemtableというメモリ上の構造にも書き込まれる。memtableのデータ量が閾値を超えるとディスクにフラッシュされ、SSTable(Sorted Strings Table)というデータベースファイルに書き込まれる。SSTableに書き込まれたデータのコミットログは上書き可能になり再利用可能な領域となる。

フラッシュされるたびにSSTableが作成されるので、読み取りの高速化のためにSSTableを1つにまとめるコンパクションが定期的に実行される。

クラスタ

データとクエリを分散させることで性能向上を図れる。

クラスタ

1つのデータベースシステムのことで、一番大きな括り。

データセンタ

クラスタを構成する単位。1データセンタでもいいし、同じデータをレプリケートしたデータセンタ複数でクラスタを構成してもいい。複数あればクエリを分散させることも可能。

ノード

データセンタを構成する各マシン。

レプリケーション

レプリケーション係数

可用性を向上させることができる。複数のノードにデータをレプリケートできる。レプリケーション係数により、何個のノードにデータがあるか決まる。レプリケーション係数は3にするのが標準的。

整合性

レプリケーションの整合性レベルは設定により選べるが、基本的に結果整合性を保証する考え方。整合性レベルは操作ごとに指定することもできるので、アプリケーション要件によって細く制御できる。
整合性レベルは、ANY,ALL,QUORUMなど。

認証と権限

RDBと同じスタイルのCREATE/ALTER/DROP USERコマンドによるユーザとパスワードの操作。権限もGRANT/REVOKEで設定可能。
最初のセキュリティ認証定義のプロセスを開始できるように、cassandraというスーパーユーザが用意されている。

第8章 HBase

データモデル

行は行キーと呼ばれるテーブル中のデータを特定するために必要なデータと、複数の列データから構成される。
列は列ファミリという単位でグループ化される。
セルはHBaseのデータモデルのうち最小の単位で、テーブル名、行キー、列ファミリ名、列名によって指定される。データとしてバージョンと値をもつ。

データに型はなく全てバイト列である。

部分的トランザクション

部分的なトランザクションがサポートされている。
行単位のロックにより、行単位の更新について成功するか失敗するかのいずれかになり、原子性(Atomicity)が保証されるが、複数行の更新においての原子性は保証されない。
整合性、独立性も行単位についてのみ保証される。
永続性は、参照されるデータは全て永続かされているため保証される。

第9章 Amazon DynamoDB

特徴

マネージド型なので管理不要で信頼性が高い。3つのアベイラビリティゾーンにデータが自動的にレプリケーションされる。
テーブルごとにディスクのRead/Writeそれぞれのスループットキャパシティの値を割り当てることができる。
ストレージの容量制限がない。

データモデル

テーブル、アイテム、アトリビュートから成り立つ。アイテム数の制限はないが、アイテムサイズ(列の長さ)とアトリビュートの文字数には制限がある。
String, Number, Binary, StringSet, Map, Listなど多数のデータ型がある。単一データ、多値データ(Set)、ワイドカラム型(Map, List)の3種類に分けられ、ワイドカラム型は主にJSON形式のドキュメントを入れ込むものである。

キーとインデックス

ハッシュキーとレンジキーの二つのキーがある。
主キーに対するインデックス以外にもローカルセカンダリインデックス(LSI)、グローバルセカンダリインデックス(Gシ)という2つのインデックスを利用できる。
LSIはハッシュキーの範囲の中でキー以外の属性を指定できるインデックスで、レンジキーの代替となる。
GSIはハッシュキーを超えてテーブル全体でキー以外の属性を指定できるインデックス。

結果整合性

Writeでは信頼性の観点から2つのアベイラビリティゾーンに書き込み完了の確認が取れた時点でACKを返す。
Readは複数のアベイラビリティゾーンにランダムにアクセスするため結果整合性の考え方が適用され、最新の情報が反映されないこともある。Consistent ReadというオプションでReadリクエストを受け取る前までのWriteが全て反映されたレスポンスを保証することができる。

第10章 MongoDB

特徴

  • JSONの一部書き換えなど、JSONについての豊富な機能
  • 配列の要素へのインデックス、一部のJSONだけをインデックスに含めるなど、インデックスについての豊富な機能
  • 集計、結合、配列展開などのフィルタをパイプラインで組み合わせて複雑な集計ができるアグリゲーションフレームワーク
  • ドキュメント構造や型へのバリデーション機能
  • データベース定義やコレクションのスキーマを定義する必要がなく、いきなりJSONを挿入可能

Webアプリのバックエンドとしてだけでなく、ログ蓄積の用途にも多く使われている。Fluentdでログを集めてMongoDBにJSONとして入れる使い方が多い。期限付きインデックスを張っておくことで、自動的に古いログを削除してくれる。

インデックス

種類

様々な種類のインデックスが利用できる。

  • 単一インデックス
  • 複合インデックス
  • マルチキーインデックス(配列の要素に対してのインデックス)
  • テキストインデックス(アルファベットに対しての全文検索)
  • ハッシュインデックス(シャーディング環境で、偏りのあるキーを均等分散させるのに使う)
  • 地理空間インデックス

属性

MongoDBではインデックスに対して属性をつけることができる。

  • TTL属性(古いデータの削除を実現)
  • パーシャル属性(部分インデックスの実現)
  • スパース属性(部分インデックスの実現)
  • ユニーク属性

実行計画とHINT

explain()メソッドで実行計画がわかる。
インデックス利用を強制するhint()メソッドがある。

シャーディング

mongosルータと呼ばれるプロセスにアクセスすると、mongosルータはデータがどのMongoDBに入っているかを知っているため、クエリの内容に従って適切なMongoDBにクエリを割り振る。
mongosルータは設定サーバ(配置情報を永続化するための特別なMongoDB)の情報を読み込んで起動する。
オンラインでのシャードの追加削除が可能で、データの自動再配置を行なってくれる。

レプリケーション

プライマリとセカンダリを組み合わせてレプリカセットを作る。mongosや設定サーバは不要。
レプリカセット内のMongoDBはお互いにヘルスチェックしあっており、プライマリが故障すると新しいプライマリを選出するための選挙を行う。新しいプライマリが選出されるまでは書き込みができない。

クエリごとに「何台のMongoDBに書き込んだらアプリケーションに応答するか」という値(W値)を決めることができる。

バックアップ

  • MongoDBを停止してファイルシステムごとバックアアップ
  • mongodumpコマンド
  • mongoexportコマンド(バイナリではなくjson,csv,tsvのいずれかの形式)
  • MongoDB Ops Manager

第11章 Couchbase

特徴

Memcached互換プロトコルによるドキュメントDB。

  • JSONドキュメントに対してSQLライクなクエリ言語でアクセス可能
  • シャーディング
  • Memcachedを組み込んでいるためMemcachedと同等のキャッシュサーバ性能に、レプリケーション、永続化等の機能を持つ

第14章 そうていされるNoSQLのユースケース

Redis

キャッシュとしての用途。Hash型などの構造化したデータをもつことができるため、Memcachedと比べると問い合わせ結果をアプリケーションから扱いやすい形でキャッシュしておくことが可能。
またマスタースレーブレプリケーションやRedis Clusterなどの機能によってスケールアウトが可能なため、アクセス数に応じてリソースをコントロールしやすい。

  • クエリと問い合わせ結果の対応
  • セッション情報
  • アプリケーションによる処理・分析結果
  • HTML、CSS、画像データなどの静的ファイル

Cassandra

IoTなど、毎秒あたり数万件以上のオペレーションを処理する用途。高速なパフォーマンスとノードを追加すればリニアにスケールする点、単一故障点がない点などが強み。
Cassandraのカラムは任意のカラムだけを物理的に持たせられるため、様々なモノからデータを取得した場合にデータのフォーマットがそれぞれ異なっても問題ない。カラムにデータが存在しない場合でもNULLとしてデータを扱い、NULLポインタを物理的に持つ多くのRDBとは大きく異なる。

MongoDB

ログ格納としての用途

  • RDBであれば、各アプリのログフォーマットがバラバラのためテーブル定義が大変。
  • ログが増えてくると書き込み性能の向上が必要になるため、スケールアウトが容易であることが求められる。
  • ログ管理は時系列の挿入と絞り込み、集計ができればよく、トランザクション、結合等の高度なRDBの機能はオーバースペック。
  • MongoDBのインデックスにTTL属性をつけることで、古いデータの自動削除を行なってくれる。

プロトタイプ開発などの高速開発としての用途

テーブル定義をせずにスクリプト言語のオブジェクトをそのままの形でJSONとして保存できる。
特に近年のWebAPIの多くがJSONでデータを公開しているため、APIから取得したJSONをそのままMongoDBに入れることができる。

Couchbase

Webセッション情報のストレージ用途

Couchbaseであればスケールアウトにより性能の拡張が容易で、大量のユーザがアクセスしても低レイテンシを確保できる。
KVSでも実現できるが、扱うデータが複雑なデータ構造のため、スキーマが自由なCouchbaseが適している。

モバイルとサーバのデータ同期

Couchbase Serverではなくモバイルアプリケーション上で利用できるCouchbase Liteを使い、端末上のローカルデータベースとして利用する。
ネットワークの状態に依存せずアプリが稼動できるため、電池の消耗を抑えたレスポンスの速いアプリとなる。
ローカルデータベースとサーバサイドのデータベース間の同期はCouchbase LiteとSync Gatewayで実行してくれるため、自前で同期の仕組みを実装しなくていい。

第15章 NoSQLの選び方

NoSQLで解決するかわからないRDB(OLTP)の課題

トランザクション処理性能向上

表形式のデータ構造そのものを見直して、トランザクションで更新するデータが一つのJSONに収まる形や一つのワイドカラムに変えることができるのであれば、NoSQLを利用して処理性能を向上させられるかもしれない。MongoDBやCouchbaseでは一つのJSONの更新はACID特性を持って行える。
また、本当にトランザクション自体が必要な処理なのかどうかを見直すことで、不要だった場合、NoSQLに置き換えることで書き込み性能が改善する可能性がある。

高い処理処理性能を出すためのNoSQLの選び方

小規模なキーバリュー

Webセッション情報など小規模なキーバリューであれば、Redisはメモリにデータを書いて取り出すだけのシンプルな仕組みのため、Redisが最適。
大規模なデータであればメモリに収まり切らず、Redisには向かない。Redis Clusterを組めばメモリ量以上のデータを扱えるが、一度ランダムなノードに問い合わせて、そのノードがデータを持っていなければ再度適切なノードに問い合わせる必要があるため効率が悪い。

柔軟なデータ分散やレンジ指定クエリを行いたい場合

MongoDBのシャーディングはキーのハッシュ値だけでなくキーのレンジやそれらを組み合わせた複合キーによって分散できる。またキーのレンジで分散した場合、一定の範囲のデータが一つのノードに集められるため、一つのノードで完結するようなレンジ指定クエリを投げる時は効率がいい。

-読書ノート