EVENT REPORTイベントレポート
ヒカラボレポートとは、開催されたヒカラボにおいて、登壇者が伝えたい講演内容を記事としてまとめたものです。ご参加された方はもちろん、ヒカラボに興味があるという方も是非ご覧ください。
ヒカラボレポートとは、開催されたヒカラボにおいて、登壇者が伝えたい講演内容を記事としてまとめたものです。ご参加された方はもちろん、ヒカラボに興味があるという方も是非ご覧ください。
IoT時代の到来を受け、ますます人気を集めているNoSQLデータベース。RDBに替わる存在として、企業への活用が加速しています。
そんななか、今回は3月12日にインテリジェンス、トークノート、グリフォン3社のエンジニアを迎えて開催されたヒカ☆ラボ「NoSQL勉強会」の様子をレポート。
転職Webサービスの立ち上げ、ソーシャルアプリ開発、社内SNS開発という異なる立場から、RedisやNewSQLなど、NoSQLの活用法などについてお話いただきました。
講演者プロフィール
インテリジェンスの大谷と申します。新規事業開発チームで開発責任者を務めておりまして、新規事業で転職Webサービスの立ち上げを担当しています。
2015年からプロジェクトがスタートし、現在は一部機能をテスト運用中です。チーム構成はエンジニアが4名。新しい技術を積極的に取り入れていく方針を掲げて、プロジェクトを運営してます。
現在手がけているサービスでは、RedisやLevelDB、MariaDBを使って構築しています。今回は、サービスを検証したり構築したりするなかで判明したデータベースの特徴を始め、活用事例を共有していきたいと考えています。
では、具体的にどのような技術を使ってサービスを作っているかと言いますと、OSにはLinux、サーバーサイドの言語にはhack(PHPを拡張した言語)を採用しているほか、バッチ処理にはGo言語を、データベースにはMariaDBを「Galera Cluster」というクラスタ構成を用いて、管理しています。
さらに、NoSQLではRedisを採用。Redis Clusterでクラスタリングをしたり、LavelDBを使ってデータのキャッシュなどを行っています。
私たちが手がけるポータルの転職サービスでは、データを大きく3タイプに分けて扱うことにしています。
1. 更新のほとんどないマスタ系データ
2. 再作成が可能で、永続性が必須ではないデータ
3. 更新頻度が高く、永続性が必須のデータ
それぞれの特性は、次のようになります。
1. 更新のほとんどないマスタ系データは、想定のデータサイズが50MB、更新頻度が月1~2回程度と低く、参照頻度が非常に多いものが当てはまります。
2. 再作成が可能で、永続性が必須ではないデータとは、データサイズが二年後でも1GBくらいに収まるもののこと。更新頻度は中くらいで、一時間に数回程度のレベル。参照頻度も中くらいのものをカテゴライズしています。
3. 更新頻度が高く、永続性が必須のデータとは、データサイズがおよそ20GB以上、更新頻度が高く、参照も非常に多いものとなります。
具体的に私たちが作るサービスでは、それぞれ次のようなデータが該当します。
都道府県名や大学名、職種、資格など。ポイントは、データ増加のコントロールが可能であるということです。知らないうちに増加したりすることがないようなデータになります。
私たちのチームでは、1.のデータを「シンプルで高速に値を取得したい!」と考え、Webサーバーに置いたLevelDBで保持することにしています。LevelDBに対して、MariaDBからバッチで定期的にデータをロードすることにしています。
そのため、マスターそのものはMariaDBで管理し、それを定期的にウェブサーバーのほうでLevelDBへ応用するように設定しています。
例えばランキング集計や、基幹システムから連携させているデータなどです。元データがあり、それを集計したり、使いやすいようにしていただければと考えています。
先ほどお伝えした通り、合計データサイズ、更新頻度は中くらい(1時間に1階程度)。参照頻度も中くらいで、特定の場面で呼ばれます。また、データの増加について言えば、本当に緩やかに増えていき、どの程度まで増加するのかが予測可能できるものを言います。
2. については、柔軟にデータを参照できて、冗長性も持たせたいという目的から、Redisを選択しました。Redisでは、クラスタリング構成にして、メモリ容量を分散するようにしました。
また、クラスタリングをするときにはMaster-Slave構成を利用して、データが消えてしまうという事態を防ぐようにしているほか、MariaDBや基幹システムからバッチで定期的にデータをロードするように設定してます。この辺りの内容については、後ほどくわしくお伝えします。
例えばユーザーの会員情報や、ユーザー同士またはユーザと企業とのメッセージのやり取りのデータです。先ほどお伝えした通り、データサイズは20GB以上で、更新頻度と参照頻度が高く、増加も多いデータになります。
こういったデータは、消えてしまうと非常にまずいです。サービスの運営に影響が出る可能性が極めて高いため、冗長性を持たせて、安全にデータを保持するようにしています。
RDB(MariaDB)で保持するほか、クラスタリング構成にして、データの消失を防ぐように設定しています。さらに、各クラスタリングに関しては、プロキシサーバを使って、クエリを分散させるようにしてます。
サーバー構成については、次のようになっています。
ユーザがアクセスをすると、ロードバランサ経由でウェブサーバにアクセスし、そこにLevelDBとRedisが待っています。さらに、別のクラウドのバックアップを別のサーバに行う仕組みになっています。そして、ウェブサーバからDBAを使って分散させて、バッチサーバがデータベースとつなぐようになっています。
LevelDBについて簡単に説明すると、Googleが開発をして2011年に公開され、C++で書かれたオープンソース(BSDライセンス)になります。ChromeのローカルKVSとしても活用されています。
また、Key-Value型の軽量なデータストアであり、ウェブキャッシュなどの動作がとても速く、操作性に優れているほか、機能はとてもシンプルです。
さらに、データはキーでソートされ、「レベル」単位でデータサイズごとに階層化される仕組みになっています。あくまで単一サーバでの運用が想定されているため、単体での冗長化については考慮されていません。
Redisや、YS系列のように、ポートや認証も不要で、このディレクトリでスタートすると指定をすれば、そのまま使用できるようになります。
ソートについては、Put/Get/Deleteという非常にシンプルな3つのソースが実装されてます。Redisのように多彩な型があるわけではなく、単純にキーとバリューの組み合わせでデータを持っています。データは、ファイルシステムに圧縮して保存されるようになってます。
LevelDBは、結構色々なサービスに利用されています。特徴としては、アプリケーションの組み込やOSへの移植が非常に簡単にできるように設定をされていて、riakやFoundationDB、InfuluxDBなどの有名なDBの間では、バックエンドにLevelDBを使っていたり、または使用できるような設計になっていたりします。
2013年、FacebookがRocksDBを公開していますが、それもバックエンドにはLevelDBを使っています。では具体的に、LevelDBがどのように弊社サービスで活用されているかというと、次のようになります。
マスターデータをMariaDBで保持し、その先に3つのWebサーバがあります。それぞれがデータのバッチを持ち、ローカルのLevelDBにバッチからデータをロードし、Webアクセス時にはLevelDBを参照して値を返すように設定しています。
そして、定期的にマスタデータをDBのバッチにコピーして、各WebサーバのLevelDBに保存。さらにWebアクセス時には、LevelDBからデータを取得する仕組みです。DBにアクセスをしてマスターを持ってきたり、マスターファイルをみてツールを読み込んだりする場合に比べて、非常に高速に対応しています。
LevelDBはシンプルなKVSで、データを読み込むスピードが非常に速い。複数サーバからのアクセスや冗長化は単体では不可能であり、RDBで持っているデータのキャッシュとしては、使っていて非常に最適だと思われます。
Redisは、ネットワーク経由で利用可能なNoSQLです。単純なKVSではなく、5つの型を持っていて、データをセットで持てるようなものだと考えてください。
データをメモリで保持するために、たくさんのメモリ容量が必要になります。Master-Slave構成での冗長化が可能なプロダクトでもあります。
弊社では具体的にどうサービスに活用しているかと言うと、次のようになります。
まずはバッチを使い、MariaDB や基幹システムからデータを持ってきます。それを、Redisで管理している3台のWebサーバーにバッチでアップロード。それぞれをクラスタリングして、ユーザーがアクセスをするとWebサーバからRedisを呼び出すような形で、サーバ構成を組んでいます。
次は、Redisのクラスタリングにフォーカスします。クラスタリングとは、複数台のアクティブなサーバーでDBを構成する技術のことを言います。これにより、データの冗長性や処理の負荷分散が可能です。
また、サーバーの台数を増やすことによって、システムの拡張を柔軟に行うことができる反面、サーバーの台数を減らして運用することもできます。例えば、運用中にサーバーの数を5台から3台にする、などということも可能です。
Redis Clusterの特徴として挙げられるのが、まず複数台で分散してデータを持つということです。例えばクラスタリングの種類など、プロダクトによっては、同じデータを複数台のサーバに持ち寄ったりすることもありますが、Redis Clusterの場合は、完全にサーバが3台あったら、3台別々のデータを持って、運用する形になります。
また、ノードごとにMaster-Slaveの構成になっていて、障害が起きたら、SlaveがMasterに自動で昇格することで、データの冗長性を保つような仕組みになっています。
さらに、ノードを追加したり削除したりする場合、例えばサーバを3台から5台の構成に増やした場合でも、リシャーディング機能を使って3台分のデータを5台分のサーバに分散できます。
弊社では、このクラスターを次のように運用しています。
ユーザがLBでアクセスした際、「このキーとこの値で何とかしたい」といった具体でキーを渡します。すると、それぞれのキーに有効なサーバーの情報を返してくれます。そして、該当するサーバーにメールを送信し、そのサーバーから操作をしてほしいと処理分散をさせる流れになっています。
毎回、どのサーバーを使うのかを選んだり、同じキーでも何回か渡すと別のサーバーに切り替わる、などいったものではなく、キーとサーバーとの組み合わせを明確に決めるような仕組みがRedis Clusterには備わってます。
よりくわしく説明をすると、メールをする側はどのサーバーでも問題ありません。「サーバーを選んでこの作業をしてほしい」と命令を出すと、最初のサーバーを判別する形になります。
スロットという概念があり、0から16384のスロットを各サーバーに割り当てます。「このキーの場合にはこのスロットだ」というようにキーとスロットの組み合わせが決まっていて、そこに対して処理が施されます。そして処理した結果を元に戻す(値を返す)というような形になってます。
例えばサーバーが5台だったら、5台に0~16384の数字を割り当て、3台だったら3台に数字を割り当てるようにします。計算式もテキストの方で公開されていて、最近であれば、この値をウェブサーバーで持っていれば、「このキーに対してはどのサーバでいけばいいか」を判別できるようになっています。
どのサーバに行く必要があるのかという部分の処理を、命令を出すまで待つのは面倒ですし、整合性を保つためには複雑な仕組みを作らなければならない可能性が高くなります。そのため実際に行った組み合わせをして、どのスロットなのかを判別して命令をするような仕組みを採用しています。
Redis Clusterは、耐障害性やバックアップ性が高くなる仕組みです。また、状況に応じてスケールアウトが可能な反面、スケールを減らすことも可能になります。
さらに冗長化にはMaster-Slave構成が欠かせません。各サーバーにおいて、マスター同士でデータが重複しないので、冗長性を持たせるには、Master-Slave構成が必要になります。これによって今まで以上に、Redisの活用方法が広がることが考えられます。
最後に、MariaDBの活用方法についてお話します。
MariaDBについて簡単に説明すると、MySQLから派生したデータベースで、オリジナルコードの作者が開発を担当しています。OracleがSun Microsystemsを買収する前、Michael “Monty” Widenius氏がSunを離れ、ともにSunを離れたエンジニア仲間と会社を立ち上げ、現在も開発を行っています。現在※の最新版は10.0系となります。MySQLの機能に加え、多くの独自機能を実装しています。
※ 2015年3月12日時点
例えば、レプリケーションを並列できたり、ストレージエンジンが標準より多いなど。さらに、実行しているSQLに対して、Explainを見るなど、さまざまな機能が実装されてます。
そして、プログラムからMySQLとほとんど同じように利用ができるようなっているほか、MySQLの有償版に付属しているスレッドツールが、標準でMariaDBにも付属しています。また、これからお話するGalera Clusterという、クラスタリングの仕組みも提供しています。
では、Galera Clusterについてくわしくお話ししましょう。
Galera Clusterは、MariaDBを複数台でクラスタリングできる仕組みになります。複数台の構成なので耐障害性が高くなり、レプリケーションなどと比較すると、障害発生時にデータの不整合が起きにくいようになっています。
3台から構築できて、柔軟なスケールアウトが可能です。JavaやPHP、MySQLベースの技術をそのまま使うことができ、クラスター用に特別な作業をしなくても、そのままGalera Clusterにつないで操作を行うことができます。
そして、弊社ではSQLというよりプロキシを使っていますが、接続先の管理を必要としない状態で戻ってきます。Slaveには参照で、Masterには更新という形で設定するケースがありますが、そうした接続の管理はしておりません。
Galera Clusterの特徴としては、MySQL Clusterに比べて構築が非常に簡単になっていること。社内でドッツというサービスを一年以上運用しており、実はそこでもGalera Clusterを使っています。非常に構築がかんたんで、ノードの追加なども手軽に行えます。
ストレージエンジンはInnoDBとExtraDBを採用していて、ExtraDBはロシアの企業が作ったもの。Galera Clusterではそれら二つをサポートしています。また、auto incrementについてはMySQLの場合、1ずつ増えていきますが、Galera Clusterでは1ずつの増加ではなくなります。そのため注意が必要です。
弊社プロジェクトでも実際に3ずつ上がっていたりして、例えば「連番だと思っていたら、飛び飛びになっていた」などという事態も考えられますので、用心してください。
そして、さきほどRedis Clusterは、各ノードでデータを重複させないため、別のデータを持ち合っているとお話しましたが、Galera Clusterについては基本的に、全ノードが全件の全データを保持しています。
ノードを追加すると、そのタイミングで既存のノードのデータを全部分割するような形になっています。ノードで障害が発生すると、各ノードがお互いを見ていて、クラスターから切り離して処理をするような形になります。
弊社のサービスで実際どのように活用しているかといいますと、インターネット経由でユーザがウェブサーバにアクセスすると、MaxScaleというプロキシが処理を分散。それを通じてクラスター構成してるMariaDBにアクセスをするという仕組みです。
接続先の単位などについては全部MaxScaleが設定してくれますので、アプリケーションからまるで単一サーバーを操作するように、接続をして操作をすればいいようになっています。
MaxScaleは、データベースとアプリケーションを中継するプロキシサーバで、MariaDBに加えてMySQLにも使うことができます。例えば監視だったり、ロードバランシングも可能ですので、MySQLを使われている方はぜひ、チェックしていただくといいのではないでしょうか。
さらにSQLを解析して、MasterやSlaveに振り分けることも可能ですので、接続先を変えてこのクエリはMaster、このクエリはSlaveといったように入れるような感じだと思いますが、そこを自動化するうえで非常に便利だと考えられます。
MaxScaleは、Maria DBを手がけている企業が作っています。Galera Clusterを監視する仕組みを実装していて、今がクラスタリングの状態がどうかというところを確認してくれるので、非常に運用しやすいと言えるでしょう。
MariaDB(Galera Cluster)についてまとめると、複数台のサーバで冗長化を実現できるほか、簡単に構築できて、どのサーバにもRead/Writeが可能になります。そして、オンラインでサーバの追加削減が簡単に行えるので、非常に運用しやすく、監視にはMaxScaleが非常にオススメです。
最後に全体のまとめを行います。まずは、DBやNoSQLを適材適所で使い分けることが重要です。どれが優れていて、どれがダメかといったことではなく、それぞれにメインとなる部分はありますので、適材適所で使えることが重要でしょう。
そして、データがどういった性質のものであるのかを考えることで、どのプロダクトが最適かを判断しやすくなるはずです。また、クラスタリングを行うことで、例えば一台では実現できなかったことなど、柔軟な運用ができるようになるかと思います。
さらに、新規開発こそ、新しい技術に挑戦する大きなチャンスだということを申し上げておきたいと思います。
なお、弊社ではエンジニアを募集しています。興味がある方は、ぜひお声掛けください。
ありがとうございました。
グリフォンの川村猛と申します。弊社は、サイバーエージェントとGREEのジョイントベンチャーとして2013年2月に設立された会社になります。そちらでCTOとして勤めております。
サイバーエージェントとGREEの子会社になりますので、基本的にGREEプラットフォームでサービスを提供しています。主にブラウザゲームを手がけておりまして、「ミリオンブレイブ」を2013年9月に、「不良遊戯」を2014年の5月にリリースしています。
2015年3月末には、「ミリオンアーサーエクスタシス」をリリース予定です。本タイトルはスクウェア・エニックスさんとの共同開発でして、「ミリオンアーサーシリーズ」でGREEプラットフォームでは初のブラウザゲームになります。
「ミリオンアーサーシリーズ」についてはネイティブアプリだけではなく、他にもテレビ番組や漫画など、さまざまなコンテンツがリリースされていますが、「ミリオンアーサーエクスタシス」はそうしたシリーズの一つとなります。ご興味がある方はぜひチェックしてみてください。
弊社のシステム環境は、いわゆるLAMP環境です。KVSはRedis、memcachedを使用しています。
本日はRedisに関する弊社での事例をお話しします。
Redisサーバーについては現在、Master1台、Slave1台の構成で運用しています。プロセスは3プロセス起動しているのですが、結果1プロセスでほぼ間に合っている状態です。
サーバー自体は、4coreで20GB。Redisの場合、フェイルオーバーにSentinelを使用することが多いかと思いますが、弊社ではハートビートで監視し、フェイルオーバーを自前で行っています。
また、PHPからRedisへの接続についてはphpRedisを使用し、開発環境限定でphpRedisAdminなどのツールも使用しています。
弊社ではRedisをそんなに特別な使い方はしていません。先ほどご紹介した、「ミリオンブレイブ」という2013年9月リリースのアプリからRedisを使い始めました。それ以前はmemcachedやtokyo tyrantなどを使っていました。Redis導入時にKVSをRedisに一本化する事も考えたのですが、結果的にmemcachedと併用をすることにしました。
その理由としては、Redis導入にあたりベンチを取ったところ、弊社のテスト結果では、単純なKVSとして使用するのであれば、memcachedのパフォーマンスがRedisを上回っていたからです。そのため、無理に全部Redisにする必要はないと判断し、memcachedを残すことにしました。
Redisとmemcachedの使い分けについては、永続化したい、長期間保持したいものについては、Redisを。データベースの参照結果や一時的なトークンやセッションなどについては、memcachedを使用しています。
Redisでは、例えば1ヶ月や3ヶ月など、ある一定期間保持し、その後不要になったので消したいなどというデータを保存しています。MySQLでも対応が可能ですが、Redisが便利なポイントは、expire設定によって期限が来たら勝手にデータを消してくれることです。
MySQLの場合、データを物理削除するためにスクリプトを作ったり、データのボリュームが多かったりすると、サービスを一旦止める必要が出てきて工数が膨らんだりすることもありますが、Redisであればそうした煩わしさから開放されます。
Redisのデータタイプは多岐にわたります。弊社では、そのうち文字列型とハッシュ型、ソート済セット型のみを利用しています。
■文字列型
まず文字列型については、ごく単純なKVSとして使っています。例えば、弊社では初回挙動と呼んでいるんですが、「初めてこのページにアクセスした」「初めて何々をした」という場合に特別なページを表示させたり、アニメーションを再生したりする挙動があり、その初回フラグをRedisで管理しています。
また、特定の動作をしたとき、1回だけメッセージを表示させるアラートポップアップの表示についても、Redisを用いてフラグ管理を行っています。
さらに、実際にゲームをやったことのある方でないとピンと来ないかも知れませんが、カードバトル系のソーシャルゲームでは、あるカードをベースに、また別のカードを素材として消費して強化するという機能があります。ユーザーがどのカードをベースにしたのかという情報等をRedisに保存しています。
同じユーザーが時間をあけてまたアクセスしたときに、前回ベースとして選んだカードの情報が保持されているので、スムーズにゲームを進めることができます。そうした利便性向上にも、Redisが一役買っています。
■ハッシュ型
次にハッシュ型について説明します。もしRedisにハッシュ型とソート済セット型がなかったとしたら、Redisを使っていなかったかも知れません。そのくらい、この2つのデータ型は重宝しています。
ハッシュ型については、例えば各ギルドにおけるユーザー毎のデータ管理に使用しています。ギルドとは、ユーザー同士で作られた10~25人のチームのことです。そのギルドのIDをkeyにして複数のユーザーIDをfieldとし、それぞれに異なるデータを収めています。
そのなかで便利なポイントは、ユーザを指定して直接データを取ってこれるところです。例えばmemcachedで似たようなことをしようとすると、ギルドIDをキーにして、ユーザーID以下のデータを全て取得してから指定のユーザーデータを探さなければならないのですが、Redisの場合そうした作業は必要ありません。
■ソート済セット型・ソートが重いもの
ソート済セット型は、MySQLの場合にはソートが重いものに使用しています。例えば、バトル中の戦況履歴。弊社のゲームでは、最大25人対25人でユーザー同士がバトルをします。そのバトル中に、相手にどれくらいのダメージを与えたのかといった履歴を、ソート済セット型で扱っています。
活発なギルドの場合、25人対25人で一人当たり1秒に1~2回のアクセスがあるので、秒間で数十件の書き込みが発生します。
履歴の数が膨大になりますが、Redisを使えば、すでにソートされた状態でデータを取得できるので、とても簡単に実装が出来ます。
■バトルマッチング
バトルマッチングについては、なるべく強さの近いギルド同士を対戦させる必要があります。そのために、各ギルドの強さをスコアリングして一定の値をつけ、強い順にソートし、上から順に似た値のチーム同士をマッチングしていきます。ソート済セット型であれば、このスコアリング結果を低い負荷で簡単に取得することが出来ます。
■リアルタイムランキング
数年間ソーシャルゲームを作ってきましたが、これは結構感動した活用法です。自分がソーシャルゲームに関わり始めた5年ほど前は、いかにしてリアルタイムランキングを作るかということが勉強会のテーマになるくらいで、各社とも工夫して取り組んでいました。
RDBだと、数十万件、数百万件のデータをリアルタイムでソートして表示させることはなかなか厳しいものがあり、とても難易度の高い作業の一つでした。
しかし、Redisを使えば「今まで悩んでいたことって、一体なんだったんだろう?」と思うくらい工数も負荷もかけずに、リアルタイムランキングを作ることが可能です。本当に実装が簡単になりました。
■トランザクション
トランザクションについては、弊社ではまだあまり活用できておらず、今後、より積極的に活用していきたいと考えています。Redisのトランザクションは、RDBのそれとは違い、ロールバックではありません。累積コマンドキューの全実行、もしくは全破棄の形となります。
具体的な例で言うと、RDBの場合にはトランザクション開始からコミットをする前に、途中でアップデートしたデータにセレクトにかけた際、データがアップデートされた状態で取得されます。
しかしRedisの場合は、最後に全部実行する形なので、EXEC前だとアップデートされてないデータが取得されます。
現状あまり使っていないのですが、今後活用していきたいと考えています。
正直、恥ずかしいのものばかりなのですが、今回はトラブル事例を3つご紹介します。
まずbgsaveによるパフォーマンス低下です。Redisの永続化というのはメモリに乗せているデータが消えないということでなく、データをファイルに吐き出してバックアップを取り、再起動させた際にそのバックアップデータを復元させるということです。
bgsaveの際、Redisが持っているデータを丸ごとファイルに書き込むため、データ量が多くなれば多くなるほど、DISK I/O、CPUの負荷が上昇し、パフォーマンスが低下するという事態が発生していました。
例えば、先ほどお話したようなユーザー同士のバトル中、非常に高い頻度で読み込み/書き込みを行うため、パフォーマンスが低下しているとレスポンス速度が落ちてしまい、アプリが重くなります。そしてユーザーが満足な状態で遊べなくなってしまいます。
このbgsaveは、デフォルトでは一定時間一定件数のキーが更新された場合、例えば60秒間に1000件とか1万件のキーが更新された場合などに、自動で実行されるようになっています。
対策としては、master機でbgsaveの設定をOFFにし、bgsaveが自動実行されないようにしました。slave機の設定はそのまま、定期的にbgsaveを行うようセッティングしています。
maxmemoryとは、Redisの最大容量を決める設定のことです。その設定容量を超えたら、LRU設定に従い、古いキーや残りのexpireが短いデータから消去していくなど、さまざまな設定を行うことができます。
しかし、データが自動で消去されることにより、困ることもあります。例えば、一ヶ月間残さなければならないデータを勝手に消去されてしまったりしたら、アプリに不具合が出るかもしれません。そういった事象を避けるためmaxmemory=0にしていたのですが、データ設計と監視が甘かったせいでトラブルになりました。
グラフをご覧ください。これは実際に障害が起きたアプリではなく、別のアプリの例になりますが、一番上の赤い部分がスワップで、既にここで上限に達していたんですね。
スワップが発生してしまって、しかもそれを放置していて、そのうちスワップ領域も食いつぶして、容量オーバーで一切更新ができなくなってしまいました。
そのためRedisを使って保存しているデータが正常に更新されなくなってしまい、例えばバトルで戦っている際に「攻撃したのに攻撃したことになっていない」などといった不具合が発生しました。
対策として、まず各データのexpireを見直しました。すると、社内の一部のエンジニアで理解が浅かった者もいて、expireが未設定のデータが結構見つかりました。
もちろん、未設定のままでも良いものもあったのですが、不要なデータが相当量あり、まずはexpireの設定し直しと、不要データの削除を行いました。それにより数GBのデータ量を減らせましたが、依然容量が不足していたので、根本的にはサーバーのスケールアップで解決しました。
次に、zabbixで細かくアラートを設定しました。以前は、容量を食いつぶしてからアラートを投げられる設定にしていたのですが、それだと手遅れになるリスクもありました。具体的には、「メモリ容量の80%を超えました」や「swap領域の50%を超えました」などと、細かくアラートを設定することで、気づきやすい設計に変更しました。
さらにインフラチームの体制や業務フローの改善を行いました。障害が起きた当時、「ミリオンアーサーエクスタシス」のインフラ構築が佳境に入っていて、インフラエンジニアが非常に忙しい状況でした。当然、忙しいことは何の言い訳にもならないですが、監視の目が甘くなっていたのは事実です。
具体的には、単純にインフラチームのエンジニアの人数を増やしたり、業務フローを見直したりしました。
最後に紹介するのは、MySQLとの併用による実装ミスです。これも本当に格好悪いミスですね。Redisの更新後に、MySQLでロールバックが発生するケースでのトラブルになります。
Redisはトランザクションを使っておらず、しかしMySQLではトランザクションをはり、コミットする前にRedisの方はデータが更新されている仕組みになっていました。
Redisのデータが更新された後、MySQLでコミットされる前に、何かエラーが起きた事がありました。
すると、MySQLのほうはロールバックしているのですが、Redisは更新されたままですので、データが不整合な状態になりました。
対策としては、単純にコードを修正しました。MySQLへコミットされた後に、Redisのデータが更新されるようにしました。
ただしそういった実装上のルールで防ぐようなやり方ですと、ミスを防げるかどうかが属人的になってしまいます。
実装者が意識しなくてもミスが起きないようなシステムを目指すべきだと思っていますので、近い将来、Redisでもトランザクションを取り入れることによって、MySQLの更新タイミングとRedisの更新タイミングを特に意識しなくても問題なく実装出来る仕組みに変えていきたいと考えています。
最後に、弊社はサイバーエージェントのグループ会社です。グループ内にはゲーム開発に携わる会社が10社あまり存在し、さまざまな種類のゲームが作られています。
例えばリアルタイムのマルチユーザープレイのゲームでは、Redisのpub/subを用いて、それぞれのクライアントとのデータ同期を行っています。毎フレームRedisへ各クライアントのデータを突っ込み、10フレームごとに親クライアントのデータを各子クライアントへ渡し、ゲームのステータスを同期させる、というような使い方でリアルタイムを実現しています。
また、Redisを一部機能でメインDBとして使用しているゲームもあります。ロックもきちんとかけて実装しています。
その場合、RedisをメインにしたことでRedisにかかる負荷が非常に高くなり、サーバー台数とプロセス数の増加が顕著になって管理コストが膨らんでしまうなどという事態も発生しています。
以上、ソーシャルゲームでのRedis活用事例とトラブル事例のご紹介でした。
ご清聴ありがとうございました。
「トークノート」という、社内SNSを手がける企業でエンジニアをしております三浦と申します。
前職はグリーでソーシャルゲームを作っておりまして、2014年1月に入社いたしました。
トークノートはリアルタイム性を持ったコミュニケーションツールです。サーバサイドは主にPHPで書き、リアルタイム処理のところはNode.js、とRedisのpubsubを用いてチャット機能を実装しています。
アプリケーションに関しては、最近作っているサブシステム、例えば外部向けのAPIなどはGo言語で書いてみたり。日ごろから色んな言語を使って、楽しみながら作っています。
今回はデータストアについて、改めて考えてみました。
アジェンダは次のようになります。
RDBMSには、皆さんご存知のMariaDBやMySQLなど、さまざまな種類があります。基本的にどのシステムでも、メインのデータストアはRDBMSになります。
まず、RDBMSの特徴といえばSQLです。汎用的、かつ高機能の問い合わせ言語である SQLを使用できる。そしてACIDなトランザクションや、レプリケーションによる負荷分散、HAクラスタリングによって可用性も実現しています。
ここで、ACIDについて少しおさらいしましょう。
Wikipediaから引用すると、原子性(atomicity、不可分性)、一貫性(consistency)、独立性(isolation)、永続性(durability)の4つが、トランザクション処理の信頼性を保証するために求められる性質であるという考え方です。お察しの通り、ACIDとはそれら4つの性質の英語の頭文字を合わせて作られた単語です。
サービスがある程度成長をしてくると、レプリケーションの限界を考えなければなりません。master/slave方式では、slaveを増やせばreadの性能はアップするものの、更新に関してはmasterを増やすわけにはいかず、writeの性能を稼げないという状況が生まれます。
master/slave方式と同じですが、例えば、master1台、slave数台といった構成をワンセットずつ増やしていき、テーブルを分けていくようなことをすれば、writeの性能を稼ぐことはできます。
しかし、分散トランザクションを行う必要が出てきたり、サーバのコストが膨らんでしまったりと、さまざまな問題が発生するほか、マイグレーションコストについても考慮しなければなりません。
multi master方式については、よほど目的が無い限り、使わないでしょう。multi master方式では、マスタを増やせば増やすほど、運用コストが増大していきます。そしてアプリケーションの実装もどんどん難しくなります。
RDBの課題や特徴を一言でいうとすれば、スケールアウトをしづらいということに尽きます。
シャーディングによる水平分散は、よく用いられる手段の一つです。
しかし、懐かしのシャーディング原始時代、2000年代に入ったくらいには、レコードが何千万単位、また1億件などというレベルで大量にあった場合、次のように処理していました。
usersテーブルを分けて、別々のインスタンスを置いて、手動でユーザIDをふり、下一桁で分割してみる…といった具合です。これはもう極めて問題がある状態で、アプリケーション側の対応が必須になります。加えてジョインもできないですし、トランザクションも使えない状況です。
しかし、今は違います。本当に便利な時代になりました。
MySQL/MariaDBなら、Spiderというプラグインがあります。
Spiderは、シャーディングのサポートをしてくれるプラグインです。
ホストを横断したパーティショニング、いわゆるシャーディングですね。そしてパーティション単位でのレプリケーション。分散トランザクションについてもSpiderが面倒を見てくれます。
直接的には関係ないのですが、Handlersocketをプラグインに使うと、MySQLにも使うことができますし、スケールアウトをすれば、かなりパフォーマンスを稼げるのではないかと思われます。SpiderとHandlersocket、どちらのプラグインもMariaDBにバンドルされています。
スケールアウトをしやすいデータストアというのは、皆さんがほしいですよね。そこで、できたのがNoSQLです。
NoSQLの特徴については、まずさまざまなタイプがあるということ。
インメモリKVS系や分散DBMS系など、観点によって分類の仕方が異なります。これまで幾度となく、NoSQLについて色々な記事で書かれてきましたが、記事によって観点や分類の仕方が異なるケースが多く見られました。続いて高速なread/wite、そしてスケーラビティが挙げられます。
そして、インメモリKVSでのキャッシュです。MemcachedやRedisなど、既存のデータストアと協調することが前提になります。Redisについては永続化の機能もあるので、永続化のデータストアとしても使う可能性はありますが、メインはRDBです。サブがmencached、Redisの位置づけになるでしょう。
あとは分散DBMSでのスケールアウトです。これはMongoDB、HBase、Cassandraなど、スケーラブルな独立したデータストアと、RDBを置き換えることを目的として使われることがあります。
そんななか、気になってくるのが分散環境におけるトランザクションです。RDBMSで分散環境を実現する場合、ネックになるのはトランザクションです。分散トランザクションを実現するためには、2フェイズコミットを使います。これには原理的なデメリットが存在します。例えばコミット要求フェイズにおけるジャイアントロックのような状況のことです。
また、分散トランザクションを行うためには、きちんとしたトランザクションマネージャが必要になります。もしトランザクションマネージャを使わないと、失敗したトランザクションのリカバリなどの問題があります。
続いて2フェイズコミットの成功例をご紹介します。まずはコミット要求フェイズで、アプリケーション側がDBにコミットを要求します。そして各DBはコミット要求への合意のレスポンスを返します。次に、コミットフェイズです。コミットを確定し、各ノードがそれぞれ成功のレスポンスを返す。これが成功例です。
ブロックについては、コミットを要求してもあるノードのレスポンスが返っては来ず、そのまま待っていてタイムアウトになってしまうなどということも考えられます。
タイムアウトを1秒間に設定したとしても、非常に負荷が高いシステムで1秒間待つというのはかなりつらい。だから、2フェイズコミットは難しいと感じています。
昨今の分散DBMS系NoSQLプロダクトは、ACIDなトランザクションをはなから諦めています。さきほども見たように、分散トランザクションが致命傷になりかねない場合もあります。そもそも最初からトランザクションを諦める必要があると感じています。
さらに、CAP定理のPartition-toleranceを重視してもいいのではないでしょうか。
改めて、CAP定理とは何かを説明します。CAP定理では、ノード間のデータ複製において、同時に次の3つの保証を提供することはできないということが証明されています。
・一貫性(Consistency)
・可用性(Availability)
・分断耐性(Partition-tolerance)
CAP定理のマインド、BASEの部分については次の3つがあります。
・Basically Available(基本的に利用可能)
・Soft-State(柔軟な状態)
・Eventual Consistency(結果整合性)
そのなかでSoft-Stateとは、Cassandraでいうところのゴシッププロトコルの部分です。例えばあるノードを更新して、それを全体に行き渡らせるまでには時間かかりますが、結果的に行き渡らせることが可能な部分というのは柔軟な状態であるといえると思います。
さらに、時間はかかるものの、いずれデータの整合性を取れる状態だということがEventual Consistencyです。
で、Partition-toleranceというのは、ノードが分断されてもある程度システムが継続するような状態であることとなります。
Consistencyもさまざまで、次のようなものがあります。
・Strong Consistency
CAP定理のConsistencyはこれですね。データ更新後、必ずその更新後データを参照可能となります。
・Weak Consistency
データ更新後、必ずその更新後データを参照可能とは限りません。
・Eventual Consistency
結果整合性です。データ更新後、その複製が作成されるのに十分な時間が経過し、いずれそのデータは参照可能となります。
CAP定理とACIDとの関係について語るとき、CAP定理におけるConsistencyとAcidにおけるConsistencyを混同すると、かなり混乱しますのでご注意ください。
・CAP定理のConsistencyは、システムレベルでのお話です。
・ACIDのConsistencyは、トランザクションレベルでのお話です。
2つはレベルが異なります。ウェブなどの記事を見ていると、ここを混同しているもの