メルカリの写真検索

澁井雄介氏:メルカリの渋井と申します。「メルカリ写真検索1年の歩み」という題でお話しします。

まず自己紹介をします。メルカリでAIチームの写真検索とEdge AIを兼務していまして、基盤だったりMLだったりいろいろなことをやっているエンジニアです。最近は、ARで遊んでいたりします。

メルカリは、C to Cのフリマアプリでして、お客様が売りたいものを売ることもできれば、売られているものを買うこともできます。写真検索は、買いたいものを買うための機能で、売られているものを、画像から見た目で探します。私の飼い猫を写真検索に掛けてみると右の通りでして、猫グッズとか犬グッズが検索されます。

本日は、写真検索の仕組みが生まれたときから、安定化させて発展するまでの3段階でお話しします。

写真検索なんですけど、実は、先週1歳の誕生日を迎えました。2019年3月18日生まれです。(オンライン開催なので)会場の反応がわからないのですが、写真検索をメルカリのユーザーがどれくらい使ってくれているのかというイメージは、こちらの動画にある通りです。

例えばWebで探していて、スクリーンショットを撮ってそれを検索する。こんな感じで検索できますし、または道端を歩いていて「いいなぁ」と思った商品の写真を撮って検索することもできるサービスになっています。

写真検索のアルゴリズム

まずは写真検索のアルゴリズム的な部分を説明します。メルカリのアプリから写真を撮ると、それが(スライドの)右から入力画像として入ってきます。多段階の機械学習や探索を掛けていまして、入力画像から物体検出をして被写体をもってきます。先ほど(同イベントで話していた)BASEさんが話していたのと近い仕組みだと思います。

被写体を特徴量抽出して特徴量ベクトルに変換します。MobileNet v2のfeature extractionです。今日たぶん出てくるのが3回目のfaissを使って、既にベクトル化されている既存の商品のインデックスから似たものを探してきてメルカリのアプリに返す仕組みになっています。

今日はシステム回りの話を中心にしたいと思います。この仕組みのアルゴリズムは先ほど申し上げた通りなんですが、システム的にどう作っているのかというと、AWSとGCPのマルチクラウド、マルチKubernetesの構成にしています。メルカリに出品された画像は全部S3に入りますので、そこから画像をもってきて特徴量抽出をして、というfaissのインデックスを作る部分をAWS側で作っています。

その一方で、メルカリのバックエンドの仕組み自体がGCPのGKEを中心に構成されているので、ユーザーからのエンドポイントをGKEに作るという構成になっています。

それぞれについてもう少し深堀りしていくと…。バッチ処理。AWSで動いているのはKubernetesのEKSで、コンテナ・ベース・パイプラインというバッチ処理を作っています。S3以外はほぼ全部Kubernetesで動いていて、cronジョブで1時間ごと、1日ごと、毎月、バッチジョブが稼働しています。

その1時間にアップロードされた画像をダウンロードしてきて、MobileNet v2で特徴量抽出をしてfaissのインデックス化をして、そのインデックス化されたものをGCPにアップロードすると。それぞれが別々のKubernetesジョブを通して稼働していくという、コンテナを1ジョブ1個立てていくという構成にしています。

最後に、作られたインデックスをGCPのストレージに上げて、コンテナレジストリでDockerイメージにしてビルドしていくという流れにしています。できあがったDockerイメージをGCPのGKEのほうに1時間ごと、毎日、毎月新たにデプロイするという構成にしています。

サービスの全体像

推論する方、サービスとして提供する方についてです。こちらのページの全体像なんですけど、4種類のデプロイメントで構成されていまして、RESTが外から叩かれる部分、つまりエンドポイントになっている部分で、RESTにアクセスしてきたあとに3段階の推論を投げています。

最初に「こういう流れで検索していますよ」と話しましたが、それをシステム的にはこうしているということです。Object DetectionとFeature extractionは一緒です。両方ともTensorFlowサービング(以下、TFサービング)として動かしていまして、Feature extractionしたものをfaissのインデックスから似た画像の商品を探してくるようになっています。

こちらのインデックスについて、「毎時間追加される」と申し上げましたけど、別々のポッドがKubernetesに追加されていくので、その追加されていくポッドとサービスを検索するために、RESTの中にサービスディスカバリーという、新しいものが追加されたらアクセス先に追加する機能を用意して、追加されてもすぐに検索対象にできるようにしています。

システムの安定化

これがシステムの全体像なのですが、複数のクラウドや複数のKubernetesにまたがったバッチとオンラインがある仕組みで、それなりに複雑な仕組みで、リリース当初はだいぶ不安定でした。

写真検索の開始当初は、ちょうど1年前です。1年前のサービスレベルはこちらの通りです。利用頻度が1秒間に1リクエスト程度、弊社のテキスト側のサーチ、写真ではなくテキスト側のサーチが、毎秒数千リクエストいくので、その数千分の1の中で、アラートが平均して週に2回ぐらい鳴るし、全断も3回経験しましたし、コストもだいぶ高かった。

さらに一番重要なのが、私がオンコールのときには絶対に鳴ってたという、本当に夜泣きするようなサービスでした。これを安定化させていったのが、去年1年がんばってきたことなんです。

障害がどこで起こっていたかというと、全般的なコンポーネントでした。ただ、リリースした当初は、そもそもシステム的に監視をしてアラートを飛ばしていたのがRESTの部分だけでして……。「スケジュールに間に合わせて出す」のと「運用機能が犠牲になる」のは開発するときにはよくあることだと思うんですけど、その一例だったサービスです。

ログであるとかリトライ監視がいろいろと不十分だったので、まずはREST以外の部分のTFサービングであるとかインデックスの数であるとか、ジョブのリトライ、ログの精査などの運用改善をやってきました。

それでログも出るしジョブパッチもリトライするしと、それなりに安定したなと思った矢先に、リトライされないジョブが発見されたこともありました。

毎時間のジョブがインデックスの生成をしているんですけど、その1バッチジョブ自体は1時間も掛からず数分で終わる程度の重さです。ただ、それがいつの間にかインデックスの数が足りないなと思って見てみると、なぜか数日掛かっているようなインデックスがある。

そこでDockerコンテナの中に入って見てみると、メインスレッドが落ちていてもサブスレッドが落ちていなくて結局リトライもされない状況になっていたので、そこを含めて自動的に落とすことをやったりしました。

コストの削減

コストについてです。インデックスを自動的に1時間ごと、1日ごと、毎月追加していくので、検索対象を漏らさないということでやっていたんですけど、ちゃんと消さなかったこともあり、古いインデックスは、追加されれば追加されるほどGCP、Googleさんにお金を払うことになっていました。

サービスとしてはちゃんと動いてるけれども、運用として漏れていたもので、RESTの中で古いインデックスの時間を取ってきてKubernetesのAPIを叩いてデリートするものを作って、コストを削減しています。Kubernetesだと、ノードのオートスケーラで、どんどん増やせば増やすほどお金が掛かっていくので、その辺をちゃんとやらなきゃなというものです。

全断への対処

全断なんですが、たぶんWebでサーバ管理をしたことがある人は、人生で1回ぐらいやると思います。証明書の期限切れが一例でした。証明書自体はLet's Encryptで発行していて、かつIstioを導入していたのでサートマネージャ(注:cert-manager。Istioの証明書管理機能)は入っていたんですけど、そのバージョンが古くて、本来だったら自動アップデートするはずのものが自動アップデートされず、それに気がつかずに全断されました。

ここにあるグラフが実際のそのときの全断のグラフです。そのときにはリクエスト数が一定時間0だったらアラートを飛ばすというのを入れていたので、ちゃんと気づけました。全断があったとしても回復できるようになっていて、ここが運用のほうでも改善してきた経験があります。

写真検索の仕組みは複数のクラウド・複数のKubernetesにまたがって、技術的にはそれなりに難しいことをやりつつ、作ったものとしてはおもしろいサービスだったんですけど、安定させるのがいろいろと苦労したなということを共有したかったのが今日の話です。

その甲斐あって、当初は毎秒1リクエストだった利用頻度は、毎秒25リクエストぐらいに増えていますし、アラートの頻度も月に1回ぐらい。全断も今年に入ってからは存在しないし、社外秘なんですけどコストも半額ぐらいになっています。写真検索という、出した当初はあまり使われなくて困ったなと思っていたものも、ちゃんと成長してくれて「かわいいやつだなぁ」と思っています。

ただし、なぜか私がオンコール担当の日は相変わらずアラートが鳴るので、「気にしてほしいんだろうなぁ」と、本当にかわいいやつだと思っています。

写真検索の発展

写真検索は、写真から何かを探すのはユーザーインタフェースとしては思いつきはするけど、新しい仕組みだと思うんですよ。ディープラーニングであるとか、faissのような仕組みがちゃんと揃って、かつ安定していないとサービスとして作ることはやっぱり難しいですし、それをアプリの中に組み込んで使っていただくのは、ユーザーとしては新しい体験になると思っています。

その一方で、新しい体験に慣れるのは時間が掛かるものなんですけど、「写真から何かを探す」「写真を入力として似たものを探す」というのは、別にアプリの中で検索ボタンを押して動かす必要はないと思いまして。それが発展編です。

今の写真検索は、写真からものを探して見つけたものを返してくるという、検索ボタン以外でもいろいろと使われています。1つの例が、既に売れてしまった商品を検索するものです。「売られちゃったね」というときは、似たものを探すボタンを用意して、そこから今販売中のものを探すこともできるようになっています。

もう1つ。これは私の好きな機能なんですけど、「売れるかチェック」というものがあります。「売れるかチェック」ボタンを押すとカメラの画面が起動して、写真を撮るとそれと似たものがどれくらい売られているのか、どれくらいの価格で売られているのか、売り切れているのは何パーセントなのかを出すようになっています。

メルカリで何かを売りたいというときに、どれくらい売れそうなのかはやっぱり気になるし、値段を探しにくる方は、やっぱり多いので、それをアドバイスするものになっています。

最後はちょっと内緒なんですが、先ほど(3月24日)リリースした機能で、「メルカリIME」というのがあります。今日はちょっとスライドをもってこれなかったんですが、写真検索から似た画像をもってきて、その画像の単語を使ってメルカリ独自のIMEをリリースしました。こちらはまだベータ版で、ABテストとして5パーセント開放(注:現在はAndroid版で100%開放)だったかな?

5パーセントに当たった方は本当にラッキーだと思うんですが、写真をインタフェースにして文字列の入力面を改善していく、すごいおもしろいサービスです。もし使える方がいましたら使ってみてください。写真検索を含めて本当にいいサービスだと思っています。

以上です。ありがとうございました。