クックパッドマートのドライバー向けWebアプリケーション

クックパッドマートの開発に携わっていますバックエンドエンジニアの中村です。

クックパッドマートは生鮮食品のECサービスで、流通の仕組みを自分たちで作っています。当然ですが商品を流通させるには、物理世界でものを動かす必要があります。実際にものを運ぶのはドライバーと呼ばれる人が行っており、このドライバーに向けていつ・何を・どこに運ぶといった指示をする必要があります。このエントリではそんなドライバーへの指示のために開発しているWebアプリケーションを紹介します。

各便のアプリケーション

クックパッドマートの流通の中には販売者が出荷してからユーザーの手元に届くまでに、タイムライン別に複数の流通方法が存在しています。タイムライン順に 「出荷サポート便」,「ハブ便」,「ステーション便」 といった流通方法が存在しておりそれぞれの便で個別にWebアプリケーションを用意しています。各流通方法を含む流通全体についてはこちらのエントリを見ていただけるとわかりやすいかと思います。

出荷サポート便Web

ローカルスポットという販売者の一時出荷拠点からハブという最大の流通拠点に運ぶ際に使用します。ドライバーはその日立ち寄るローカルスポットを確認し、各ローカルスポットで指定された商品を集荷します。その後指定されたハブの番地に商品を納品します。

ハブ便Web

あるハブから別のハブに運ぶ際に使用します。ドライバーは立ち寄るハブを確認し、それぞれのハブで指示されたコンテナを集荷・納品します。また集荷の際には行き先を示したラベルに張り替えるため、コンテナ用のラベルの印刷を行います。

ステーション便Web

ハブからステーションというユーザーが実際に受け取りを行う拠点に運ぶ際に使用します。ドライバーは集荷するハブを確認し、ハブでコンテナを集荷します。集荷の際には納品先のステーション番地のラベルに張り替えるため、コンテナ用のラベル印刷を行います。その後指定されたステーションに立ち寄りながらコンテナを納品していきます。

使用技術

開発時期によって違いはありますが、最近は以下のような構成にしています。

いわゆるBFF(Backend For Frontend)は配置せず、クライアントから直接APIを呼んでいます。パブリックに公開しているサービスではないのでSEO対策が不要なこととそこまでシビアなパフォーマンスを求められるわけではないので基本的にはSSR(Server Side Rendering)を使用せずシンプルな構成にしています。 SSG(Static Site Generator)も検討しましたが、一部動的に処理したいものがあったためバックエンドと同じく社内基盤のAWSのECS上にデプロイしています。

MUIを活用

ドライバー向けという性質上、パブリックに公開しているサービスに比べてリッチな独自デザインの必要性は低いため、流通チームには専属のデザイナーがいません。 そこで、MUIをほぼデフォルトで使いきることでドライバーの使用に問題ないクオリティのUIを素早く実装できるようにしています。

ネイティブアプリからの移行

実は元々Android/iOSのネイティブアプリを提供していた時期もありました。しかしチーム内にはサーバーサイドのエンジニアしかおらず、アプリを改修したいとなると他のチームに依頼をしてリソースを調整するといったことが都度発生し、どうしても足が遅くなってしまっていました。日々流通の形が進化していくので、こうした足の遅いアプリの開発がボトルネックになってしまうことが多くなってきました。しかし、一方で常にアプリ開発のタスクがあるわけではなく散発的に改修が必要になる状況で、流通チーム内にアプリエンジニアを常駐させておくほどではありませんでした。それならいっそ全てWebに移行して、サーバーエンジニアだけで開発を完結できるようにしようという決断をしてネイティブアプリからWebアプリにリプレイスしました。

Next.jsを選択したのもネイティブアプリで提供していたUI/UXに遜色ないものを実現したかったというモチベーションが大きかったからです。Reactベースのフレームワークは他にも選択肢がありますが社内でも実績・ノウハウのあるNext.jsを選びました。

ネイティブ特有の機能

リプレイスの際、大きなハードルだったのはネイティブアプリ特有の機能をどうするかということです。幸いにも最近のブラウザはかなりの機能を実現できるようになってきており、絶対にネイティブアプリでなければならないということが少なくなってきました。

カメラ

集荷・納品時の検品のためにQRコードを読んだり、納品結果の撮影のために使用しています。

一番苦労したのはカメラの権限の許可設定でした。対象のドメインとブラウザ自体の2箇所で権限を許可する必要があり、全ドライバーに権限を許可してもらうよう展開するのが開発より大変でした。

プッシュ通知

プッシュ通知も実現できるようになりつつありますが、iOSは未サポートだったりと全端末で活用できるわけではありません。代わりに全ドライバーをSlackに招待してSlack上でメンションすることによって通知するようにしました。

その他にもSMSを送信することも検討していて、実現の目処も立ってはいたのですが、別文脈でドライバーをSlackに招待してコミュニケーションをとるようにする取り組みがあり、機能としてもかかる工数としても都合がよかったため利用した形です。

位置情報

使っていません。ネイティブアプリでは取得していたのですが、結局活用しなかったのでリプレイス時にはオミットしました。仮に使いたくなった場合はドライバーの位置情報から配送の進捗を見たいといった用途が考えられるので、Webだとバックグラウンドで取得できないことが高いハードルになりそうです。

最後に

マートのドライバー向けWebアプリケーションについて紹介してきました。

途中にも書きましたが日々流通の形が進化しており、この先もまだまだ改善していく必要があります。単なるWebアプリケーションではなく物理世界と密接に関係している開発ができるのはなかなか刺激的で楽しいので、少しでも面白そうだなと思って頂いた方はぜひご連絡ください。

2022年クックパッドマート連載の他のエントリ

クックパッド生鮮 EC お届けの裏側 2022 年版

クックパッドマート流通基盤アプリケーション開発グループでバックエンドエンジニアをしている奥薗 ( @mokuzon ) です。クックパッドマートは生鮮食品の EC サービスで、商品を届ける流通を内製しています。そんなクックパッドマート流通にエンジニアがどう貢献しているかを今日から 4 日連続でご紹介していきます。

今回はクックパッドマートの裏側の流通がどんな要素で構成されているかを一気にご紹介します。このエントリを読んでおくと今後クックパッドマートの関連エントリを読むときにより具体的にイメージしやすくもなると思います。

項目が多いので全体を俯瞰しやすいよう目次を貼っておきます。

クックパッドマートについて

クックパッドマートは 2018 年 9 月 20 日にリリースされた生鮮食品の EC プラットフォームです。リリースから 3 年以上経ち、新規事業ならではのスピードを維持しつつサービス拡大のため試行錯誤を日々続けています。

https://cookpad-mart.com/

クックパッドマートは iOS と Android の専用アプリと、最近ではクックパッドのレシピアプリでも利用可能になっています。このアプリで商品を購入して、近所の受け取り場所 ( ステーションと呼んでいます ) で受け取れます。有料で自宅配送するオプションもあります。

取り扱っている商品はクックパッドが生産・在庫を持っているわけではありません。市場の卸売業者の方々、農家の方々、小売店の方々など様々な販売者がクックパッドマートを通して商品を販売しています。これが生鮮 EC ではなく生鮮 EC プラットフォームを名乗っている理由です。

クックパッドマートではこれらの販売者が出荷した商品を前述のステーションまで運ぶ流通を内製しています。この流通についてより詳しくご紹介します。

クックパッドマートの流通

資材

コンテナ

商品単位ではなくコンテナ単位で流通させています。これは効率よく物流を回すためのセオリーで、コンテナ物語 という有名な本があるので興味がある方は読んでみてください。

Docker に代表されるコンテナ技術と同様、規格化には物流にも絶大な恩恵があります。

シッパー

冷蔵機能のない車で肉や魚をはじめとしたチルド商品を運ぶために使っています。断熱材で出来ていて、この中に業務用蓄冷剤を入れて使用しています。 1 つのシッパーには上記のコンテナが 4 つまで入ります。

ラベル

商品やコンテナに貼り付けるラベルシールはマート流通で非常に重要なユーザーインターフェースです。現実の物体とソフトウェア上のデータを紐付けたり、直接ラベルに作業指示を印字したり、活用方法は様々です。

QR コードを印字しているものもあります。なお、このラベルを印刷するプリンターをマートのネットワークに繋ぐハードウェアの制御部分は内製です。

プリンターについて過去にご紹介したエントリーはこちらです。 https://techlife.cookpad.com/entry/2019/04/10/180000

ドライバー

クックパッドには自前のドライバーはいません。数社の運送会社と契約し、クックパッドマートの配送業務を担っていただいています。車両は軽カーゴ ( いわゆる軽バン ) と 2t トラックを使い分けています。

拠点

商品を流通させるにあたって、一時的に商品を保管したり配送効率を向上させるための拠点が必要です。ユーザーから近い順に

  • ステーション
  • ハブ
  • ローカルスポット

の 3 種類あります。

ステーション

先程も紹介したユーザーが商品を受け取りに行く拠点です。

  • コンビニ
  • ドラッグストア
  • コインランドリー
  • マンション
  • 銀行

など、様々なところに設置させて頂いています。現在 1 都 3 県で 700 箇所超あり、どんどん増えています。 実際の受け取り場所は https://cookpad-mart.com/about/maps/stations で確認することが出来ます。

冷蔵庫についている A, B + 冷蔵庫内部の両サイドに貼られている数字のシールで番地を表現しています。

ハブ

マートが持つ最大の物流拠点で、複数箇所あります。一般的な物流用語だとデポと呼んだりします。販売者が出荷した商品を集約し、ここを基点に各ステーションに配送を行っています。 ハブ内でユニークな通し番号が振られた番地札がついています。

冷蔵庫とスチールラックの棚があります。冷蔵の必要のない商品は冷蔵庫を使わないほうが空間効率もコスト効率も圧倒的に高いため、スチールラックの活用は重要です。

ローカルスポット

原則販売者は上記のハブに商品を出荷しに来ますが、ハブが遠い販売者のすべてがハブに直接出荷しに来ることは現実的には難しいです。そこで、各地に設けた一時置き場に販売者が一時的に商品を置いておき、マートのドライバーが回収しハブに運ぶ運用を一部しています。この一時置き場をローカルスポットと呼んでいます。

ルート

3 箇所の拠点を紹介しました。これらを 3 種類のルートで結んでいます。ユーザーから近い順に

  • ステーション便
  • ハブ便
  • 出荷サポート便

があります。

このルートの組み方、通称ルーティングには大きな技術的チャレンジがあります。ここでは簡単に種類だけご説明します。 詳細は クックパッドマートの配送ルートを自動生成している仕組み にて。

ステーション便

ハブそれぞれから 700 箇所強のステーションへ繋がるルートです。ハブで商品を集荷し、5-7 箇所のステーションへ運びます。車両は軽カーゴです。

ハブ便

ハブ同士を繋ぎ、マートの商圏のどこでも商品を届けられるようにしています。実際にはもう少し効率的な組み方をしていますが、環状線のようなイメージです。一般的な物流用語では横持ちと呼んだりします。この便は物量が多いので 2t トラックを使っています。

出荷サポート便

ローカルスポットからハブに商品を運ぶ便です。軽カーゴを使っています。

タイムライン

前述した 3 つの便は時間帯が決まっています。並べると以下のようになります。

販売者は 00:00-21:00 の間に出荷し、順次運ばれていきます。

現状のタイムラインだと販売者はユーザーから注文があった後に商品を出荷するため、それがこのタイムラインに乗ってステーションに届きユーザーが受取可能になるのにほぼ 2 日近くかかってしまうというのがサービス上大きな課題となっています。

より効率的な運び時間を短縮したり、時間枠をユーザーの生活時間にあわせて最適化するなど、今も様々な改善策を考えているところです。

なお、見て分かる通り配送はほぼ 24 時間動き続けています。システムにトラブルがあると即配送遅延に繋がるため、バックエンドエンジニアはシフトを組んで即応出来るようにしています。これについては以前エントリーを書いていますので興味のある方はどうぞ。 https://techlife.cookpad.com/entry/introduce-mart-on-call

アプリケーション

ひたすら物理世界の説明をしてきて、ついにアプリケーションの話です。開発者ブログとはいったい...いえ、これが面白いんですよ。

ドライバー向けアプリケーション

ドライバーにはルートの種類ごとに別のアプリケーションを提供しています。こういったパートナー向けアプリはモバイルアプリで提供されていることが多いと思いますが、マートではこれらはいま全て web フロントエンド技術で実装したアプリケーションを提供しています。 詳細は クックパッドマートのドライバー向けWebアプリケーション にて。

クックパッドスタッフ向け admin

これは特筆することはありません、典型的な管理画面です。

ラベル

ラベルの内容の定義方法についてご紹介した過去のエントリーです。 https://techlife.cookpad.com/entry/2021/08/18/100000

これ以外にも、Bluetooth プリンター向けに Flutter で書かれた専用のモバイルアプリもあります。

温度監視

食品を扱う以上、温度監視はとても重要です。一部構成が異なるものもありますが、各温度センサーから温度をリアルタイムでネットワーク越しに収集し、Grafana で表示したり Prometheus でアラートを飛ばすシステムを構築しています。

おわりに

クックパッドマートの流通の構成要素とそれに付随するアプリケーションについてご紹介しました。我々クックパッドマートの流通エンジニアが戦っている物理世界がどういうものであるか、イメージして頂けたら幸いです。

もう少しラフに流通エンジニアについて紹介している note がありますのでこちらもあわせてどうぞ。 https://note.com/cookpad_mart/n/nc38d718a5d90

今回は物理世界の説明が中心になってしまいましたが、明日からはより技術的に突っ込んだお話をしていきますのでお楽しみに。

クックパッドマートは流通エンジニアを大募集しています。少しでも興味を持っていただけましたら、Twitter で @mokuzon に声をかけていただいてもよいですし、カジュアル面談も実施していますのでぜひご応募ください。

cookpad-mart-careers.studio.site info.cookpad.com

2022年クックパッドマート連載の他のエントリ

fastText in Cookpad

研究開発部の原島です。去年からはレシピサービス開発部も兼務しています。そちらの話(検索の話)はおいおいするとして、今日は研究開発部の話(機械学習の話)をします。

fastText

単語の分散表現、重要ですよね。ニューラル全盛期の現代において、使わないという選択肢はほとんどないように思います。

最初に話題になったのは、2013 年に発表された word2vec でしょう。「king」のベクトルから「man」のベクトルを引き、「woman」のベクトルを足したら「queen」のベクトルになったという話は有名です。一方、最近は、2018 年に発表された BERT(及び、それに類するモデル)の話題で持ちきりですね。

fastText は、ご存知の方も多いと思いますが、分散表現を学習するためのライブラリです。学習のアルゴリズム自体を指すこともあるように思います。fastText の論文は以下です。2017 年に発表されたものなので、発展が速いこの業界においてはもう古い論文なのかもしれません。

  • Enriching Word Vectors with Subword Information. Piotr Bojanowski, Edouard Grave, Armand Joulin, Tomas Mikolov.

なぜ fastText なのか?

クックパッドでは fastText をよく使っています。では、なぜ fastText なのでしょう?上でも触れたように、word2vec や BERT などの選択肢もあります。もちろん、fastText も主要な選択肢の一つではありますが、どうして fastText なのでしょうか?

様々な理由がありますが、まとめると、「性能と運用のバランスがよい」といったところでしょうか。

性能の面では、サブワード(部分文字列)が考慮できる分、word2vec よりは fastText がよいでしょう。一方、文脈を考慮した表現が学習できる分、fastText よりは BERT がよさそうです。もちろん、これらは一般論です。実際にはタスクや学習データによって話が違ってくるでしょう。

一方、運用の面では BERT より fastText や word2vec がよいでしょう。BERT は事前学習が大変です。クックパッドでも何度かトライしていますが、お金も時間もかかります。学習データ、単語分割器、サブワード分割器、whole word masking、マスク確率、...。試行錯誤するだけでもかなりのお金と時間がかかります。

もちろん、ファインチューニングで済ますという手もあります。ありがたいことに、世の中には事前学習済みのモデルが沢山あります。これらを使えば、事前学習する必要はありません。しかし、結局、デプロイするにはモデルが大きかったり、API として使うには推論が遅かったりといった問題が残ります。

このように、性能と運用のバランスを考えると、fastText はいまでも非常に優れた選択肢だと思います。

fastText を使っている取り組み

クックパッドで fastText を使っている取り組みとしては、たとえば、以下があります。

  • 単語埋め込みを利用した商品に対するキーワードの予測(to appear). 山口泰弘, 深澤祐援, 原島純. 言語処理学会第 28 回年次大会発表論文集.

こちらは、クックパッドマートの商品名から、食材を表すキーワードを予測する取り組みです。キーワードや商品名をベクトルに変換するのに fastText を使っています。予測結果はクックパッドマートの管理画面で使われています。

余談ですが、こちらの取り組みは今年の言語処理学会で委員特別賞をいただきました。ありがとうございます。

こちらは、レシピのタイトルから、そのレシピで使われるであろう食材を予測する取り組みです。タイトル中の単語をベクトルに変換するのに fastText を使っています。予測結果はレシピの投稿画面で使われています。

こちらは、レシピのタイトルから、そのレシピのカテゴリ(e.g., 肉料理、魚料理、野菜料理、...)を予測する取り組みです。こちらも、タイトル中の単語をベクトルに変換するのに fastText を使っています。予測結果は、近日中に、レシピのブックマーク画面で使われる予定です。

その他、まだ実験段階の取り組みでも fastText をよく使っています。

fastText の学習・利用フロー

以下は、クックパッドにおける fastText の学習・利用フローです。Redshift から学習データを取得し、fastText を学習した後、モデルを S3に保存するというのがおおまかな流れです。たいしたことはしていません。ちょっと変わったことがあるとすれば、学習データが Redshift にあることくらいでしょうか。

f:id:jharashima:20220418092004p:plain:w300

1. 学習データの取得

fastText の学習にはテキストが必要です。日本語の場合、さらに、単語分割が必要です。

クックパッドの場合、全レシピのテキスト(e.g., タイトル)が Redshift に保存されています。また、その分割結果も Redshift に保存されています。詳細は以下の記事をご覧ください。fastText の学習にはこれを使っています。

分割結果の取得には Queuery(きゅうり)というシステムを使っています。Queuery は、UNLOAD を使うことで、Redshift やクライアントに負荷をかけずに SELECT を実行できるシステムです。Queuery は去年末に OSS 化されました。詳細は以下の記事をご覧ください。研究開発部の山口による Python クライアントもあります。

2. fastText の学習

Python スクリプトに以下の 2 行を書くだけです。fastText、便利すぎますね...。

import fasttext
model = fasttext.train_unsupervised('data.txt', model='skipgram')  # cbow でも可

全レシピ(2022 年 4 月時点で約 367 万品)のテキストを使っても、学習は約 10 分で終わります。メモリも 2GB 程度で済んでいます。学習には EC2 のスポットインスタンスを使っています。

パラメータは特にいじっておらず、デフォルトのままです。たとえば、ベクトルの次元数は 100 です。パラメータのチューニングは今後の課題(後述)です。

3. モデルの保存

モデルは S3 に保存しています。ロールバックできるように、過去に学習したモデルも残してあります。幸い、実際にロールバックが必要になったことはありません。まだ特に困っていませんが、ライフサイクルくらいは設定してもいいかもしれません。

4. モデルのダウンロード

学習済みのモデルを使いたいアプリケーションに対して S3 の該当フォルダへの Read アクセスを許可します。これで各アプリケーションでモデルをダウンロードできます。

以上が fastText の学習・利用フローです。その他、補足事項として以下があります。

fastText はレシピのフィールド(e.g., タイトル、材料、...)毎に学習しています。これは、fastText を使うタスク毎に着目するフィールドが違うためです。タイトルに着目するタスク(e.g., レシピの分類)ではタイトルで学習したモデルが使えるように、材料に着目するタスク(e.g., 材料の分類)では材料で学習したモデルが使えるようにしています。

ジョブスケジューラーやデプロイツールには Kuroko2hako を使っています。実行は基本的に月次です。学習時間が短いので、日次で実行したところで、特に問題はありません。ただ、分散表現はそんなに変わらないだろうと思うので、月次としています。もしかしたら年次でもいいのかもしれません。

今後の課題

最後に、今後の課題を三つほど挙げておきます。

一つ目は、「fastText の学習」でも触れたように、パラメータのチューニングです。学習アルゴリズムや学習データ、学習率、ベクトルの次元数、サブワードのレンジなど、チューニングの余地はたくさんあります。この辺りは腰を据えて取り組んでいきたいです。

二つ目は分散表現の評価です。一つ目の話とも関連するのですが、どのような分散表現がよいかは自明ではありません。基本的には、後段のタスクにおける評価指標を最適化する分散表現がよい気がします。ただ、後段のタスクにもいろいろあるので、悩ましいところです。

三つ目は代替モデルの調査です。「なぜ fastText なのか?」でも触れたように、本番での運用まで考えると、BERT のようなモデルが fastText より明らかによいとは言えません。一方、この業界の発展は速く、様々な懸念を払拭するモデルが明日にも発表されるかもしれません。業界の動向には常にアンテナを張っていきたいです。

おわりに

そういえば、つい最近、就業形インターンシップに「機械学習コース」を開設しました。上で挙げた課題はもちろん、クックパッドにおける機械学習に興味がある方は是非ご応募ください。

中途採用のご応募もお待ちしております。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/