エンジニアこんにちは、BOOTHチームでサーバサイドエンジニアをやっているRNDです。BigQueryを使った話を書きます。「あなたにおすすめのショップ」という機能をリリースしました。トップページに、「あなたにおすすめのショップ」としてユーザーごとにレコメンドされたショップが表示されます。ユーザーがイラストコミュニケーションサービス「pixiv」でフォローしているクリエイターのショップを表示する仕様になっています。ユーザーが興味を持っていることが明らかな「pixivでフォローしているクリエイターのショップ」を確実に表示することを優先したためこのような仕様になっています。その代わり、将来的にアルゴリズムの変更に柔軟に対応できるようレコメンドの基盤の構築に主眼をおいて開発を進めました。今回の記事ではこの「あなたにおすすめショップ」の基盤となる仕組みについて紹介します。BOOTHのデータとpixivのデータの双方に依存しています。これを実現するにはBOOTHとpixivのサービス間をまたいだデータ連携の仕組みが必要です。pixiv側にフォロー情報を取得するAPIを生やしてそれを利用する方法がまず考えられます。この方法はBOOTHからpixivへ大量のAPIアクセスが発生するため、pixiv側の負荷を気にする必要があるレコメンドの計算をBOOTHのRailsアプリケーション上で行う必要があり負荷を気にしないといけないpixiv側の他のデータをおすすめに使いたくなった時はAPIアクセスがどんどん複雑になってしまいレコメンドのアルゴリズムの変更が容易でない等、保守性について考えることが多く採用を見送りました。今回、上記の問題を解決してサービス間データ連携を行うためにBigQueryを用いました。BigQuery上のデータセットにアップロードされ同期される仕組みを運用しています。そのためpixivのデータもBOOTHのデータもBigQuery上からアクセスすることができます。BigQueryを用いた方法では以下の3つのバッチスクリプトを順に実行することでレコメンドを実現します。BOOTHのDBに入っているデータを定期的にBigQueryにuploadするBigQueryでレコメンド結果を計算し、テーブルに保存するクエリを実行するBigQuery上のレコメンド結果のテーブルをBOOTHのDBに読み込むこのうち、1は既に実現できていたので今回のリリースでは2と3を実装しました。BigQuery連携案この方法ではBoothからpixivへのAPIアクセスは発生せず、APIアクセスはBOOTHからGCP(BigQuery)に対して行われるためpixiv側の負荷を気にしなくてもよいレコメンドの計算はGCP上で行われるため負荷はそこまで気にしなくてもよいレコメンドのアルゴリズムを変更したい時はレコメンドの計算のSQLを書き換えるだけでよいBOOTH側ではBigQuery上のおすすめショップを読み込む時の負荷だけを気にすればよいと、考えることが減ります。レコメンドの知見がサービスを横断して流用しやすいこともメリットの一つにありました。データのサイズが大きいことが課題になりました。BOOTHのユーザー数もpixivほどではないにしてもかなり多いので、すべてのユーザーに対しておすすめショップを算出・保存するのは経済的ではありません。ディスク容量の削減とBigQueryからテーブルを読み込む際の負荷について考えなければいけません。Rails(5系)、MySQL)で気をつけたことについて書きます。おすすめショップのIDのリストをシリアライズして一つのカラムにバイナリ列で保存しました。今回は要件とデータ容量のコストを鑑みてこの方法を採用するのが合理的と判断しました。おすすめショップのスキーマわかりやすいように以下にmigrationのサンプルを載せます。3]BigQueryから一度に読み込むレコードの数を制限する前述したように、DBの容量だけでなくBigQueryからデータを読み込む際の負荷も課題となります。BigQueryからレコードを読み込みDBに保存するバッチを書きました。BigQueryを扱うためのライブラリです。google-cloud-bigqueryを利用して大量のレコードの読み込む時の方法は以下のような感じです。'認証キー(json)のPATH'endgoogle-cloud-bigqueryを使ってテーブルを分割して読み込む以下にサンプルコードを示します。BQ上のレコードにアクセスするためのオブジェクトで取得できます。ここでmaxを指定しないと毎回10万件ずつレコードを読み込んでしまい、メモリを圧迫します。ページネーションは以下の書き方で行います。現在のページトークンを取得するおすすめショップでは、Jobのリトライ時に途中から読み込めるように1ページinsertし終わるごとにページトークンを取得しRedisに保存しています。書き込み負荷を考慮してinsertする1レコードのinsertごとにクエリを発行するとユーザー数分の書き込みが発生してしまうので、数百~数千recordを1回のクエリでまとめてinsertします。5以前ならactive-record-importを使用するなどですね。ショップが非公開になっていないかなどの条件によってフィルタリングされています。対象のショップが除外されるまでに時間差が生じます。Railsアプリケーション側でも対象となるショップのフィルタリング処理を行っています。以下に、モデルのサンプルコードを書いておきます。pixivのフォロー状況を即座に反映するようなことができません。いった用途で使いたい場合は別の仕組みが必要になります。BOOTH側のコードだけを見てもレコメンドがわからん、という感じになります。pixiv側のデータの仕様が変わったりした場合壊れる可能性があります。この辺は何か上手い運用の仕方を考える必要がありますね。この仕組みを使うのは厳しいです。「あなたにおすすめショップ」はまだまだ成長途中の機能です。今後BOOTH内のデータに基づいた推薦等も反映したいという話が進行しています。今回の仕組みに載せることでBOOTHのRailsアプリケーション/DBの負荷を考えなくてよいアルゴリズムの変更が容易(BigQueryを用いている)他サービスのレコメンドの知見が流用できるといったメリットを享受することが出来ます。私は業務経験ゼロで負荷対策なんもわからん/BigQueryなんもわからんという状態でBOOTHチームにジョインしましたが様々な部署のメンバーと相談しながら進め、おすすめショップをより良い形で実現することができ、圧倒的成長を得ることができました。)BigQueryについて無限にお世話になった。)BigQueryを使ったレコメンドの仕組みやテーブル構造等の相談で無限にお世話になった。Rails系サービス互助会(ピクシブ内のRailsを使っているプロダクトのメンバーが技術について共有・相談する集まり)Railsの書き方全般で無限にお世話になった。負荷テストやバッチの負荷軽減で無限にお世話になった。BOOTHチームメンバーとの相談すべてにおいて無限にお世話になった。フランクに他部署の人に相談できるところもピクシブの魅力です。いう感じに、採用に繋がりそうないい話をして締めとさせていただきます。recruit.jobcan.jpRND2019年4月新卒入社。クリエイター事業部所属。BOOTHでサーバサイドエンジニアをやっています。絵を描くのとボードゲームが趣味。»common.min.js.html。