snaqme Engineers Blog

おやつ体験メーカー snaq.me エンジニアによる開発プロダクトブログ

FastAPI 採用経緯

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
スナックミーは昨年からPHP -> FastAPIにReplaceをすすめ、すでに多くのAPIがFastAPIで本番稼働しています。

ここではなぜReplaceしようと考えたのか、FastAPIを採用したのかなどを記載させていただければと思います。

Replaceしようと考えた理由

  • 高学習コスト (メジャーではないPHPフレームワークの利用のため)
  • コードの質 (スピードを意識しすぎて コードの質を担保できなくなった 当時は 速 ≠ 質 と勘違い )
  • 心理的安全性 (テスト含めリファクタの難易度が高い)

などの理由からReplaceを決断し様々なフレームワークを調査検討し始めました

高学習コスト

メジャーではないPHPフレームワークの利用のため、新しいメンバーが入る時に

のため学習コストが高くなる可能性があったため

では、なぜPHPフレームワークを最初に導入したのかというと、立ち上げ当初は慣れたアセットで行う方がプロダクトが検証しやすいなどの理由から上記のフレームワークを導入しました。

コードの質

立ち上げ当初は1週間単位で方向性・仕様が変わる(市場検証など)ため、ひとまず動くものを作り続けていました。そのため、設計に大きな時間を使わず1に開発、2に開発、3に開発するという状況でした。

またスピードを重視するあまり、質のことをほぼ考えておらず、中長期にスピード低下する危険性などは意識していませんでした。

心理的安全性

実際のコードはスパゲッティコード状態、テストの記入なし、変更の影響範囲が分かりにくいなどから改修・追加開発をするときに難易度が高く、何か不具合があったときに問題解決に時間がかかることが予想してました。 というのもこのPHPのコードは私だけ把握するようにし、理由はPHPのReplaceは決まっている & このコードを理解するより他で稼働しているシステムを理解し追加・改修等に集中するためです。Replace促進するためあえての属人化にしてました。

Bule/Greenデプロイを導入してあった程度で、それ以外はこの開発をする際の心理的安全性は決して高いものではありませんでした。

FastAPIを採用した理由

  • 他のメンバーが学習コスト低くスタートできる
  • APIに特化したい (Railsのような多機能なFWはいらない)
  • パフォーマンスに優れている (レスポンス速度や処理)

などの理由からFastAPIにしました

他のメンバーが学習コスト低くスタートできる

昨今Rust, Go などを使った開発が主流になりつつありますが、

  • 誰も(しっかり)経験したことない言語
  • APIをReplaceするというプロジェクトであること

などの場合、学習コストをあまり当てたくないという理由でRust, Goは対象からなくしました。 その中で候補になった言語は、弊社で本番稼働しているPython, Ruby, JS, (PHP) となり、その中でもFastAPIの自由度の高さがありました。(シンプルでソースコード量が少なく、規制を弊社仕様にカスタムしながら開発できる) 自由度が高い = 複雑さが増す可能性がある ということですが、API開発という観点 & 開発する前に指針を概ね定めるという観点でカバーすると決めました。

※ 新規PJなどではしっかり用途にあった技術選定を行いますので、Rust, Goなど関係なく現時点の最善な選定をします。

APIに特化したい (view開発は求めてない)

今回はAPIのReplaceなので、API以外の機能は特にいくら優れていても加点はほぼないと割り切りました。

パフォーマンスに優れている

APIに関してはパフォーマンスの高さが大事だと考えており、ここでいうパフォーマンスとは

  • レスポンス速度
  • 高リクエストも捌ける

他の後押しポイント

  • FastAPIは2018年にスタートしとしたが github の stars 46.7k
    • (flask (2010年〜) -> 59.4k, django (2005年〜) -> 64.9k) ということで近年の中で勢いがある
  • mypy と Pydanticを組み合わせることで型対応も可能
  • Swagger が標準装備してあり、APIのドキュメント管理が自動化 (実はここほぼで決定打)

など

まとめ

いろんな角度から考察した上で、FastAPIを採用し 現在カバレッジは90%以上確保していたり、かなり安定した運用・開発ができReplaceに関しては今の所うまく行っていると考えています。 今後はFastAPIのドキュメントがまだまだ少ないというのが現状なので、弊社で培ってきた考え方(エラーハンドリング・Pydanticでのバリデーション・schemaなど)を定期的に共有していきたいと思っております。

弊社では 「おやつと、世界を面白く。」 一緒に面白くしたい仲間をお待ちしてます!!

meety.net

engineers.snaq.me

スナックミーのエンジニアチームで直近動きたいこと (2022年4月)

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
四半期ごとにチームに共有している直近動いていきたいことを一部変えてこちらのエンジニアブログに載せたいと思います。

エンジニアのバリュー

S: Scale Out
N: Next
A: Agile
Q: Quality

今年から弊社はVPoEを設け CTOである私が技術面にフォーカスし、VPoE が組織面にフォーカスする体制になりました。 この背景や行っていることは別途紹介させていただければと思っています。 VPoEを作る前からエンジニアのバリューを作成したいとずっと思っており、これを機にバリューを作成しました。
ちなみにスナックミー全社では以下がバリューになってます。

[おもしろい!]
難易度や工数じゃなく「それっておもしろい?」を軸に“ワクワクファースト”で行動します。
おもしろいほうがやる気が出るし、お客様に喜んでいただけるから。

[やってみよ!]
ごちゃごちゃ言ってないで、“オーナーシップ”を持ってまずやってみよう。
[つぎにいかそう!]
失敗したっていい。学びがあれば儲けもの。
失敗を恐れずどんどんチャレンジしていきましょう。みんな”やさしい”から失敗したって大丈夫だよ!

を頭文字にしたバリューです。 snaqme.com

バリューは覚えやすく自分的に気に入っています。 ですが、このバリューは全社に向けてバリューであり、エンジニアの目線で行くと少し昇華する必要があると感じていました。理由としてエンジニアにとって少しイメージしずらいものにどうしてもなっていたりします。例えば「おもしろい」これは人によって何が面白いのかバラバラです。バラバラというのが問題なのではなく、必ずしもユーザーさんと直接紐づいていないケースがあります。SREだと仲間のエンジニアのパフォーマンス最大化のために動くこと多く、製造や出荷などの社内システムは社員やパートさんが利用するために開発するケースが多いです。最終的にはユーザーさんに紐づきますが、少し感覚距離が遠いことろにいます。
ということから「」をベースにエンジニア向けに昇華させたバリューを作りました。
以下がバリューになりsnaq.meの「SNAQ」それぞれの頭文字にしたバリューにしました

Scale Out

エンジニアリングで事業成長を引っ張っていけるような開発をしていこう。その為にもスケールしやすい仕組みの開発を心がけよう。

Next

日々の挑戦の中で失敗をしてしまった時にでも、次への一歩が踏み出しやすい仕組みづくりを心がけよう。壊しやすい仕組みだったり、最速で開発・改善していけるような環境を持ち続けよう。

Agile

小さく早くスピード感を持って、継続的に改善を積み重ねていこう。

Quality

ユーザーにとって一番良いものを作っていこう。


  • スピードを重視したいが、短期的なスピードではなく中長期的なスピードも重視したい
  • 如何に小さく始められ
  • 失敗しても次に活かせるような仕組み
  • 速さだけでなくシステムの質も忘れない

このあたりをイメージしやすいようなバリューにしてます。

詳しいことは以下に

note.com

グロースハック

バリューを作ったので、バリューを意識した動きにしていきたいですが、もう一つテーマを決めたいなと思い共有しました。
それがグロースハックです。なぜ、グロースハックにしたのかというと、エンジニアは言われたものを開発することは一つの価値ですが、エンジニア自身で価値を社内・社外に提供できるような存在になっていきたいと思っているからです。相互の目線で一つの価値を2倍にも3倍にもすることが可能であり必要だと思っています。どっちが上ではなくどんな価値が最大化されるのかエンジニア自身が考えを共有し結果ユーザーさんの満足度が上がったり、インパクトが上がるもの作り上げていきたいと思っています。

まとめ

今回はエンジニアのバリュー(Scale Out Next Agile Quality )はVPoEが筆頭に考えを共有し一つ形にしましたので、どんどん浸透させていきたいと思います。
また、このバリューに共感でき方はぜひ一度カジュアルに話しませんか? 実際どう体現させているのか、どんなことをしているのかなどお話しします。 おやつと、世界を面白く。」 一緒に面白くしたい仲間をお待ちしてます!!

meety.net

engineers.snaq.me

他ブランド含めた在庫管理について

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
弊社ではsnaq.meというおやつの定期便を展開しているのですが、実はあまり知られていない「他ブランド含めた在庫管理」について今回紹介させてください。

当初のブランドの種類

他ブランド と聞いてそもそもsnaq.meに他ブランドがあることを知らない方でもいるのではないでしょうか?
サービス最初は下記のように1つで展開していました。(1つといいつつ常時100種類ほどの多ジャンルのおやつからユーザーさんごとにパーソナライズされお届けしています。)

f:id:snaqme-labs:20220301184910j:plain

ブランドの種類

そこから進化していき今はさまざまなBOXを選べるようになっています(お届け2回目から可能です)

  • 通常: 今まで通り常時100種類ほどの多ジャンルのおやつからお届け
  • オツマミー: お酒のおつまみにもぴったりなスナック
  • グラノーラ: スナックミーのパティシエが作るグラノーラ1袋(250g)、トッピングにぴったりのナッツ、ドライフルーツ、ミニクッキーなどを合計4種セレクトしてお届け
  • 焼き菓子: 定期便でお届けしている人気の焼菓子のみをセレクトしたBOX

f:id:snaqme-labs:20220301185432j:plain

それぞれのBOXが取り扱うおやつは一部共通になっています。
ざっくりイメージとして、それぞれのBOXとおやつの共通部分は以下のような感じです。少なからず何か一緒のおやつがある状況です。
それぞれに特化したおやつもあるものの適しているおやつもあるため以下のような管理になっています。 f:id:snaqme-labs:20220301191356j:plain

在庫の管理の仕方

では、今まではどのように管理していたのかというとシンプルに書くと以下通りです。単一エリア在庫管理 同じ在庫の数を見て判断 もしくは、同一商品を作成という形で行っていました。同一商品を別々なものとして扱う場合、ユーザーごとの嗜好データにブレが生じる可能性があります。なぜならデータ上別々のおやつと判断されるため、管理コストが格段に上がってしまいます。
また、ユーザー数が増加とともに出荷スペースの細分化の必要性が増してきます。なぜなら細分化していかないと物理的なスペース・1人あたりの出荷効率から1箇所で出荷できる数に限度があるため、出荷できる箇所を分ける必要になります。(最大出荷数の向上)

f:id:snaqme-labs:20220301191728j:plain

単一エリア在庫管理

単一エリア在庫管理の在庫数を見て判断する方針で問題になってくるのが、BOXとの関係が密結合な状態であるため並列処理ができず、直列の処理になってしまいます。つまりどういうことことかというと、1つの作業が終わらないと次の作業に移れない状況になるということです。例えば2つ在庫があり、2人で分ける場合、1人が終わらないと次の人に行けない状態。別々で管理できれば待つことなくそれぞれに1つずつ振り分けることが可能ですができないということです。また在庫管理したことがある方はイメージつくと思いますが、在庫の考え方として実在庫理論在庫の2種類用意して運用しているところが多いと思います。

  • 実在庫 : 実際に存在している在庫数
  • 理論在庫 : 購入などによる在庫を確保可能な数

この実在庫と理論在庫の関係上、在庫を確保後に次の作業を開始することは可能ですが在庫が同一の場所である時のみ運用成立できます。在庫が別々にある場合は、在庫管理を分ける必要があります。

複数エリア在庫管理

ということから、在庫を分担する仕組みを導入しました (複数エリア在庫管理)
在庫管理のSaasの拠点別在庫管理できるサービスを参考にし、拠点内で複数在庫管理させたい場合、拠点が複数ある場合両方に対応できるようにしたい思い、以下のような構成しました。決して、DBを分けマイクロサービスにすることはせず、1つのDB内で別々で管理できるようにしてます。

entity 拠点 {
id -- comment '拠点のid'
}

entity エリア {
id -- comment 'エリアのid'
}

entity 在庫拠点ーエリア {
----
拠点_id -- comment '拠点のid'
エリア_id -- comment 'エリアのid'
}

entity 在庫 {
----
在庫拠点ーエリア_id -- comment '在庫拠点ーエリアのid'
}

拠点||..|{在庫拠点ーエリア
エリア||..|{在庫拠点ーエリア
在庫||..|{在庫拠点ーエリア

f:id:snaqme-labs:20220301192615j:plain

  • 拠点 : 物理的に離れている (都道府県)
  • エリア : 同じ建物内に複数存在 (フロア別や部屋別)

ざっくり上記のようなER図で開発し、現在拠点は1つですが、エリアはすでに複数運用しています。

まとめ

複数エリア在庫管理を導入する前は毎日のようにエンジニアが何かしら修正をしたり、運用方法の注意点を説明したりなど、本質的じゃない動きをよく行なっていました。 オペレーション側も注意する項目が多く、神経を費やし心理的安全性は決して高い状態ではありませんでした。
一見同じおやつが別々のエリアに存在し複雑そうに見えていますが、実運用上あるおやつが全体で何個あるかは把握せず、そのエリアに何個あるのか把握できる大事なので、運用上の心理的安全性・ミス・出戻りなどはかなり減ってきました。
また、具体的に 単一エリア在庫管理から複数エリア在庫管理にどのように変更したのかは別の機会で共有させてもらえればと思います。
スナックミーではユーザーさんが利用する開発 (体験向上)以外に、今回の在庫管理を含めたWMSなども内製していますので、さまざまな観点から技術的チャレンジハードからソフトまでプロダクト開発が可能です。

より詳しいこと聞きたい方・一緒に働きたい方は 以下のmeety or twitterから連絡お待ちしてます。

meety.net

twitter.com

ユーザーさんにおやつを届けるまでのアーキテクチャ

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
登壇などでユーザーさんにおやつを届けるまでのアーキテクチャを紹介させてもらったことはありましたが、ここではなかったので改めて紹介させていただきます。 過去紹介しているものからいくつかアップデートもしているので、その辺りも一緒にご紹介させていただければと思います。

snaq.meとは

おいしいマルシェおやつとワクワクを詰め込んでハッピーな「おやつ体験」をお届けします

農家さんが大切に育てたこだわりの素材が、生産者さんの手で美味しいおやつに。全国の生産者さんとお客さまを繋げ、体と心に優しく美味しいおやつを、ワクワクと共に届けます。おやつの時間を価値あるものに、人々の心と暮らしを豊かにします。

ユーザーさんにおやつを届ける概要

アサイン : ユーザーさんに届けるおやつを決めること

ユーザーさんのアクション(評価やリクエスト、好き嫌いなど)から弊社の独自のアルゴリズムを用いてアサインをしています

・評価: 4段階 (苦手~大好き)
・リクエスト: 必ず入るとは限らないが「このおやつ食べたい!」という意思表示
・好き嫌い: 苦手な原料や好きなジャンルなど

f:id:snaqme-labs:20220201194831j:plain

アサインはサービス当初全て手作業で行っていました。評価は当初からできるようにしていたので、各々の評価データを1件ずつ確認しながらアサインをしていました。さらにアサインするためにどの商品がアサイン可能なのか、どういう組み合わせがいいのかは担当者に依存するため属人性の高い業務でした。そのため当初からアサインの自動化はサービス当初を急務として開発を進めサービス開始3ヶ月でβ版の導入しました。
(ユーザーが増えれば増えるほどアサイン業務に時間が埋め尽くされるため。
ex) 1人1分で100人行うには100分、500人もやろうと思えば8時間強の業務)

アサインを支えるアーキテクチャ

f:id:snaqme-labs:20220207164054p:plain

① : アサインするおやつの順番決め
② : アサイン実行環境
③ : ユーザー情報の格納

① : アサインするおやつの順番決め

ユーザーごとに好き嫌いが異なるので、アサインした方がいい順番も異なってきます。そのため、ユーザーとおやつに点数をつけていくところになります。点数が高ければ、好きな可能性が高く、低いと好きな可能性が低いという感じです。

② : アサイン実行環境

アサインを実行環境であり、基本的にここは2パターンで実行されます。

  • 基幹システムからの実行
  • 定刻での自動実行

ここの実行環境ではアサイン登録はされず実行と結果のファイルだけ出力される仕組みになってます。また、弊社の独自のアルゴリズムが存在し、ユーザーごとに8品の商品をアサインを可能にしています。アサインできる条件はいくつかあり、実際にアサインを実行する前にさまざまな前処理を行なっています。例えば、在庫がなく棚卸しさせるおやつや製造しアサイン可能であればロケーションidを採番したりなどです。

ロケーションid: おやつがどの場所に存在させるか振る作業
在庫があれば全てアサイン可能ではなく、限られたSKUのもアサインをしています
常時100種類ほど

③ : ユーザー情報の格納

ここに関しては以下をご覧ください

labs.snaq.me

アサインの種類

  • snaq.me の定期
  • スイッチボックス
  • 追いシリーズ (翌日配送のsnaq.meやotuma.me)
  • otuma.me 定期
  • その他 (臨時対応)

当初はsnaq.meだけのアサインでしたが、さまざまなアサインのタイプにも対応できる仕組みになっています。(条件も異なっています。)

まとめ

アサインのシステムはver.1 -> ver.2 -> ver.3 -> ...とアップデートしてきました。このアップデートはユーザーさんの声やデータから導かれた課題に対して向き合い取り組んできたところになります。しかし、課題は山積みです。製造や出荷効率を上げつつ、ユーザーさんの満足度をあげるこの両輪をうまく回転させることで事業を推進していけると思っています。

より詳しいこと聞きたい方は 以下のmeety or twitterから連絡お待ちしてます。

meety.net

twitter.com

スナックミーのエンジニアチームが直近動きたいこと (2022年1月)

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
四半期ごとにチームに共有している直近動いていきたいことを一部変えてこちらのエンジニアブログに載せたいと思います。

システムのシンプル化

非エンジニアにも向けて共有したので、シンプルと言う言葉にしてみました。
なぜシンプル化させたいかというと、サービス始めると様々な仕様が生まれては消えていき、相関してコードも増えていきます。言い換えるとプロダクト開発をメインで進めていくと様々な技術負債が生まれてきます。
技術負債とはコードやアーキテクチャだけの話ではありません。
例えば、「サービス当初は簡単でわかりやすかったが、様々な機能が増え、気づくとよくわからない」。
もしくは「以前はこの設定で動作していたのに、今はうまく機能してない・機能してなかったっけ?という、一つの機能の影響度が不透明」。このような状態はよくあると思います。
前者はいい意味で捉えるとサービスを使う用途は人によってそれぞれなので、必要な機能と不要な機能というのは存在してくることはあると思います。(100%いいとは限らない)
一方で、後者はチームで混乱を招く可能性があります。新しい仲間が入った時の学習コストが増加したり、本質的ではないコミュニケーションが発生したり(多方面から○○は□□すれば良いですか?と言うのな同じような質問など)、小さいミスが多発したりなどです。
非エンジニアにとってシステムはいくら内製化しても
仕様 = 守るもの
と捉えがちです。あながち間違ってないですが、足りないものに付け足す依頼はできても、マストな項目が多い場合、多いというのとに関しては気づきにくいものです。なぜなら、一つの機能に対して引き算していくことはその機能の周りにある関係性が見えにくいためだと考えています(必要だと思ってしまう)。また、人はペインに改善を求めますが、重要ではなく、複雑すぎるシステムは思考停止になりかねません。
そこで無意味な制限を極力なくすことで、システムのつながりがクリアにしたい。
このような技術的負債を定期的に見直す必要があると考え、今年のテーマとして取り掛かっていきたいと思っています

システムが原因で各々のチームをチェーンのようなもので身動きしずらい状態にせず、動きやすいゴムのようなものに変えていきたいとイメージです

シンプル化にすることでのメリット、デメリットは
メリット

おもしろいことがしやすくなる => できることの幅が広がる
チャレンジがしやすくなる => ユーザーさんの満足度 =>会社・個の成長
チーム内の意思決定が早くなる => スピード感が生まれる / 次に活かしやすい

デメリット

個々が好き勝手でき、カオス状態になる可能性がある

会社を成長させていくためにはこの状態が必要だと考えているので、今年のエンジニアチームのテーマとしています。

データドリブン

「完璧なデータ分析基盤を作り、KPIから事業を促進していくぞ」みたいなことをしたいのではなく、取れてないデータを把握・残し、施策を打つときに何を確認し、トレードオフが起きていることろは何かなどしっかり考察できる状態にしていきたいと考えています。
改めて現状のKPIを一つ一つ見直し、インパクトがあるところ、トレードオフになっているところを把握できる状態にしていきたいと考えています。
というのも今年の3月でサービス開始丸6年たちあらゆるデータから施策など打ってきました。
その中でできていない施策、施策の因果関係、見ているデータの粒度など、整っていない箇所も多くあり、データを扱えるレベルを1段階2段階高めていきたいと考えています。

相手を理解していこうとする意識

物事を進めていくには必要な観点だと思っています。
弊社は、製造・出荷、商品開発 (定期便、ストア)、パティシエ、マーケ、CS、デザイン (オフライン、オンライン)など多岐に渡るメンバーが多く存在しています。
さまざまなメンバーがいるので、少しでも理解していくことがプロダクトを1段階、2段階前に進めることができると考えてます
一次情報が大事な話と一緒で、会社内でも自分たちの領域外の情報・思考をしっかり自分の目・体で感じる必要性があると思います。

例えば「Aができないので、Aが欲しいから作って欲しい」 この観点だけ見るとAが必要だから、Aを作る。理由もAのように聞こえる。しかし、必ずしもAが必要なのではなく、BCを行うことで解決できることは多々ある。
しかし、最初からBCの案が出るわけではなく、事業のドメイン知識やそのポジションの考え方を把握する必要があります。
エンジニア同士でもAを作ることできても、コード安全性、汎用性、データの冪等性などは別の話と近いかもしれない

まとめ

今年は昨年の動きや振り返りから個・チーム・会社全体として1段階、2段階スケールアップしていけるような取り組みをして、スナックミー全体をさらに促進していきたいと思います。

上記の3つの中で「システムのシンプル化」をマイクロサービス化と書かなかったのはマイクロサービスを目的としないようにしたいという意味を込めています。たまに手法を目的・目標としてプロダクト開発する時がありますが、目的はユーザーさんの満足度を上げることだと考えているので、チームとしての目標に掲げませんでした。
しかし、エンジニアは新古知新の姿勢が大事なので、現状に満足するとすぐついていけなくなるのは事実です。そのためにチームで補いながら開発していきたいと思います。

今年もよろしくお願いします。新しい仲間もお待ちしております。

おやつと、世界を面白く。」 一緒に面白くしたい仲間をお待ちしてます!!

meety.net

engineers.snaq.me

スナックミーのエンジニアチームで直近動きたいこと (2021年10月)

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
四半期ごとにチームに共有している直近動いていきたいことを一部変えてこちらのエンジニアブログに載せたいと思います。

歩み寄りの意識

スナックミーにとってエンジニアが開発することで全体が前進できる部分が多く存在しています。(おやつ選定のアルゴリズム、出荷システム、LINEやメールを用いたCRM、おやつのリクエストなど)
一つの機能、1回の分析、パフォーマンス改善全てが無駄なことなんて1つもなく、何かしらプラスに起因してます。
よりプラスに働くには、互いに(エンジニア同士、他チーム)押さえ合うのではなく、良い方向に引き合う関係である必要があると思います。
そのためにも、ただ要望を受け対応するだけではなく、相互に高め合えるような姿勢を意識したいと考えています。
そこで高め合えるためには互いに歩み寄りの意識が必要だと思っています。
完全に任せっきりにするのではなく、少しでも知っていく姿勢 (100知る必要はなく、1でも知ろうとする動き)
1 知っている状態と知らない状態では議論の深さが違いより良質なアウトプットになると思っています。
また、知っていくことで開発も相手の目線で提案・開発ができるようになると考えています。

非エンジニアから見るとエンジニアがやっていることは一番ブラックボックスに近い仕事かと思います。UI/UXなど目に見えるものは認識されやすいが、それ以外のことはなかなかイメージしずらいため、何をしているのが話してもよくわからない。
そのためエンジニアから歩み寄ることで認識できる部分があるはずなので、この実践をエンジニアチームではやっていきたいと思っています。

仕様追加 と 仕様変更 (改善)

機能の要望をもらうとき、追加と変更・改善を一緒に考えて対応している場合はないでしょうか? それぞれの立ち位置を考えないと、システム全体が劣化していく可能性があります。 (リファクタしていく話とは違う話です)

仕様追加

仕様追加は現時点から

  • プラス+ に寄与
  • マイナス- に転じる

にもなりうる

例えば、ある商品登録するシステムにおいて必要だと認識していた1つの機能を追加されることは、数年前から利用している人にとって学習コストはさほど上がらず、便利になったと感じることが多い。
一方、これから始める人(始めたての人)にとってこの追加機能は追加されたことで学習コストを数倍にも膨れ上がる可能性があります。 (将来的には属人化さ理解すらできなくなる)

f:id:snaqme-labs:20210930212415j:plain

仕様変更 (改善)

一方でこの仕様変更や改善は

  • マイナス- から 0 or プラス+
  • プラス+ から プラス+α

全方面にとってプラスに寄与することが多い
マイナスに寄与する可能性がある場合は A/Bテストなどで検証は必要になる

f:id:snaqme-labs:20210930212426j:plain

毎回問いたい

追加要望がNGと言っているわけではなく、本当に必要なことって何なのか?ということです

追加の場合

  • 解決したいことは何か
  • 本当に追加する必要性があるのか
  • 追加することによって紛らわしくなってないか など

変更・改善の場合

  • 何を良くしたい(ペイン)のか
  • 何が良くなるといいのか
  • 変更の場合、利用者が混乱する時があるので許容範囲なのか など

考えて実装できるように、 コードが書けるだけのエンジニアになってほしくないそんな思いが強いです。
しかし、深く考えすぎて何もできなくなると元も子もないので、シンプルにすることを忘れないようにしたい。

開発から最短デプロイ

先にお伝えしますがなんでもテストしないで、何が変更されたのか共有しないで好き勝手に一人でデプロイできるようにしたいという話ではないです。
チームで100、120動けるようになることは素晴らしいが、誰か欠けると1歩すら動けなくなることは好ましい状態じゃないです。

開発者の一つの価値として機能を開発しユーザーさんに届けることで生み出せます。
「変化なくして、進化なし。」とあるように何もしないことは衰退の始まりだと考えています。

そこでどうしたら 開発したものをいかに早くデリバリーできるか を考え実行する

  • 中間地点のタッチポイントをいかに減らせるか
    • チェックなしでOKということではない
    • テストなどの検証を抑える (100%は目指さない)
    • QAの観点
  • チーム間の仕様の共有は行き届いている状態 (slackなどにポストすればいいという話でも、esaなどのdocsにまとめればいいという話ではない)
  • メンバーが何をするべきかわかっている状態 など要点を現時点から中期の状態を想定しチームで考え試していくことが大事だと考えています。
    (互いのストロングポイントを活かしながら)

最後に
先月の9月でスナックミーは創立から6年経ちました。snaq.me は来年の3月で丸6年になる予定です。
エンジニアメンバーは今、10名ほどになり少しずつですができることが増えてきています。メンバーが増えると対数的にスピード感や意思決定が遅くなっていく傾向がありますが、早期の段階から取り組むことで良くなると思っています。
また、スタートアップはスピードが命といっても過言ではない、そのため立ち上げ時のスピードをより加速させるためのアプローチはチーム全体で取り組むことで達成できると私は思っています。
これからも定期的にこういう発信はしていこうと思います。

おやつと、世界を面白く。」 一緒に面白くしたい仲間をお待ちしてます!!

meety.net

engineers.snaq.me

おやつのアサイン最適化を求めて〜4時間かかった処理を20分に短縮した取り組み〜

こんにちは、SRE の多田(@tada_infra)です!

この記事ではスナックミーのお客様にお届けする,おやつのアサインをカイゼンした話についてです.私と開発者の加藤(id:hisaaki_kato)で記事を書いていきます.

課題とカイゼンの経緯

スナックミーではお客様にお届けするおやつを決めて,専用の BOX に梱包する作業を「アサイ」と呼びます(下記資料イメージ参照).このアサイン工程で事前に○日のアサインはどれくらいの人数に対してどれくらいのおやつを確保しておく必要があるかを計算する処理を「プレアサイ」と呼ぶのですが,この処理がユーザー数の増加に伴い4時間かかる課題がありました.

現状4時間かかるのだからこのままユーザーが推移していけば自然と「プレアサイ」にかかる時間が増えることが予測されます.その分,内部のオペレーション的に①無駄な待ち時間が発生する,②在庫確認するためとはいえ気軽に処理を実行できないのは不都合が多いと言った課題があったので,私と加藤を中心にカイゼンを行うことにしました.

カイゼンの取り組み

次からは具体的にプログラム側で行ったカイゼンAWS 側で行ったカイゼンを紹介してきます.

プログラム側のカイゼン

プログラム側では、以下のAWS 側のカイゼンで後述しますが、処理を並列化することを念頭にボトルネックとなっていた処理を別リポジトリへと外出しを行いました.

ボトルネックとなっていた処理の概要

一連の処理時間の計測によってボトルネックとなっていた処理は判明しており,DBから取得したユーザー情報をユーザークラスへと格納する処理に全体の8割ほどの時間がかかっていました.アサインを行うための前段階として,ユーザーごとのおやつの好き嫌いやこれまでにお届けしたおやつの評価情報などを格納するだけの処理ですが,ユーザー規模の増加によって指数的に処理時間が増大してしまっていた形です.

必然的に,この部分の処理時間が短縮すれば大幅な高速化が見込めます.そのため,この部分について並列化のために処理の外出しを行いました.

データの受け渡し

処理の外出しに伴い,データの受け渡しを行う必要が生じます.元側の処理ではDBからデータの取得のみを行い,これを外出しした処理でユーザークラスに格納し,再度元側の処理でこれらを受け取って後続の処理をしていくイメージです.ただしユーザーデータそのものは大きすぎて受け渡せないため,S3に保存してそれらのパスをパラメータとしてやり取りする形を取りました.

ファイルの読み書きの高速化

S3へと保存するファイルについては当初はcsvjsonの形式で保存する想定でしたが,これらファイルの大量の読み書きが新たなボトルネックとなってしまうことが分かりました.そのため,ファイルはpickle形式で保存することでこの部分についても大幅な高速化が実現できました.

AWS 側のカイゼン

AWS 側のカイゼンとして当初 AWS Batch 1台で全て処理したところを、プログラム側で外だしした処理を以下の観点で考慮し Step Functions のワークフローにすることにしてカイゼンを試みることにしました.

  • Map を使った並列処理を組める
  • 並列処理実行時の成功/失敗のステータスを Step Functions のワークフローで分岐して組める
  • AWS Batch にも配列処理があるが,特定の処理だけ並列実行ができなさそうであるため

構成イメージ

Step Functions のワークフロー 構成イメージは下記のものになります.ECS Fargate の起動とパラメーターを渡す Lambda と Map を使って多数の ECS Fargate が起動するため各コンテナのジョブステータスをポーリングする Lambda が並列処理の成功と失敗を判定します.Fargate は起動後インプットファイルの取得とアウトプットの出力で S3 バケットを使うような動きをします.

f:id:sadayoshi_tada:20210809214619p:plain

Step Functions の 定義

Step Functions の定義は下記のような ASL を設定しました.最初の Lambda で ECS Fargate を起動して次の Lambda で ECS Fargate のタスク実行状況をポーリングして成功か失敗かを判定する動作をします.60秒待機した後,再度ポーリングし,ジョブが終了していた場合,成功か失敗かを判定するような形です.

ASL 定義

{
    "Comment": "Processing ECS Fargate",
    "StartAt": "test",
    "States": {
      "test": {
        "Type": "Map",
        "End": true,
        "Iterator": {
          "StartAt": "Lambda Invoke",
          "States": {
            "Lambda Invoke": {
              "Type": "Task",
              "Resource": "arn:aws:states:::lambda:invoke",
              "OutputPath": "$.Payload",
              "Parameters": {
                "Payload.$": "$",
                "FunctionName": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:run-sfn-execute"
              },
              "Retry": [
                {
                  "ErrorEquals": [
                    "Lambda.ServiceException",
                    "Lambda.AWSLambdaException",
                    "Lambda.SdkClientException"
                  ],
                  "IntervalSeconds": 2,
                  "MaxAttempts": 6,
                  "BackoffRate": 2
                }
              ],
              "Next": "status Lambda Invoke"
            },
            "status Lambda Invoke": {
              "Type": "Task",
              "Resource": "arn:aws:states:::lambda:invoke",
              "ResultPath": "$.status",
              "Parameters": {
                "Payload.$": "$",
                "FunctionName": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:job-polling"
              },
              "Retry": [
                {
                  "ErrorEquals": [
                    "Lambda.ServiceException",
                    "Lambda.AWSLambdaException",
                    "Lambda.SdkClientException"
                  ],
                  "IntervalSeconds": 2,
                  "MaxAttempts": 6,
                  "BackoffRate": 2
                }
              ],
              "Next": "Job Complete Check"
            },
            "Job Complete Check": {
              "Type": "Choice",
              "Choices": [
                {
                  "Variable": "$.status.Payload",
                  "StringEquals": "FAILLED",
                  "Next": "Notify Fail"
                },
                {
                  "Variable": "$.status.Payload",
                  "StringEquals": "SUCCEEDED",
                  "Next": "Notify Success"
                }
              ],
              "Default": "Wait 60 Seconds"
            },
            "Wait 60 Seconds": {
              "Type": "Wait",
              "Seconds": 60,
              "Next": "status Lambda Invoke"
            },
            "Notify Success": {
              "Type": "Pass",
              "Result": "Success",
              "End": true
            },
            "Notify Fail": {
              "Type": "Pass",
              "Result": "Fail",
              "End": true
            }
          }
        },
        "ItemsPath": "$.parameter",
        "Parameters": {
          "hoge.$": "$.fuga",
          "s3_bucket.$": "$.s3_bucket"
        }
      }
    }
  }

ワークフロー の処理結果確認

ワークフローの処理結果を確認すると以下のようなイメージで遷移し,期待通り動作していることを確認できました.

ワークフロー動作イメージ

f:id:sadayoshi_tada:20210809215552p:plain

一点だけ使っていて注意点があったのですが,Map の同時実行数が40以上を超えるとMaxConcurrencyの注記にもあるように一気に処理しない動作でした.ただ,一気に処理しきれない分も失敗にならず順次実行されるのでこの点を認識していれば問題ないのかなという感想を持ちました.

同時反復は制限される場合があります。この現象が発生すると、一部の反復は前の反復が完了するまで開始されません。入力配列に40項目を超えると、この問題が発生する可能性が高くなります。

docs.aws.amazon.com

40以上実行した場合の処理イメージ(第1弾の処理)

f:id:sadayoshi_tada:20210809221833p:plain

40以上実行した場合の処理イメージ(第2弾の処理)

f:id:sadayoshi_tada:20210809221843p:plain

40以上実行した場合の処理イメージ(最終結果)

f:id:sadayoshi_tada:20210809221852p:plain

まとめ

おやつのアサイン最適化を求めたカイゼンの取り組みを紹介しました.今回のカイゼンですが,検証や本番適用まで3日間に収めつつ大幅な時間の短縮により社内関係者から感謝の言葉をもらえたのは嬉しかったです.これからもよりよい運用の形を開発者と目指していければと思います.

f:id:sadayoshi_tada:20210809225514p:plain f:id:sadayoshi_tada:20210809225548p:plain