
はじめに
はじめまして!DMMのML基盤チームの上田亮です。 2023年に新卒として入社し、検索やレコメンドのインフラ基盤の開発に携わっています。
私たちのチームでは、DMMの数百万を超えるコンテンツを情報検索や機械学習を使ってユーザーにパーソナライズして届けるための検索・レコメンド基盤を構築しています。
基盤は主にAPIとバッチの用途で2つのKubernetesクラスタ(EKS) を軸としたAWS環境で構成されています。バッチ基盤では、DMMグループ全体で利用しているデータ分析基盤からデータの抽出・加工・機械学習などを適用し、 その結果をデータストレージに定期的に書き込むバッチジョブを実行しています。API基盤では、ユーザーリクエストに対しデータストレージから取り出したデータをもとにレスポンスを返すWeb APIサーバを提供しています。
現在、データ分析基盤がAWSからGCPへ移行したことに伴い、バッチ基盤をGCPに移行する取り組みを行っています。新しいデータ分析基盤ではDWHとしてBigQueryを利用しており、バッチジョブで必要な機械学習の特徴量作成などのデータ加工を、分散処理によって高速で行うことができます。本記事では、一例として、テキストの前処理においてよく用いられる形態素解析をBigQuery Remote Functionsで実装した方法を紹介しようと思います。
Remote Functionsについて
BigQuery Remote Functionsは、Cloud FunctionsやCloud RunをBigQueryのSQLの関数として呼び出す機能です。任意の言語・ライブラリを使って関数を実行できるため、SQLだけでは難しい柔軟な処理が可能になります。
テキストデータの分かち書きにBigQuery Remote Functionsを採用した理由としては、バッチに必要なデータは全てBigQueryに保存されていることから、データ処理のインターフェイスをできるだけSQLに統一し、実装をシンプルにするためです。これにより、BigQueryの大量のコンピュートリソースの恩恵を受けることもできます。また、頻繁に使用する形態素解析処理を共通の関数にすることで、一元管理する目的もあります。
Cloud RunによるAPIの実装
形態素解析エンジンには、MeCabを用いました。APIの実装には、MeCabとNEologd辞書をインストールしたコンテナイメージを利用するために、Cloud Runを採用し、Python、FastAPIを用いて実装しました。
工夫した点は、任意の単語を含むユーザー辞書を利用可能にしたことです。これは、NEologd辞書ではカバーできない、弊社のサービスに特有の表現を含む重要な語彙を抽出する必要があったためです。Remote functionsへのリクエストには、user_defined_context というユーザー定義のcontextを設定することで、Cloud Runの挙動を動的に制御することができます。今回の実装では、user_defined_context にユーザー辞書に登録したい単語リストを設定できるようにしました。弊社では動画や電子書籍など多くのサービスを抱えていますが、それぞれのサービスで特有の単語が異なります。そのため、同一のエンドポイントを参照しつつ、ユーザー辞書ごとに異なるRemote Functionsを作成する運用にしています。
リクエスト例: BigQueryからRemote Functionsが呼ばれると、callsにBigQueryのレコードに対応する文字列の配列が入ってきます。
{
"requestId": "124ab1c",
"caller": "//bigquery.googleapis.com/projects/myproject/jobs/myproject:US.bquxjob_5b4c112c_17961fafea",
"sessionUser": "test-user@test-company.com",
"userDefinedContext": {
"userDictWords": "A/Bテスト,最近傍探索",
},
"calls": [
["近似最近傍探索によるベクトル検索エンジン"],
["検索改善を支えるA/Bテストインフラアーキテクチャ"]
]
}
レスポンス例: 形態素解析の結果をrepliesに配列として格納して返すことで、BigQueryのレコードとして受け取ることができます。
{
"replies": [
["近似\t名詞,サ変接続,*,*,*,*,近似,キンジ,キンジ","最近傍探索\t名詞,一般,*,*,*,*,*,*,*,ユーザー辞書","による\t助詞,格助詞,連語,*,*,*,による,ニヨル,ニヨル","ベクトル\t名詞,一般,*,*,*,*,ベクトル,ベクトル,ベクトル","検索エンジン\t名詞,固有名詞,一般,*,*,*,検索エンジン,ケンサクエンジン,ケンサクエンジン","EOS",""],
["検索\t名詞,サ変接続,*,*,*,*,検索,ケンサク,ケンサク","改善\t名詞,サ変接続,*,*,*,*,改善,カイゼン,カイゼン","を\t助詞,格助詞,一般,*,*,*,を,ヲ,ヲ","支える\t動詞,自立,*,*,一段,基本形,支える,ササエル,ササエル","A/Bテスト\t名詞,一般,*,*,*,*,*,*,*,ユーザー辞書","インフラ\t名詞,一般,*,*,*,*,インフラ,インフラ,インフラ","アーキテクチャ\t名詞,一般,*,*,*,*,アーキテクチャ,アーキテクチャ,アーキテクチャ","EOS",""]
}
Remote Functionsの作成
Remote FunctionsはBigQueryのUDFと同じような方法で作成することができます。TerraformのGCP provider version 5.19.0から、google_bigquery_routineリソースでRemote Functionsがサポートされるようになりました。これにより、以下のようにRemote FunctionsのリソースをTerraform管理することができます。ユーザー辞書として利用したい単語は、単語をカンマ区切りの文字列としてuser_defined_contextに設定します。
resource "google_bigquery_routine" "remote_functions" {
dataset_id = ${google_bigquery_dataset.this.dataset_id}
routine_id = "wakati"
routine_type = "SCALAR_FUNCTION"
arguments {
name = "sentence"
data_type = jsonencode({
typeKind : "STRING"
})
}
return_type = "{\"typeKind\" : \"JSON\"}"
remote_function_options {
endpoint = data.google_cloud_run_service.mecab_api.status[0].url
connection = "projects/${var.project_id}/locations/us/connections/cloud-run"
user_defined_context = { "user_dict_words" : "noun1,noun2" }
}
}
実行
実際に稼働中のデータパイプラインで、約30万レコード、合計1.2億文字のテキストデータに対し形態素解析処理を行ってみました。
Cloud Runのリソースは以下のように設定しています。
- CPU: 2vCPU
- メモリ: 4GiB
- インスタンスあたりの最大同時リクエスト数:20
Cloud Runのアクティブなコンテナインスタンス数の推移を表したグラフを以下に示します。最大で一時的に32インスタンスまでスケールアウトしていることがわかります。クエリの実行は、わずか5分程度で完了しました。Cloud Runの最小インスタンス数は0に設定しているため、コールドスタートを考慮しても高速に処理できているのではないでしょうか。

まとめ
今回は、BigQuery Remote Functionsを使用した形態素解析の事例について紹介しました。データの前処理をBigQueryに統一しつつ、複雑な前処理を行うことが可能になりました。簡単に作成することができ、想定していたよりも処理速度が速かったので、共通化できそうな他の処理もRemote Functionsとして切り出していきたいです。
本記事の内容は、私たちML基盤チームが日々行っている取り組みのほんの一部です。機会があれば、基盤の全体構成についても詳細をお伝えしたいと思います!