Zenn
🚗

Amazon Redshiftで集計した大量データをクライアント向け分析画面上で表示するためのアーキテクチャ設計

2025/02/21に公開
1

こんにちは!
ourly株式会社 執行役員CTO(@tigers_loveng)の相澤です。

弊社のプロダクトであるourlyの機能の一つとして「分析機能」があります。
主に社内報の閲覧状況やプロフィールの活用状況、サーベイの回答データなどの、プロダクトで収集した様々なデータを分析した結果を確認することができる機能です。

実は昨年4月に、その分析機能をフルリニューアルし、新しくデータ分析(集計)基盤として、Amazon Redshift(以降Redshift)を採用しました。

https://aws.amazon.com/jp/redshift/

それまではAurora PostgreSQLに貯めたログデータをリアルタイム集計していたため、分析画面の表示に時間がかかっていたり、初期リリース時に構築したデータ分析画面が微妙に使いづらかったり...といった問題を抱えておりました。

フルリニューアルプロジェクト開始時に分析基盤の設計をしている中で、Redshiftで集計したデータをどのようにクライアント向け分析画面に表示するのかが不明瞭なことが分かりました。(他にも色々ありましたが...)

ですが、調べてみても見つけられたのはRuby on Rails(以降Rails)とRedshiftをどのように接続するかという記事ばかりで、目的としてたアーキテクチャ設計の良い事例に辿り着けませんでした。

ということで、今回は弊社で議論、検討したアーキテクチャ設計の裏側と結局どうしたのかという話をしたいと思います。同じような要件を検討している方にとっての一例として、参考になれば幸いです。

なお、この記事では具体的なコードやどのような実装になっているかという話はせず、あくまでアーキテクチャ設計の話になります。

検討背景

まず、最初は、Redshiftって普通にRDSやAuroraのようなDBとして同じように扱えるんだっけ? というところからのスタートでした。

これは後から調べて分かったのですが、Redshiftを始めとしたデータウェアハウスサービスはOLAP(オンライン分析処理)には向いているが、OLTP(オンライントランザクション処理)には向いていないということが分かりました。

OLAPとOLTPについては以下AWSの記事が非常に分かりやすかったです。

https://aws.amazon.com/jp/compare/the-difference-between-olap-and-oltp/

設計当初はそこらへんはあまり分かっておらず、まずはRailsとRedshiftを繋いでクエリを実行してみてどの程度の時間、負荷がかかるかを調べるところからスタートしました。

検討内容

1. RailsのModelをRedshiftに直接つなぐ

まず真っ先に思いついたのは、ActiveRecordの接続先を特定のModelのみRedshiftに切り替えるという方法でした。

それができればそのまま Model.all のように取得できるので実装コスト & 学習コストの面で良さそうだなと考えた次第です。

幸い、複数DBをModelごとに切り替える方法についてはRailガイドで解説があったので特につまずくこともなく検証できました。

https://railsguides.jp/v7.2/active_record_multiple_databases.html

本番環境のDBはAurora PostgreSQLを採用しており、RedshiftもPostgreSQLベースで作られているため、いけるかなと思いましたが、実際に試してみると RedshiftとPostgreSQLの以下の違いによってうまく接続ができませんでした。

  • RedshiftはPostgreSQLをベースにしているとはいえ、サポートしているパラメータや機能が限られている
  • RailsのPostgreSQLアダプタが内部で SET client_min_messages TO ... などのコマンドを投げるが、Redshiftでは許可されていなかったりする※
  • そのため接続時にエラーが発生し、安定したデータ取得が難しい

こういった事情があり、ActiveRecordで想定通りに扱うのは設定が不足しているというより、「アダプタの作りこみやバージョン差異対応が必要」という結論になりました。

Redshift用のActiveRecordアダプタを導入する方法もありましたが、あまり活発に更新されていなかったり、ActiveRecordのバージョンごとに別のgemになっていたり(あと個人で開発されてるgemだったり)不安要素があったため見送りとしました。

2. pg gemで直接Redshiftにクエリを投げる

次に検討したのは、PostgreSQLのドライバ(pg gem)経由でRedshiftにクエリを送るという方法です。

これならActiveRecordのような細かい設定不要で、シンプルにSQLだけ投げる形なので比較的自由度が高いと考えました。

ただ、運用面では以下の懸念がありました。

Connection Poolの管理を自分でやらないといけない

ActiveRecordを経由しないので、コネクションプーリングの再接続やら接続数の制御やらを自前で気を配る必要があります。

Redshiftではあまり同時接続数を増やせないので、プールサイズを厳密に決める・アプリの接続設定を工夫するなど手間がかかる印象でした。

結果を一気に取得する時のメモリ負荷

大量のデータを1回のクエリで取ってきて全部メモリに載せてしまうと、Rails側がOOM(Out of Memory)を起こす可能性があります。

ストリーミング的に少しずつ取る手もありますが、実装コストが大きくなるのでこちらも見送りとしました。

そうこうしているうちに、やはりRedshiftを直接参照してリアルタイムでレスポンスを返すのは厳しいんじゃないか、という雰囲気になっていきました。

3. aws-sdk-redshiftdataapiserviceを利用する

最後に試したのは、Redshift Data API(いわゆる aws-sdk-redshiftdataapiservice gem)を使う方法です。

これはRedshiftに対してREST的にクエリを投げられる仕組みで、Lambdaなどサーバレス環境でも使いやすいのが特徴です。

ただし、こちらも同期処理には不向きでした。理由はクエリ結果の取得に、下記のようなフローを踏むからです。

  • ExecuteStatement APIでクエリを投げる
  • 返却されるクエリ実行IDを受け取り、しばらくステータスをポーリングして完了を待つ
  • 完了後にGetStatementResult APIで結果を取得する

APIの呼び出し自体が非同期前提なので、クエリ開始→結果取得まで最低でも数秒〜数十秒かかるケースがありました。

ユーザーがブラウザでサクサク分析結果を見たい時に、この待ち時間は許容できないだろう…ということでこちらも断念...

もちろんうまく設計すれば使い所はあるのですが、少なくとも今回の「リアルタイム性が求められるAPI応答」では噛み合いませんでした。

じゃあどうするの!?

ということで、発想を切り替えて 集計データをAuroraに入れてしまえば他のデータと同じように扱えるよね!? という一番シンプルな答えに辿り着きました。

実際、「集計された大量の履歴データ」はRedshiftに置いておき、そこから必要な分析結果だけをAuroraに移しておけば、APIサーバはAuroraを向いて高速にデータを返却できます。

Redshift → Auroraへのデータ同期フロー

データ量が多いため、ストリーミング的に同期するのではなく、一度Amazon S3(以降S3)にデータをUNLOADしてから同期するようにしました。

RedshiftでUNLOADクエリを発行

UNLOADはRedshiftで使用可能なデータをS3にエクスポートするための機能です。

UNLOAD ('SELECT * FROM aggregated_table')
TO 's3://${bucket_name}/${yyyymmdd_date}/'
IAM_ROLE 'arn:aws:iam::xxx:role/xxx'
FORMAT CSV
HEADER
GZIP
ALLOWOVERWRITE
PARALLEL OFF
;

上記のような感じで、Redshift内の集計データを圧縮CSVとしてS3へ出力します。データ形式はJSONやPARQUETなども選べたり、他のオプションで柔軟にエクスポート形式を変更できます。

バッチ処理でS3上のCSVをAuroraにロード

アップサートや重複排除などは、必要に応じてSQLレベルまたはアプリケーションロジックで実装しますが、今回はアーキテクチャ設計の話なので詳細は割愛します。

これで、Redshiftで集計を終えたデータが定期的にAuroraに格納され、クライアントからのリクエスト時はAPIサーバがAuroraを参照する形になります。

疎結合

このフローを選んだ理由の一つは、サービス間を疎結合にして保守運用をラクにする という狙いもありました。

データ集計 → データをS3にアンロード → アンロードしたデータのダウンロード → ダウンロードしたデータの登録 というように、明確な段階に分けたことで、どこでどんな問題が起きたかを特定しやすくしています。

特にバッチ処理で非同期で大量データを扱う場合には、影響範囲をなるべく小さくしたフローを組み合わせることで問題の特定と対処をいかに早めるかが大事だと考えています。

今後の改善案

  • 現状バッチサーバをEC2で動かしているが、それをStep Functionに移行する
    • → EC2のメンテナンスが不要になる
  • 画面内の検索条件がそこまで多くないので、検索条件ごとにファイルを生成してS3に置いておけばAuroraのデータ探索が不要になる
    • → データ登録処理が不要になるため、さらに疎結合にできる

まとめ

  • Redshiftは分析・集計に強みがあるが、同期的な大量クエリを捌く用途には向かない
  • ActiveRecordで直接アクセスしようとするとPostgreSQLとは違うパラメータ制約が多く苦戦する可能性あり
  • pg gemで直接投げたり、Data APIを使ったりする手もあるが、パフォーマンスや運用面で課題が残る
  • リアルタイム性を重視した閲覧用DBとしてはAurora PostgreSQLなどのOLTP向けDBを併用するのがベストプラクティス
  • Redshift → (UNLOAD) → S3 → (バッチ) → Aurora のフローでデータを同期し、疎結合のパイプラインにすることで保守性・可用性が高まる

これまでに試した方法をざっくりまとめると、「分析やバッチ集計はRedshift」「ユーザーへの応答はAurora」という使い分けがやはり王道でした。RedshiftをOLTP用途に使おうとすると、コネクションプール管理やクエリチューニングでなかなか大変なので、こうした設計が結果として一番安定すると感じています。

もし「分析結果をなるべく早く、かつ大量に返したいけど、Redshift直アクセスはちょっと…」と悩んでいる場合には、今回紹介したようなアーキテクチャが参考になれば幸いです。

それでは今回はこのへんで。最後まで読んでいただき、ありがとうございました!

1
ourly tech blog

Discussion

ログインするとコメントできます