クックパッドマートにおける実世界での配送を意識した注文の検証処理【連載:クックパッドマート開発の裏側 vol.1】

はじめに

こんにちは、買物事業部の勝間(@ryo_katsuma)です。 今日から5日間は、買物事業部のメンバーで連載記事を書かせていただきます。

買物事業部は、クックパッドの生鮮食品ECサービスの新規事業「クックパッドマート」の開発を行っている事業部です。 クックパッドマートのサービスについての説明や、立ち上げ期の舞台裏については昨年の長野によるエントリをご参照ください。

クックパッドマートは、iOS、Androidアプリのリリース、商品受け取り場所におけるスマートロックの設置、注文当日配送の実現など、 サービスをより多くの人に便利に使っていただくためのいろいろな新しい取り組みを行ってきました。 今回はこれらの取り組みについて多くの方にぜひ知っていただきたいと思い、連載形式で紹介させていただきます。

ちなみに明日以降は、以下のような内容を予定しています。

注文の検証処理

vol.1の本稿では、クックパッドマートにおける注文の検証処理(validation)について紹介します。 注文の検証処理とは、クックパッドマート内での用語になりますが、その名の通りユーザーがカートに入れた商品について、「注文可能かどうか」を判定するための検証処理になります。

iTunes Storeのようなデジタルコンテンツの販売の場合、クレジットカードなど決済手段に問題がない限り「注文できるかどうか」の検証処理に複雑なケースはあまり多くなく、せいぜい販売上限数を考慮するくらいではないかと思います。 一方で、物流が絡むECにおいては、現実世界での非常に細かな制限が多く絡み、注文の検証処理には多くのことを考慮する必要があります。

ここからは、クックパッドマートにおける注文の検証において、どのようなことを考慮しているかをご紹介します。

前提

まず、クックパッドマートにおけるデータの関連性についてご紹介します。 説明を簡略化するために実際の概念とは一部異なるものもありますが、注文処理においては以下の概念が重要な登場人物になります。

product

  • いわゆる「商品」の概念
  • ユーザーが購入し、販売店や生産者の方に用意いただく食材を指す

location

  • クックパッドマートにおける「受け取り場所」の概念
  • ユーザーは常に1つの受け取り場所を指定している

delivery

  • location毎の注文締切、受け取り開始、受け取り終了などの「配送スケジュール」
  • たとえば「4/8 2:00注文締切」「4/8 16:00 ~ 23:00 商品受け取り可能」などのデータを持つ

order

  • いわゆる「注文」の概念
  • 注文毎に配送スケジュールのdeliveryに紐付き、各orderは複数のproductを持つ

データ間の関連性

rails的に表現すると、このような関連性を持っています。

class Delivery
  belons_to :location
end

class Location
  has_many :deliveries
end

class Product
end

class Order
  has_many :products
  belongs_to :delivery
end

また、商品の集荷配送については、後ほど詳細を述べますが下記のような流れになります。

  • 販売店は注文情報に従い、コンテナに商品を入れて準備
  • ドライバーは指示書に従い、複数の販売店から商品が積載されたコンテナを集荷し、温度管理された配送資材(以下、シッパー)に入れる
  • ドライバーは指示書に従い、複数の受け取り場所でシッパーからコンテナを取り出し、冷蔵庫に設置する
f:id:ryokatsuma:20190405192518p:plain

注文の受付可能数

前述の通り、在庫の概念が無いデジタルコンテンツではなく、実在する販売店や生産者の方に用意いただく商品なので扱える数は有限です。 また、商品の多くはクックパッドマートだけではなく、実店舗でも販売されているので、その数には上限が設けられている必要があります。

そこで、商品には購入可能数が設定されてる必要があります。ここではsales_limit_per_dayとすると、購入可能数を超えているかどうかのチェックはこのようになります。

product.sales_limit_per_day < delivery.orders.where(product: product).count

これで購入可能数の確認は十分でしょうか?答えはNoです。

すべての配送場所を考慮

ある販売店の商品は、1つの受け取り場所だけではなく、N箇所の受け取り場所に配送されます。 言い換えると、あるdeliveryの配送日と同じ配送日の別のdeliveryが存在することになります。 たとえば、4/8配送分の販売店Aの豚肉は、受け取り場所1のユーザーで購入されていなくても、受け取り場所2のユーザーで購入されている可能性があります。

そこで、ある配送日における全配送場所のdeliveryを考慮すると、受付可能数のチェックはこのようにする必要があります。

deliveries = Delivery.find_by(date: delivery.date)
product.sales_limit_per_day < Order.where(delivery: deliveries, product: product).count

これで購入可能数のチェックはOKになりましたが、注文の検証としてはまだ不十分です。

受け取り場所のスペースを考慮

受け取り場所において、注文した商品は冷蔵庫の中のコンテナ内に設置されます。

f:id:ryokatsuma:20190405192532j:plain

受け取り場所の冷蔵庫設置面積は基本的に広げることはできないので、冷蔵庫の数は簡単に増やすことがはできません。言い換えると、受け取り場所における冷蔵庫内のコンテナの数も有限になります。 例えば、注文商品の種類が多く、コンテナに空きスペースが存在しない場合は、冷蔵庫に商品を設置することができなくなるので、配送しても冷蔵できない状態になるので注文を受け付けてはいけないことになります。

そのため、「注文しようとしている商品は受け取り場所のコンテナに設置できるかどうか」を判定する必要があります。 そこで、クックパッドマートでは、販売商品1つづつ簡易的に体積を測定し(縦x横x高さ)、注文しようとしている商品の体積は、受け取り場所のコンテナの総体積に収まるかどうかを確認しています。

f:id:ryokatsuma:20190405192553p:plain

実際は、コンテナは配送のオペレーションの観点で販売店や生産者ごとに分けられている(複数の販売店, 生産者の商品が1つのコンテナに混在することは無い)ので、冷蔵庫の中で

  • 空きコンテナを確保できるか
  • コンテナの中に空き容量があるか

を判定しています。

ContainerBox.find_available(
  shop: product.shop,
  delivery: delivery,
  capacity: product.volume
).any?

ちなみに、コンテナの空き容量確認で現在は体積を利用していますが、ここは議論の余地があると考えています。 たとえば、商品は上積み(= 商品の上に商品を載せる)を禁止させたほうが商品が痛むリスクを減らすことができるはずですが、「商品に上積みすれば体積的にはコンテナに積載可能」な状態になってしまいます。そのため、本質的には「商品の接地面積を測定し、その総数がコンテナの設置面積を超えなければ積載可能」とでもしたほうが良いと考えています。 ただ、実際は商品それぞれ接地面積を簡単に測定することは難しいため、オペレーションコストを考えて体積を利用して運用を行っています。

さて、これで注文の検証は十分でしょうか?

配送時に温度を考慮

クックパッドマートで扱う商品は、商品の品質を下げないようにするために「予冷品(肉や魚など)」「未予冷品(野菜など)」「常温品(パンなど)」と複数の温度カテゴリに属し、各温度帯の商品はそれぞれ別のシッパーで配送を行っています。

f:id:ryokatsuma:20190405171806j:plain

たとえば、(これは実際に自分たちで配送実験を行うことで初めて理解したことなのですが、)夏の朝採れ野菜は相当高い温度に上がっています。この野菜を肉や魚など予冷品と同じシッパーで配送した場合、シッパー内の温度が上がってしまい、予冷品の品質に大きな影響が出ることが分かりました。部内では夏の朝採れ野菜温度高すぎ問題として有名な問題です。

また、パンも配送時に冷やしすぎると(約-2度〜5度)、小麦のデンプンがアルファ化老化して品質に大きな影響が出ることが知られています。 このように、商品の配送時にはその商品の「温度帯ごとに分けたシッパー」で配送することが、商品の品質保持の観点で必要になります。

一方で、各ドライバーが配送できるシッパーの積載量は有限です。 先の冷蔵庫の例と同じく、ドライバーも当日急に多く確保することは難しいので、あらかじめ確保したドライバーたちの車の積載量を超えるシッパーは配送できません。 つまり、1ドライバーが配送できるコンテナの総量も有限になるので、注文対象商品の温度カテゴリのシッパーに余裕があるかどうかを判定する必要があります。 注文対象商品の集荷配送を行うドライバーはあらかじめ決定できるので、このように書けます。

Shipper.find_available(
  driver: routing.driver,
  delivery: delivery,
  temperature_category: product.temperature_category
).any?

これらすべての確認を行うことで、「この商品は注文可能かどうか」が判定できます。なかなか道が長いですね。

まとめ

クックパッドマートにおける現在の注文の検証処理について解説を行いました。 実際はさらに細かな考慮がもう少しありますが、概ねこのような確認を注文処理の直前に行っています。 「かなり複雑すぎる処理をしているな、、」と思われた方もいるかと思いますが、実際に実装している僕もそう思います。

また、これらの処理はどんどんアップデートを行っています。 実際、温度カテゴリも最初は全く考慮せずにすべて冷蔵で配送していたものが、パンの扱いが出てきたことで2つの温度カテゴリになり、 夏の朝採れ野菜温度高すぎ問題に対応するために3つの温度カテゴリに対応しました。 このように現実に起きる問題に柔軟に対応するためは、的確にオブジェクト指向プログラミングで設計、実装を行う必要があり、ここは難しくも楽しいところでもあります。

一方で、このような複雑な処理も、おいしい食材をユーザーさんに確実に届けるために必要な条件だと考えています。 実際、リリースしてから半年近く経過していますが、冷蔵庫に設置できないなどの配送事故や、商品が傷んでいたといった事故は起きずに済んでいるので、引き続きおいしい食材を安全に届けるために技術によるサポートを行っていきたいと思います。

この記事を通して、クックパッドマートのサービス開発にご興味を持っていただけた方がいらっしゃいましたら、ぜひ一緒にサービスを作りましょう!

www.wantedly.com

お知らせ

クックパッドマートでは、4/24(木)に、買物事業部のエンジニアによる発表とエンジニアとのミートアップを開催予定です。

cookpad.connpass.com

今回の記事のような生鮮ECそのものの仕組みや、流通の仕組みの開発に興味がある方、クックパッドマートのエンジニアと直接話してみたい方はぜひご応募ください!お待ちしています!

1日でSwiftコンパイラを作る!Swiftコンパイラインターンを開催しました

こんにちは、モバイル基盤部の @giginet です。

去る3月28日、Cookpad Spring 1day Internship 2019の一環として、Swiftコンパイラコースを開講しました。

最近のSwiftコンパイラ

近年、iOSエンジニアの間ではOpen Source Swiftがホットトピックとなっています。

ここ1年ほど、わいわいswiftcというSwift言語処理系に関する勉強会が盛り上がっていますし、 先日のtry!Swiftでは、参加者がSwift自体にcontributionするOpen Source Swiftワークショップが開かれました。

Swiftコンパイラに用いられているLLVMという技術は今、多くの言語処理系で利用されています。これを学ぶことで、さまざまな言語処理系に応用することができます。

このインターンは、Swiftコンパイラを例に、LLVMに触れ、コンパイラの動作を理解することを目的に構成しました。 後半のワークショップでは、実際にMinSwiftという簡易的なSwiftコンパイラをSwiftで開発してみます。

講義

まず最初の30分は講義パートです。Swiftコンパイラの構成を見ていき、LLVMの仕組みを学びます。

Swiftコンパイラ(swiftc)の構成

f:id:gigi-net:20190404183119j:plain

まず、Swiftコンパイラがどのようなフローを経て、動作するかを見ていきました。 コンパイラと一口で言っても、swiftcはパーサーや意味解析、中間言語など、様々な要素技術から構成されています。

1日でSwiftコンパイラ全てを理解することはできないので、今回はそれぞれの役割は説明するだけで留め、このインターンでは、現在、多くの言語処理系の要となっているLLVMに焦点を当てていきました。

LLVMを学ぶ

Swiftコンパイラのうち、LLVMとの橋渡しを行うIRGenに注目し、LLVMとは一体どのようなもので、どのように動作しているかについて学びました。

LLVMとは

LLVMは、コンパイラ基盤と呼ばれるもので、機械語の生成や最適化など、コンパイラに必要なものを共通化して作れるようにした仕組みです。

この仕組みを用いれば、一から作るよりは非常に少ない労力で汎用的なコンパイラが作成できます。 現に、Swiftのみならず、非常に多くのコンパイラがLLVM上で実現されており、LLVMについて学ぶことで多くの言語処理系について理解することができます。

LLVM IR

LLVM IR(LLVM Intermediate Representation)は、LLVMで使われる中間表現です。

どの言語であっても、最終的に適切なLLVM IRを生成することで、バイナリの生成や複数アーキテクチャへの対応、最適化などをLLVMの提供する仕組みに任せることができます。

LLVM IRは、ヒューマンリーダブルであるという特徴があり、例えば以下のような C のコードを例に挙げます。

int main(void) {
    return 42;
}

このコードは、以下のような LLVM IR で表現することができます。

define i32 @main() {
  ret i32 42
}

この講義では、LLVM IRの簡単な読み方や、LLVMの最適化がどのように動作するかを扱いました。

MinSwift

簡単な講義を経て、SwiftでSwiftをコンパイルするコンパイラ、MinSwift*1 を製作しながら、コンパイラの構成や、LLVMの扱いについて学びました。

このワークショップは、LLVMが公式で提供しているLLVM Tutorialを参考に構成されており、ここで実装している架空の関数型言語Kaleidoscopeと同程度の表現力を持つコンパイラをSwiftで実装しました。

MinSwiftは以下のように動作します。

  1. Swiftのコードをパース
  2. Abstract Syntax Treeに変換
  3. LLVM IRを生成
  4. ビルドして実行オブジェクトを生成
  5. SwiftやC++からリンクして呼び出す

多くはSwiftコードのパーサーをSwiftで書いたり、LLVM IRの生成部分を実装しますが、他にも実装を通して、受講者は非常に多くのことを学ぶことができます。 例えば必要となったのは以下のようなトピックです

  • Swift Package Managerを使ったコマンドラインツールの開発
  • Swiftにおけるユニットテストの実行とTDD
  • Swiftによるパーサーの実装
  • LLVMSwift を用いたlibLLVMの利用
  • LLVM IRの読み方
  • XcodeやLLDBの扱い方

ワークショップ

f:id:gigi-net:20190404183144p:plain

ワークショップは、予め用意されているユニットテストを通しながら、ドキュメントを参考に実装を進めていく形式となっていました。

f:id:gigi-net:20190404183201p:plain

テストケースが全て通過するように、1ステップずつ実装を進めていくと、最終的にSwiftコードからLLVM IRを生成し、LLVMを使ってコンパイルできるコンパイラ、MinSwiftが完成します!

最後まで実装を行うと、以下のようなコードをコンパイルすることができるようになります。少しは実用に耐えるコンパイラになったでしょうか?

func fibonacci(_ x: Double) -> Double {
    if x < 3 {
        return 1
    } else {
        return fibonacci(x - 1) + fibonacci(x - 2)
    }
}

応用課題

最後に、それぞれ好きなテーマを探して、MinSwiftの改善に取り組んでもらいました。

例えば以下のような課題です。

  • 関数定義の拡張
  • 数値リテラルの改善
  • for文の実装
  • 変数の実装
  • 文字列リテラルの実装

課題を完了させて、独自の実装まで到達できた参加者は極僅かでしたが、一方で短い時間の中、上記のような言語機能を追加できた参加者もいました。

f:id:gigi-net:20190404183220j:plain

まとめ

普段Swiftを書き慣れていても、アプリ開発と言語処理系の開発では全くノウハウが違い、戸惑った方も多かったようです。

コンパイラインターンという割には、パーサーの実装量が多くなってしまったのは反省点です。

未経験の方には多少難しかったようですが、概ね好評を頂けたようで嬉しく思っています😊

クックパッドではSwiftコンパイラや言語処理系に興味があるエンジニアを募集しています。

*1:これはRubyコミッターの @mametter が行ったRuby処理系のワークショップ、MinRubyのオマージュとなっています https://techlife.cookpad.com/entry/2018/10/16/131000

SRE Lounge #8 でスポンサー & 登壇をしました

こんにちは。技術部 SRE グループの吉川 ( @rrreeeyyy ) です。

少し遅くなってしまいましたが、先日の 3/13 に行われた SRE Lounge #8 の、 会場・フード・ドリンクスポンサーをクックパッドで行いました!

また、スポンサーだけでなく Cookpad Microservice Architecture Overview というタイトルで登壇もしましたので、簡単に紹介させて頂きます!

SRE Lounge について

SRE Lounge は、UZABASE さんのSRE チームが中心となり発足した勉強会で、 現在は SRE 同士での情報共有や交流の場として有志の方により定期的に開催されています。

SRE Lounge は有志の方が開催しているため、会場の提供やドリンク・フードなどの支援を行うスポンサーを募集しており、 今回は、主催の方とのご縁もあり、クックパッドで会場提供ならびにドリンク・フードのスポンサーをさせてもらいました。

f:id:rrreeeyyy:20190403220609j:plain
乾杯の様子

各発表について

ソラコムAPIの裏側で運用チームは何をやってきたのか

最初は、株式会社ソラコムの五十嵐様より、「ソラコムAPIの裏側で運用チームは何をやってきたのか」というタイトルで発表がありました。 ソラコム様でのシステム構成や、内部での DevOps の取り組みや苦労についてお話されたあと、 SRE の採用について、各社の Job Description の分析についての調査結果を踏まえてお話されていました。

f:id:rrreeeyyy:20190403220725j:plain

資料はこちらです。

Cookpad Microservice Architecture Overview

次に、私、吉川 ( @rrreeeyyy ) が、 「Cookpad Microservice Architecture Overview」という題でお話させてもらいました。

クックパッドでのマイクロサービスへの取り組みに関して、 Cookpad Tech Kitchen #20 などでもお話させて頂いてますが、 今回は、これらの全体像がどうなっているのかについて簡単に紹介しました。

発表は少し駆け足になってしまったのですが、この発表をベースに、懇親会で気になったことについて深い議論を交わすことができ、 非常に有意義な時間を過ごすことができたと感じています。

f:id:rrreeeyyy:20190403220833j:plain

資料はこちらです。

また、マイクロサービスへの取り組みのさらなる詳細は、 先ほど紹介した Cookpad Tech Kitchen #20 などの資料も併せてご覧いただければと思います。

割れ窓理論をWebインフラの改善に活用し、チーム内の知識共有を促進している話

最後に、株式会社はてなの @hokkai7go 様より「割れ窓理論をWebインフラの改善に活用し、チーム内の知識共有を促進している話」というタイトルで発表がありました。

技術的負債や軽微な問題などを、Issue にしておき、週に 1 度、1 時間程度取って作業をしているそうです。 今回の発表にもある通り、こういった取り組みに対して実施してみてどうだったかの振り返りや改善をしっかり行えており、学びのあるお話でした。

f:id:rrreeeyyy:20190403220901j:plain

資料はこちらです。

おわりに

class SRE implements DevOps と言われているように、 会社の事業や、チームとして実現したい信頼性のあり方によって SRE の振る舞いや、目指すべきゴールはある程度変わってくると考えています。

その際に、SRE Lounge のような場で、他社の SRE の方々と意見を交換し、どういったゴールを目指しているのか、 どういった取り組みを行っていて、どういった結果になったのかを知るのは、非常に有意義なものだと感じました。

SRE Lounge から派生して、テーマを決めてディスカッションを行う SRE Session #1 というのも開催されるそうです。興味がある方は是非参加してみてください。

また、クックパッドでは SRE に限らず、SRE と一緒にサービスの信頼性に取り組んでくれる開発エンジニアも募集しています。 興味がある方は https://info.cookpad.com/careers/ をご覧頂いたり、Twitter などでお声がけ頂ければと思います。