【技術本】スケーラブルリアルタイムデータ分析入門
https://amzn.to/4lygw7S
【技術本】スケーラブルリアルタイムデータ分析入門
へポスト
テラバイト、ペタバイト級のビッグデータに対し、リアルタイムで分析結果を返すシステムのアーキテクチャの紹介を記した著作。
アーキテクチャを要約すると以下の3層に大きく分かれている。
・バッチ層
データを完全正規化したマスタデータを持ち、この全マスタデータから様々な用途に利用するための汎用的な中間データとしてバッチ処理を行いビューを作成しサービス層に提供する。
データは追記のみ。理由としてデータ汚染が起きた場合、追記のみなら問題の起きた行を取り除いてビューを再計算すれば正常化できるようにする。
高レイテンシの計算を行う。
・サービス層
バッチ層の作成したビューを持つ。テーブル結合などはコストが高いので事前に冗長なデータを含んだ非正規化した状態のデータに加工したビューであり、各用途のクエリに特化したビューを複数保持している。
インデックスが張られ高速にアクセスできるインタフェースを提供。
ランダム読み出しをサポート。
・速度層
バッチ処理に含まれなかった新規データ分のみによる計算結果を数ミリ秒単位で返す。
以上により、膨大なデータ量をバッチ層で事前計算しサービス層でビューとして提供する事で、リアルタイムの計算は、サービス層のビューの計算と、速度層の新しいデータ分の計算を処理したものだけで済む。本著作では数ミリ~数十ミリ秒での応答を想定している。
また将来的に更なるデータ量増加などの負荷増大にも対応し、各層ごとに分散スケール可能なミドルウェアを使用し耐障害性も考慮している。
・バッチ層:Hadoop(HDFS),MapReduce
・サービス層:ElephantDB
・速度層:Apache Cassandra,Apache Kafka, Apache Stormなど
更に、分散環境で計算を行う環境でありながらも、同一であったり集約するデータはサーバ間で分散されないようにハッシュなどを利用して一つのサーバに集約して複数サーバ間で応答を待つような事が起きないようレイテンシを短くしたり、サーバ内のデータ配置も同一パーティションに連続記録されるよう考慮しディスクシーク時間のレイテンシを短くするなど工夫も行っている。
そしてこのアーキテクチャを元に、次の機能を例に、データ論理設計や実コードによる実装の紹介を行っている。
各機能ごとにデータの特性があるので、独自の機能追加を想定した場合の参考にもなる。
・時間区切りのURLごとのページ閲覧数
・時間区切りのURLごとの重複なし訪問者数
・直帰率分析
ビッグデータをリアルタイムで分析し、かつ各機能レイヤごとにスケール可能であるという、今までの古い伝統的なミドルウェア類、論理データ設計の経験がある場合は複雑に考えて仕舞いがちかもしれないが、本書のアーキテクチャ(ラムダ・アーキテクチャ)は至ってシンプル。分散スケールや耐障害性機能の大半をミドルウェアの機能に頼ったのもあるが、過去のレガシーな呪縛を脱ぎ捨てて新しく柔軟な膨大なデータを扱えるリアルタイムシステムの事例の一つとして大変興味深く読むことが出来た。
スタートアップでゼロベースからアーキテクチャを構想する経験のできる貴重な著作だと感じた。
記載概要メモは以下の通りです。
------------------------------------------
1.ビッグデータを扱うための新しいパラダイム
ビッグデータによってリレーショナルデータベースなどの伝統的なデータベースシステムは限界にきている。
NoSQLデータベースなどは、巨大なデータ集合に対してスケールで対応できるが、これらの新技術を効率よく扱うには複数の技術を組み合わせる必要がある。
ビッグデータに対しより単純な代替手法としての新しいパラダイム=ラムダアーキテクチャ
伝統的なデータベースを使ったスケールはデータベース負荷がボトルネック。
例)
->処理タイムアウト発生。
->アプリとデータベース間にキューを挟んで複数のイベントを纏めてデータベース更新頻度を減らして対策。
->さらに利用者増加により負荷が上昇。水平分割(シャーディング)を迫られる。シャーディングのたびにデータ格納整理、多数の稼働中のワーカスクリプトの管理、アプリケーションの修正など複雑さが増していく。
->サーバ数が増える=耐障害性問題が深刻になっていく。
->追い打ちをかけるようにプログラムバグによるデータ汚染。
ビッグデータ技法がどう役立つのか:
・分散システムの特性を意識:シャーディング、複製などはシステムが代わりに行ってくれる。スケールも、こちらはノードを加える程度で済む。
・データを不可変にして扱う仕組みにすることで対人為障害などによるデータ破壊を防ぐ。
ラムダアーキテクチャの持つ特性
・頑強さと耐障害性
・低レイテンシの読み出しと更新
・スケーラビリティ
・汎用化:様々なシステムが利用できる
・拡張性
・非定型クエリ:任意のクエリが実行できる
・最小限の保守
・デバッグ可能性:問題時に必要な情報を提供できる
完全増分アーキテクチャ
・ディスクの索引が増分方式で追加されたり変更されたりすると使われなくなった領域の一括回収(コンパクション)が起こる。コンパクションはCPUとディスクI/Oを大量に消費する副作用がある。->全体障害への懸念。ラムダアーキテクチャでは、コンパクション不要。
高可用性と一貫性の両方を実現することは不可能。
ネットワーク分断障害が終わってから結果整合性を回復するにはアプリケーションからの大量な支援が必要。分散データベースが高可用性実現のため保存情報の全ての複製を複数保持しているので、ディスク障害やネットワーク障害があっても情報を使い続けられる反面、複製によって受け取る更新が異なり、複製の内容が枝分かれしてしまう。
・何らかのミスがデータベース内の状態を変更し、データ汚染を引き起こしてしまう。
同一性情報の扱い
例:複数のIDを利用するユーザを同一人物と換算して閲覧履歴情報を分析する。
もしも、新しい同一人物と紐づけられる情報が後からわかると、あらゆるクエリの結果が変わる可能性が出てくる。完全増分型では、エラー率が高い近似法を使わざるをえない。ラムダアーキテクチャと比較しても完全増分型はレイテンシとスループット両方でパフォーマンスに影響が出る。データアクセスもランダムアクセスになるのでHDDのシーク時間がボトルネックとなるのでSSDが必須となる。
ラムダアーキテクチャ:下記の3つの層で構築される
・バッチ層
数十テラ〜数十ペタの全データをバッチで事前計算する層
マスタデータを保持
・サービス層
バッチで計算したビューへのアクセスを提供する層
何らかのデータ汚染が起きても、バッチ層からデータを再計算すれば良い。
・速度層
バッチ処理開始より後の新しいデータの増分を低レイテンシで応答する。
データに対してランダムアクセスを提供。
ユニークユーザ換算などは、近似値計算を利用することになる。
------------------------------------------
2.ビッグデータのためのデータモデル
正しい情報を生み出す源=マスタデータの集合。唯一データ汚染から完全に守る必要あり。
サービス層、速度層のデータ集合を失うことになっても、バッチ層のマスタデータ集合から再構築できる。
・非構造化文字列の方が生データに近い。これらを保存すべき。
・データは不可変にする。データは追加するのみ。更新はしない。
->耐人為障害性を持たせることができる。
・データ汚染が起きたらビューを再計算する。
・データの変更履歴を辿れるような形で追記のみの実装->簡潔さ。
・事実ベースモデルによるデータ格納方法を推奨。
データをこれ以上分解できない基本的な部品まで分解する方法のこと。
事実を一意に同定可能なデータと断定できるようにnonce(64bit乱数値)をスキーマに追加する。
・データ集合に任意の期間の変更履歴をたどることができる。
データ汚染が起きても、誤った事実のみ削除することで正常化する。
・バッチ層、サービス層で利用するデータを分けることで、正規化、非正規化データの良いところを利用できる。
->バッチ層:正規化
->サービス層:データをクエリ加工用にテーブルを結合した非正規化されたデータを利用できる。結合処理が不要になりパフォーマンスも上がる。
・データの格納について
グラフスキーマというデータ集合の構造を記録したグラフを事実ベースモデルを利用して格納する手法と、必要フィールドが全て存在するか定義をチェックする強制可能スキーマを使う。
------------------------------------------
3.ビッグデータのためのデータモデル詳説
Apache Thriftによる静的に型付けされた強制可能スキーマを定義
・フィールドの存在と、期待する型のみチェック。
値の範囲や、存在しない未来のタイムスタンプ値の禁止などの高度な属性確認はできない。
------------------------------------------
4.バッチ層のデータストレージ
分散ファイルシステム
・ファイルを複数のバイト列で構成しディスク上に連続して格納できる。
・ファイル圧縮が自由。
・細かい粒度のパーミッションシステムは不可変性を強制するのに都合がいい。
・クラスター構成でマシン追加でスケール。
・耐障害性設計がある。
Hadoop分散ファイルシステム(HDFS)
・ファイルは複数ブロックに分割され、ブロックはクラスター内の複数データノードに分散される。
・ファイルを多数のノードに分散->並列処理を可能にする
・ブロックが複数ノード間に複製->個々のノードがダウンしてもデータにアクセスし続けられる。
・ネームノードがそれぞれのファイルに対するブロックを追跡、格納先を管理。
データの垂直分割を考慮
・データ全体から、関数の計算に関係するデータだけにアクセスできるようにデータ分割を容易にする。
例):日付ごとにフォルダ分け
フォルダ内のファイル操作でシェルなどでデータ集合の操作を行うには低レベル言語すぎるので注意:操作自動化などのライブラリを利用すべき。
->Pailライブラリ(後述)
------------------------------------------
5.バッチ層のデータストレージ詳説
Hadoop(HDFS)を採用。
HDFS API紹介。
MapReduceと連携する場合は小さなファイルを格納すると計算性能が著しく低下するので注意->事前にデータを集約すべき
Pailライブラリ
・複数の小さなファイルを別フォルダに統合するときに利用。
・MapReduceを使って実装されている。
・データの垂直分割にも対応。
・データ圧縮にも対応。
ストレージに格納するまでの流れ
生データ->Thriftオブジェクト->Pail垂直分割
------------------------------------------
6.バッチ層
事前計算としてマスタデータ集合をバッチビューに変換する。
バッチビューはサービス層に取り込まれ、サービス層はインデックスを作成することで中間データへの迅速なアクセスを可能とする。
速度層はバッチビューに取り込まれていないデータを使った低レイテンシの更新を提供する。
バッチ層で全てのあらゆるパターンを事前計算できるとは限らない。
汎用的な中間結果を事前計算しておいて、後でクエリが来てからその中間結果を使って処理を完了させる。
性能と耐人為障害性、アルゴリズムの汎用性の観点から、増分アルゴリズムは使わず、再計算型アルゴリズムを使う。
データ格納の問題
URLごとの重複なし訪問者を調べる場合。
再計算型は、URLと訪問者数のマッピングのみで済むが、増分型は、増分前の訪問者も比較できる様ユーザIDなどを保持しなくてはならない。
->従って再計算型が優れている。
更に再計算型は、データ汚染が起きても、問題のデータを修正、削除さえすれば、再計算で回復できる。
バッチ層では、再計算型アルゴリズムをサポートし、補助として一部増分アルゴリズムを使用するなどに留めておく。
MapReduce
・分散計算のパラダイム。スケール可能で耐障害性の高いバッチ計算のための基本要素を提供。
・キー・バリューのペアを操作するmap関数とreduce関数の視点で計算処理を記述できる。
例):単語数カウント
-> map関数:テキストの行ごと実行し、単語ごとに単語、カウント1を設定
-> reduce関数:同じ単語ごとにカウントを集計
・同時実行、マシン間のデータ転送、実行計画の作成の詳細はMapReduceフレームワークによって抽象化される。
・map関数、reduce関数がクラスター内で並列実行される。
・耐障害性も備える。
バッチ処理を考える自然な手法としてパイプ図の利用。
バッチ層は、高レイテンシの計算をする。それを逆手にとって実時間ではできない深い分析やコストのかかる計算をすべき。
------------------------------------------
7.バッチ層詳説
パイプ図をほぼそのままマッピングするツール:JCascalog
・MapReduce計算を表すために合成可能な抽象化を提供するJavaライブラリ。
・MapReduceに対する高レベルインタフェースを提供。
------------------------------------------
8.バッチ層の例:アーキテクチャとアルゴリズム
下記機能の設計
・時系列に並べたページ閲覧数
時間、日、週、月など粒度ごとに事前計算し、あらゆる範囲計算のパターンを効率的にカバーする。
それぞれを組み合わせ最小の要素数で結果を出せる様にする。
・時系列に並べた重複なし訪問者数
時間枠ごとの訪問者集合へランダムアクセスが必要だが高コスト。
正確さを犠牲にする概算アルゴリズム(HyperLogLog)の活用。
・直帰率分析
2つの訪問間隔が30分未満の場合、同じ訪問の一部。1つだけなら直帰と判定
新しいデータの併合タイミング
バッチを動作させる度に、新規データをマスタデータに併合して計算を行う。
これにより、全てのバッチビューが全く同じマスタデータに基づいていることを保証。
バッチフローワーク中に新しいデータが入るのを防ぐ。
------------------------------------------
9.バッチ層の例:実装
Thrift,Pail,JCascalogなどを利用して実装。
------------------------------------------
10.サービス層
バッチ層が計算したビューに索引付けしてインタフェースを提供。
事前計算したデータを迅速にクエリできる様になる。
ハードウェアがHDDの場合はディスクシーク時間を考慮する。データを同じパーティションに連続して格納できる様に設計する。
正規化されたデータのクエリは遅くなるので、敢えて冗長な情報を含むように表を結合したビューを作成する。->非正規化。
マスタデータを含んだバッチ層と、サービス層を分離することで、バッチ層を正規化し、サービス層をクエリに最適化した非正規化を共存させることができる。
矛盾やデータ汚染が起きたらサービス層をゼロから再計算すれば簡単に修復できる。
サービス層データベースを複数マシンに分散。分散しているので耐障害性も必要。
ランダム読み出しとビューの一部に直接アクセスするため索引をサポート。
ランダム書き込みは不要なのでオンラインコンパクションは不要。
------------------------------------------
11.サービス層詳説
ElephantDBを利用。
・シャード数、サーバ数、複製数を指定可能。
バッチビューを一定数のシャードに分割し、それぞれのElephantDBサーバがシャードの一部を担当。
------------------------------------------
12.実時間ビュー
増分計算に基づき結果を返す。
サービス層ビューに取り込まれる前のデータだけを扱う。
速度層ビューは一時的。データがサービス層ビューに取り込まれたら即座に破棄できる。
一般的にレイテンシがミリ秒レベルの資源効率に優れた方法で実時間ビューを作成する必要がある。
・ランダム読み出し:索引付け
・ランダム書き込み
・スケーラビリティ:実時間ビューを多くのマシンに分散
・耐障害性:ディスク、マシンに障害が起きても実時間ビューの取得は正常に機能させる。
オンラインコンパクション、並行処理におけるトランザクション分離レベル機能などのロックは、扱うデータがサービス層よりずっと少ないので、切迫した状況は起きない。
CAP定理:一貫性、可用性、分断耐性のうち、最大2つを実現できる。
分散データシステムが分断された時は、一貫性か、可用性のどちらか一つしか選べない。可用性を選ぶと、障害中には古いデータ結果が返される可能性がある。
速度層の実時間ビューのアーキテクチャの選択
・非同期更新(キューなどを挟む)
バッチ更新を実行できるので帯域が増加
変動する負荷にも対応
・同期更新
データベースに直接要求を発行し更新が処理するまでブロックする。
------------------------------------------
13.実時間ビュー詳説
Apache Cassandraを利用。
・カラムナ(列指向)データベース
・ソートマップを持つソートマップ
・入れ子になったマップで標準的な操作(追加、検索、範囲取得)が可能
・複合カラムにより、マップを任意の深さに入れ子にできる。
RandomPArtitioner:ハッシュマップの様にキーをクラスタ内のノードに均一に配置
OrderPreservingPartitioner:キーを順番に格納。ソートマップ。
------------------------------------------
14.キューイングとストリーム処理
耐障害性と再試行可能性が必要。
・単一コンシューマキュー設計:
キューからイベントを読み出す際、イベントをその場で削除しないという考え方に基づいている。イベント処理成功(ack)か失敗(fail)を通知してから削除を判断する。->複数アプリケーションで利用するには、キューのコピーが必要->キューサーバの負荷増大。
・複数コンシューマキュー設計:
イベントの消費済みと未消費状態をキューからアプリケーション自体に移す。ストリーム履歴の任意の時点からイベントストリームを再生する様要求できるようになる。-> Apache Kafka
キューとワーカーモデルは時代遅れ。
ストリーム処理
・逐次処理
タプルを一度に1つずつ処理するが、コードはクラスター間で並列動作する。->システムはスケーラブルになる。
Stormモデル:
->スパウト=ストリームの送出源。ボルト=任意数のストリームを受け取り、任意数のストリームを作成。ボルト自体は関数の実行、データのフィルタリング、合計の計算、ストリームの結合、データベースの更新などを行う。
->中間キューなしで実装できる。
・マイクロバッチ
16章後述
------------------------------------------
15.キューイングとストリーム処理詳説
Apache Stormでstormモデルを実装。
ZooKeeperでワーカーの割り当て場所と他のトポロジ設定情報を管理。
Apache Kafka(分散メッセージング), Apache Cassandra(カラムナデータベース)と連携。
・スパウト=Apache Kafka
・ストリームメッセージ処理=Apache Storm
・読み出し、更新用データベース=Apache Cassandra
------------------------------------------
16.マイクロバッチストリーム処理
逐次ストリームは障害中に最低一回の処理しか保証しない。
マイクロバッチにより、少しのレイテンシを犠牲にして、耐障害性のある正確さを手にすることができる。
逐次処理より高い帯域を実現できる。
・バッチローカル計算
バッチないだけで行われる保持されている状態に左右されない計算
・ステートフルな計算
グローバルカウントの更新、バッチをまたいだ状態を保持する計算
Apache Kafka:バッチが送信された時に、コンシューマは特定のバッチのどのパーティションからのどのオフセットかを覚えていることができる。バッチを再生する時は全く同じバッチを送信できる->トランザクションスパウト。
マイクロバッチストリーム処理計算もパイプ図を使って表すことができる。
------------------------------------------
17.マイクロバッチストリーム処理詳説
Apache StormのマイクロバッチAPIのTridentを使用。
------------------------------------------
18.ラムダアーキテクチャをより深く学ぶ
可変性(CRUD)には基本的に耐人為障害性がない。
唯一の解決策は中核データを不可変にすること。
バッチ層で索引付けビューを作成し、ビューに対するクエリを低レイテンシで解決する目的がある。
バッチ層のレイテンシを下げる方法として増分型にする選択肢もある。
増分と再計算の両者の長所を活かす方法として部分的再計算もある。
クエリ層:
バッチビューと実時間ビューを利用してクエリに答えること。
ラムダアーキテクチャ
・耐人為障害性
・水平スケーラビリティ
・低レイテンシ読み出し、更新
ビッグデータシステム構築が伝統的なアーキテクチャに基づいたシステムの構築よりずっと簡単。
------------------------------------------
以上。
へポスト