- Wafrisは、Railsミドルウェアクライアントを提供するオープンソースのWebアプリケーションファイアウォール企業である
- v1クライアントではローカルのRedisデータストアが必要だったが、v2ではSQLiteを使用する
- RedisからSQLiteへ移行することを決めた背景、性能面での考慮事項、アーキテクチャ変更について説明している
TL;DR
- SQLiteには得意なことと不得意なことがある
- Redisにも得意なことと不得意なことがある
- 従来型RDBMS(Postgres/MySQL)にも得意なことと不得意なことがある
- これらのデータストアはそのまま置き換え可能ではなく、そうしようとすると問題が起きる
- 本稿では、Redisベースのv1クライアントをSQLiteベースのv2クライアントへ再設計する中で行ったテストと意思決定の過程を説明する
変更を余儀なくした要因
- Wafrisの目標は、開発者が簡単にサイトを保護できるようにすることだった
- Redisのデプロイ上の問題により、v1はこの目標を完全には達成できなかった
- HerokuのようにRedisを簡単に使える環境で作業していたためRedisを選んだが、多くのユーザーがRedisのデプロイ問題に直面した
- Redisのような別個のDBを使わせることは、ユーザーのためになっていなかった
「速さ」とは何か?
- Redisは従来型RDBMSより「高速」だが、それでも接続、メモリ、プロセスなどを管理する必要がある
- クラウド環境ではネットワーク遅延が大きな問題になり得る
- 受信するHTTPリクエストごとにWafrisルールを評価する必要があるため、ネットワーク遅延がアプリケーションの速度を低下させる可能性がある
単一体(Monolith-ish)という前提
- 完全に分散したアプリケーションもあるが、ほとんどのRailsアプリは「壮麗なモノリス(Majestic Monoliths)」である
- 複数リージョンにデプロイされていたり、機能が重複するサーバー群に分割されていたり、一部しかRailsでないアプリでは、Redis利用時にさらに多くの問題が発生する
アーキテクチャの再考
- WafrisはRailsミドルウェアとしてインストールされるWebアプリケーションファイアウォールである
- 大きく2段階に分けると、1) HTTPリクエストをルールと照合し、2) 処理結果を報告する
- ルールの「読み取り」(第1段階)は「書き込み」(第2段階)よりはるかに重要である
- 読み取りは逐次的に処理される必要があり、失敗が許されず、ユーザー体感性能に影響する
- 書き込みは低速でもよく、バッチ処理や非同期化が可能である
SQLiteの登場
- SQLiteがどのような用途に適しているかについては、すでに他の人々がうまく説明している
- SQLiteはクライアント/サーバー型DBと競争しているのではなく、
fopen()と競争している
- ネットワーク往復をなくせば、Redisよりはるかに高速になると予想された
- SQLiteとRedisのベンチマーク評価を行うことにした
SQLiteとRedisのベンチマーク
- ベンチマークとは、精密な数字で自分をだます暗黒技術である
- データストアのベンチマークはさらに厄介である
- 絶対的な速度を求めるのではなく、自分たちのデータとユースケースに特化したベンチマークを作成した
- 最適化の小細工は無視した。Wafrisをアプリに入れればすぐに動作してほしいからである
- 理論上のベンチマークではなく、自分たちのアプリの主要経路と最悪のクエリをテストした
- IP範囲(IPv4、IPv6)をカテゴリにマッピングする複雑な「lexical decimal」データ構造への要求が最悪のクエリだった
- 範囲検索を事前計算し、SQLiteテーブルとRedisソート済みセットに書き込んだ
- 受信するHTTPリクエストごとに、リクエストIPを許可/ブロック用のカスタム範囲、GeoIP範囲、IPレピュテーション範囲と照合しなければならない
テストプロトコル
- M2 MacBook Airで、Homebrew経由でインストールしたRedisとローカルSQLite DBを使ってテストした
- 既存の範囲データセット(120万件)に対してテストした
- 複数のIPセットをSQLiteとRedisに同じ順序で実行した
- 各倍率ごとにテストを5回実行し、平均を取った
テスト結果
- SQLiteがRedisを圧倒した(自分たちの特殊なユースケースにおいて)
- SQLiteはローカルRedisインスタンスより約3倍高速だった
- これはネットワーク遅延を考慮する前の結果である
- たとえSQLiteがRedisと同等であっても、ネットワーク時間を排除できるぶん有利である
グラフに現れないこと
- ベンチマークでSQLiteの性能が2倍悪くても、実際にはネットワーク遅延のためより高速になり得る
- Redisサーバーをどれだけ強力にしても、ネットワーク帯域幅や接続などの限界があり、リージョン間遅延もある
- SQLiteなら「無料で」ほぼ無限の水平スケーリングが可能である
- SQLiteによりオンボーディングは大幅に改善される。ユーザーは使われていることにすら気づかないかもしれない
- Redisからさらに性能を引き出すことはできるが、ユーザーにRedis設定の変更を納得してもらうのは難しかった
結果は始まりにすぎない
- SQLiteがRedisより高速であることは証明できたが、現実にはトレードオフがある
- 上記テストでは書き込みを考慮していない
- 読み取りと書き込みの競合を管理するには、DBへの接続、コネクションプール、トランザクションなどが必要になる
- ちょうど電動スーパーカーがコンクリートブロックを運ぶのに向かないように、SQLiteを不適切な役割に使うべきではない
同期アーキテクチャの構築
- v1(Redis)では、ユーザーがWafris Hubでルールを更新すると、Redisデータストア内のルールも更新されていた
- SQLiteではWebサーバーへ「プッシュ」できないため、この方式は機能しない
- v2(SQLite)では、1) ユーザーがWafris Hubでルールを更新する 2) クライアントが定期的に更新済みルールを確認する 3) ルールが更新されていれば、完全に新しいSQLite DBをダウンロードする
- これにより、ユーザーのインストールと設定に関する責任は大幅に軽減された
- v2クライアントのインストール成功率は3倍に増加した
SQLiteの分散アーキテクチャ
- オートスケーリングが有効なクラウドプロバイダーにデプロイされたRailsアプリを考えてみよう
- リクエストが100req/sから10,000req/sに増加すると、コンピューティングインスタンスはスケールするが、DBはそうではない
- これは実際に、Railsアプリが過負荷で停止する主な理由である
- SQLite DBを各コンピューティングインスタンスに同期すれば、すべての呼び出しをローカルに保てるため、この問題を解決できる
書き込みはどうするのか?
- アプリを読み取り(ルール評価)経路と書き込み(報告)経路に分割し、そのうえで書き込み経路は無視した
- 書き込み経路は、1) Wafris Hubへ非同期に接続して報告する 2) レポートをバッチ送信する 3) クライアント側でのDB書き込みを完全に排除する、という形に再設計した
- これは他の人には合わないかもしれないが、私たちはデプロイが簡単で高速なWafrisクライアントを求めるユーザーだけを重視している
結論
- SQLiteを使うv2アーキテクチャに非常に満足している
- すでに多くのサイトが攻撃に耐え、オンライン状態を維持する助けになっている
- 導入開始がはるかに容易になり、私たちのサポート作業とユーザーの手間が減った
- これは、より安全でセキュアなインターネットに向けた勝利だと考えている
7件のコメント
SQLiteは十分に優秀ですが、このケースはどちらかというと、単にRedisに向いていないユースケースだったのでは……という気がしますね。
ベンチマークをM2でやったというのはちょっと…
では、AWSインスタンスごとに全部計測しないといけないんですか? オープンソースに求めるものが多すぎますね。
同じサーバー環境で行ったのですが、問題になりますか?
ベンチマークでは特定のCPUを使うべきなのでしょうか…?
M2で行ったことの何が問題になり得るのでしょうか?(実際のサービス環境がM2プロセッサではない点以外で)
それが問題なんです。ラボで実験しておいて、「これは商用向けに完璧だ!」と主張すること。