dely engineering blog

レシピ動画サービス「kurashiru」を運営するdelyのテックブログ

State of dely devという取り組み

f:id:delyumemori:20180807164938j:plain

こんにちは。

普段はdelyのAndroidアプリの設計、実装とかその他もろもろを担当している梅森です。

最近弊社では、長期的な技術的な意思決定や技術的課題の可視化・解決の支援など、技術的な側面においてdelyの開発チームが成長していけるような取り組みを考えて実施しています。本稿ではそのうちの取り組みの一つをご紹介します。

エンジニアは普段の機能実装と並行して長期的なプロダクト改善を行うための施策を行っていたりしますが、アーキテクチャの移行やリファクタリング、自動テストといった施策は短期的には成果が見えにくいということもあり、業務的に近いエンジニアや成果が分かりやすいことをやっているエンジニアがどんなことをやっているかは分かるが、あまり仕事で関わらないエンジニアは何をやっているかよく分からないという状態になったり、会社としてはどのような技術的な意思決定を推奨しているかということが分からない(あるいはそもそも知見が存在しない)という状態になりがちです。

開発チームの規模が大きくなっていけば、例えばiOSやAndroid、サーバーサイド、SREといった技術領域ごとにチームを組織化してそこで知見を共有したり溜めたり、技術的な意思決定をしたりといったことを計画的に行っていくことになると思いますが、弊社くらいの規模ですとそこまでの取り組みを本格的に始めていくというのはまだまだ難しいものがあります。

そこで、手始めに各技術領域ごとで現在取り組んでいることや技術的な課題感、ロードマップ、最近興味のある技術領域などを定期的に開発チームにヒアリングして、Qiita:teamにまとめて共有するという取り組みを始めてみました。(delyは全社的にドキュメントをQiita:teamにまとめる文化に最近はなってます)

こんな感じです f:id:delyumemori:20180806160923p:plain

参考にしたのは、The State of Go 最新版へのリンク という1年に1回発表されるGo言語のそのタイミングでの状況をまとめたスライド(発表は実際に拝見したことは無いですが、いつか拝見してみたい)です。このような形で定期的に開発チームの技術的取り組みを概観した資料があったらいいんじゃないかということで、「State of dely dev」(なんでTheが抜けてるんだろう)というタイトルでQiita:teamに記事を投稿しています。

今回はなるべくそれを生に近い形でこちらのブログに転載します。箇条書きでざっくり書いてあるだけですが、雰囲気だけでも感じ取ってもらい、なにかの参考になれば幸いです。なお、この情報は2018年7月時点の情報なので、古い情報も多々含まれていることをご了承ください。


iOSチームの技術的課題

自動テスト

  • なかなかコード量増加のスピードに追い付けていない
  • 将来的にはテストもコーディングと一緒に書いていくスタイルにしたい

自動生成周り

  • サーバー側のswagger.json出力の準備が進んできた
  • JSON API形式の自動生成との相性は悪いが、後は手を動かすだけという状態
  • プロダクト開発とは違う課題が出てきたりして面白い、コーディング的な難しさに直面しがち
  • 使っているライブラリ自体の不具合も出やすい、一回ライブラリにプルリク送ってマージされたりといったことも
  • コミットメッセージを英語で書いたりして、英語への心理的ハードルが下がった
  • 人が少ないからこそ自動化的な方向性に投資していく

チーム開発について

  • 大きいプルリクの問題は解決しているわけではない、一日一回はWIPの状態でもコミット&プッシュして分割レビューの目安にするとかいう手はあるかも
  • 一個もプルリクきてないとSlackで通知を送ったりするのはいいかも
  • Dangerで500行以上のコミットの場合はWarningを出すようにしている

プロトタイプ開発

  • プロトタイプ専用のコードベースでプロトタイプ開発している
  • その中で既存コードを模写しつつリファクタできそうなところを発見したりしている
  • ビルドも速いのでiOSの新しい機能を試したりできる、今度そこで試した機能をリリースする(3Dタッチしたときにプレビュー出したり)
  • Codableでjsonパースするとかも試してみて発見がある
  • 検索画面のUIとかも新しい動きを試してみたりしている
  • アニメーションテストするときとかもいい
  • 将来的にはUIコンポーネントを分離して、それをプロトタイプ開発でブラッシュアップして本体に取り込むみたいなこともしたい
  • ペーパープロトタイピングで認識をすり合わせて作り始められているのでスピード感をもって作れている

ライブラリの更新問題

  • ライブラリが更新されたら自動的にプルリクを作るものを作ったが、なかなかうまくいっていない

Androidチームの技術的課題

自動テストについて

  • Kotlinはテストコードから導入する予定
  • まずはUIテストからやる予定
  • テストコードは気兼ねなく捨てられるのでまずは捨ててもいいからコードを書いていく

Redirectable UIについて

  • ConstraintLayout 1.0にはすっかり慣れた
  • ConstraintLayout 2.0 - MotionLayoutの可能性

アクセシビリティ対応をそろそろ考えたい

  • 文字サイズ大きくしても破綻しないUI
  • 画像にキャプションつけるとか(画面読み上げ対応)
  • 色覚対応
  • 長期的に対応したい
  • とりあえず文字サイズが一番大きい

JetPack対応

  • 今のところすぐ導入すべきものはないが
  • これは~という理由で導入してないと言えるくらいのレベルには理解しておきたい

ビルド遅い問題

  • Firebase、Google Play Servicesのアップデート等でだいぶビルドが遅くなってしまった
  • 都度都度ライブラリの依存関係の見直し等を行うことでビルド遅い問題に対処していきたい
  • マルチモジュール化の検討もすすめる

Webフロントエンドチームの技術的課題

もろもろ進行してます

  • Webpackerへの移行は完了
  • jQuery依存からの脱却は完了
  • SSR導入は決定、作業中

PWA

  • コンパイルされたJSを分割してそのページにあったものをロードするということができるようになった
  • webpackで任意のチャンクに分割してJSを別々にロードすることのできる機能を活用している

SPA

  • レスポンシブ表示で同じ構造で表示するのではなく、PCとSPに最適化された構造に移行していきたい
  • ローディング時に読み込み対象のプレースホルダーを導入したい、今は読み込みでガタガタ動いてしまう
  • デプロイ、テスト遅い問題(デプロイ40分)

バックエンドチームの技術的課題

既存の技術的負債の返済

  • まずはRails 5.0へのアップデート
  • 現状のアーキテクチャでもクラシルのリード過多なアプリであればスケールしていけそう

A/Bテスト

  • API側のA/Bテストも出来るようにする予定

テスト遅い問題

  • テストを早くする方法は分かっているが作業はめんどくさい、ElasticSearch、キャッシュも分離しないと早くできない
  • 実際にどのテストケースが遅いかを調べたりとか
  • データベースのボトルネックになっているクエリを潰しているが、先回りして検出してやりたい

Swagger

  • swagger.jsonの自動生成については目途は立ったのでマイルストーンを立てる
  • iOSとの連携も進行中

API定義をyamlで記述するトレンド

  • APIをyamlで記述するのが流行っている
  • Googleでyamlで書いた構造にAPIに沿っているかテストできるサービスを出したりしている
  • API構造をyamlで記述しているのがメインストリームになりそう
  • その先のアクション(APIを丸ごと自動生成したり)がありそう

機械学習チームの技術的課題

レシピデータの構造化を進める

  • レシピ情報の構造化を進めている
  • レシピの特徴量を出していくにあたって、レシピを入力する側の手順を整理したい。レシピ情報の構造化を進められれば、料理の難易度を判断したりもできそう
  • ルールベースのバリデーションでチェックするなどの方法であれば、そこまでコストがかからなく役にも立ちそう

SREチームの技術的課題、ロードマップ

データ可視化プロジェクト

  • 独自のダッシュボードを作っている
  • Vue.js+Express
  • メインのデータレイクはエターナルポース
  • SQL書かない人が使うようなダッシュボードがほしい
既存のプロダクトについて
  • Metabase、Re:dashなどはSQL書く人用のプロダクト
  • サービスの用語を使ってデータを分析できるようなツールがない
  • グラフについてもプリセットのものでなく、より見やすいものが欲しい
  • 変数についても細かい制御ができない
プロトタイプ開発
  • まずプロトタイプを作って、今クオーター中にプロトタイプをもとに機能追加の土台になるものをリリースし、アジャイルみたいな開発フローに載せたい
  • 一チームいてもいいくらいの規模
  • 1年後を考えた時に、自分たちのサービスの用語で整理された可視化ツールがあった方がいい
  • 現状はレポーティング、データ分析をバラバラ、属人的にやっている、それを統合したい

データレイクの移行(エターナルポース)

  • できた、追加でやることはない
  • 必要になったら過去データ(Firebase, Logpose)の移行もやる

こまごまサーバーインフラ整理プロジェクト

  • こまごまとしている社内システム的なプロジェクトがあり、メンテされていない
  • それらのデプロイがうまくいかなくなっている、方法を統一したい
  • どういうものがあってどのような構成になっているかは把握できている

検索周りの技術的課題

検索アルゴリズム自体の改善

  • バックエンドのロジックのA/Bテストをやるかもしれないので、それが入ったらロジック部分の検証をやるかも

検索ログの分析の知見を開発部以外でも活用する

  • 検索のログデータを活用して、いろいろなところにデータを渡せるようになってきた
  • 日ごとの検索ランキングをSlackで自動で共有したり
  • 検索共有MTGで検索トレンドを共有したりしている、最近はタイアップやマーケの人にも共有している
  • 検索自体の改善もそうだけど、レシピ選定もデータに基づいて行ったりできるようになってきた
  • 調理部の作業フロー自体の改善にも貢献できるようになってきた
  • 最近では、2か月先向けのレシピの提案を行えるようになってきた

検索内容の改善

  • 検索されても存在しないクエリがたくさんあったのが、最近はいい感じに埋まってきた

ちょっと長くなってしまいましたが、delyの開発チームでは最近このような取り組みをしていますというご紹介でした。

delyでは、会社の開発文化自体の発展、促進に興味がある人を募集しています。

(あとAndroidエンジニアも募集してます。)

クラシル、不屈のキャッシュ戦略

こんにちは!

プロダクトマネージャーをしている奥原 (@okutaku0507) です。前までサーバーサイドのリードエンジニアをしていました。

delyの開発ブログが長らく更新されておらず、不甲斐ないです。これからは活発にdelyが取り入れている最新技術や実際にあった事例、取り入れているアーキテクチャなどを中心に発信していきたいと思っています。 

久しぶりの今回は、delyが運営/開発しているレシピ動画サービスであるkurashiruの涙あり、笑いありのキャッシュ戦略について歴史と実際の事例を元に書いていきたいと考えています。最後には、僕が作成したクラシルに用いられているキャッシュ戦略をgemにしたライブラリを紹介いたします。

はじめに

サーバーサイドチームはクラシルの開発から1年半程度まで、主に僕一人しかいませんでした。TVCMによる急激なユーザー数増加や新機能開発社員100人を支える管理サイト開発などのサーバーサイド面を一人で担当せざるを得ませんでした。言い訳になってしまうかもしれないですが、最小工数で最大限いい感じにする必要があり、キャッシュ周りに十分な時間が割けず、他のエンジニアの方々から見れば継ぎ接ぎで不恰好な印象を受けるかも知れません。リソースが恐ろしく枯渇しているスタートアップの一エンジニアの苦悩と格闘の歴史だと思っていただければと思います。もしよければ、建設的な意見をいただくかあるいは仲間に加わっていただき、一緒に改善していければ幸いです。

そして、この記事はこんな方におすすめです。もちろん、キャッシュ戦略自体はどの言語で記述されていても役立つと思います。

  • ユーザー側でDBへのリクエストがほぼリード
  • 同じ画面のリクエストが多く、キャッシュ効率が良い
  • サーバーサイドリソースが恒常的に枯渇状態

 

1. 全リクエストがDBにアクセス時代

ELBなどは省き、必要最低限の構成を描くとこんな感じでした。

 

f:id:okutaku:20180721184937j:plain

 

Railsアプリケーションが一つのデータベースを参照するという、とてもシンプルな構成です。この構成で問題なのが、全てのリクエストでデータベースへ問い合わせを行なっている点です。たとえ同じクエリが走って同じ結果が得られたとしても、全て問い合わせが行われているため、とても効率が悪いです。 

そして、サービスがグロースしていく中で一つの問題が起きはじめました。 

プッシュ打つとサーバー落ちる不可避問題。

全端末をターゲットにプッシュを打つとサーバーが落ちるという問題に行き着きました。その原因として、様々な原因が考えられられ、メモリやCPUなど様々なメトリクスを調べましたが、わからず仕舞いでした。

f:id:okutaku:20180721213947p:plain

f:id:okutaku:20180721214019p:plain

f:id:okutaku:20180721214114p:plain

そんなこんなあり、夜も眠られない日々が続きました...

※ 「ゆうすこ」は弊社代表です 

 

そして、DBがボトルネックになって結果的にレイテンシが上がり、LBのヘルスチェックが落ちているのではないかという考えに至り、キャッシュを導入することになりました。

f:id:okutaku:20180721214736p:plain

何はともあれ、CTOが徹夜でコードを書くという涙ぐましい努力により、僕らはいっときの平和を手にしました。

 

後にSREがジョインし、調べるとリソースが効率的に使われてなかったことも原因だとわかりました。

tech.dely.jp

 

2. オーソドックスなキャッシュ時代

AWSのマネージドサービスであるElastiCacheを導入しました。

クライアントに返すAPIレスポンス(json)を、パラメータを含めたエンドポイント毎にElastiCacheにキャッシュするようにしました。そうすることで、DBへ過度に不要な問い合わせが発生しなくなりました。簡単ですが、構成は以下のようになります。

 

f:id:okutaku:20180721231346j:plain

 

これによりしばらくは枕を高くして寝ることができました。

しかし、現実はそんなに甘くはありません。

弊社ではインシデント級のアラートはイワさんが教えてくれます。これはレイテンシが1秒を超えてるよという恐怖の通知です。この通知がプッシュの時に頻繁にくるようになりました。

f:id:okutaku:20180721232528p:plain

このインシデントについて調査していくと、ElastiCacheのGetHitsというメトリクスが引っかかりました。

f:id:okutaku:20180721233211p:plain

当時はElastiCacheを3台立てていたのですが、この図からわかるように、明らかに1つのサーバーにリクエストが偏っています。また、この時間のRailsのログを調査すると以下のようなエラーが出ていることがわかりました。

W, [2016-08-13T12:13:26.663212 #6100] WARN -- : localhost:11211 failed (count: 0) Timeout::Error: IO timeout
D, [2016-08-13T12:13:27.053149 #6100] DEBUG -- : #<Dalli::NetworkError: Socket operation failed, retrying...>
D, [2016-08-13T12:13:27.553223 #6100] DEBUG -- : Dalli::Server#connect localhost:11211
D, [2016-08-13T12:13:28.311837 #6111] DEBUG -- : #<Dalli::NetworkError: Socket operation failed, retrying...>

f:id:okutaku:20180721234031p:plain

当初、一つのリクエストに対して一意にキャッシュを取得するためのキーが決まるようになっていました。プッシュでホームを開かせるように、特定のリクエストにアクセスが集中する場合、TCPポートの上限に達してポートが足りなくなってしまいます。そうすると、Rails側でキャッシュが上手く取得できず、Railsがタイムアウトするまで待ってしまうため、結果的にレイテンシが上がっていたということになります。

この解決策は単純で、集中してしまうなら分散させればいいと当時は考えました。

cache_key = “test-key”
cacher = Cacher::ObjectCacher.new(key: cache_key)
cacher.write(“test-str”)
# Cache write: test-key-1
# Cache write: test-key-2
# Cache write: test-key-3

上記のようにキャッシュキーの後ろに数字をつけるようにすることで、同じ内容を分散させることができます。厳密に言えば、クラシルで使っているdalliというgemのロジックによるため、均等に分散させることはできませんが、目的は果たすことができました。

こうして、再び弊社に平和が訪れました。

f:id:okutaku:20180721235033p:plain

※ 現在のメトリクスをスクショしたため上と異なります

 

3. キャッシュ分散時代

キャッシュキーの後ろに数字をつけることで、いい感じにキャッシュが分散され、イワさんから怒られることは劇的に減りました。

しかし、まだまだ現実は甘くありませんでした。

たまーにですが、プッシュ時にイワさんから怒られることがありました。

f:id:okutaku:20180721232528p:plain

詳しく調査をすると、どうやらイワさんから怒られる時は、ElastiCacheのキャッシュミスが急激に増加していることがわかりました。つまり、キャッシュ切れです。分散させたのはいいですが、全て同じ有効期限を設定していたが故に、切れる時は分散された全てのキャッシュが切れていたということです。

ならば、有効期限を長くすればいいという話になりますが、

  • タイアップ広告が指定された時間に公開されること
  • キャッシュを長くしてデータの不整合がおきること

などの理由によりそれは難しい状況でした。
また、設計はシンプルに保ちたかったので、ユーザーから来るリクエストのエコシステムの中でいい感じにやってくれることを目指した方が賢明です。

そこで、ヒラメキました!

先ほどの分散されたキャッシュキーに対して、有効期限のグラデーションをつければいいのではないか。つまり、図で書くとこのような感じです。

f:id:okutaku:20180722120709p:plain

この図において、key-1~3は同じ内容をキャッシュしており、キャッシュの有効期限だけが異なっています。この場合、key-1の有効期限が最も短いため、最初にキャッシュ切れを生じますが、有効期限がそれよりも長いkey-2~3は生き残っているので、このキーを参照しているリクエストはDBへの問い合わせを行いません。

また、矢印の色の違いはキャッシュ内容の世代を表しています。例えば、key-1がキャッシュ切れになり、新たにキャッシュの内容を更新しようとした際に、他のkey-2~3も同時に上書きを行います。こうすることで、あと少しでキャッシュ切れになろうとしていたkey-2~3が新たに有効期限が設定されます。この仕組みを繰り返すことで、実質的にキャッシュミスが生じるのは有効期限が最も短いkey-1になり、キャッシュミスになる確率がこのケースでは1/3になります。

cache = ActiveSupport::Cache::DalliStore.new
cache.write('millas', 'cake', expires_in: 1.minutes)
# Cache write: millas-1 ({:expires_in=>60 seconds})
# Cache write: millas-2 ({:expires_in=>120 seconds})
# Cache write: millas-3 ({:expires_in=>180 seconds})

これで、弊社に恒久の平和が訪れました。

エンジニアはプッシュの度に怯えた日々を過ごすことなく、自分がリソースを割くべきことに集中することができるようになりました。

ここまでのストーリーをQiitaにも書きましたので、リンクを貼っておきます。また、この仕組みをRailsに簡単に導入することができるgemも作ったので、Railsエンジニアのリソースが枯渇していて、クラシルのようにリードが激しいメディアの方、参考にしてもらえれば幸いです。

qiita.com

github.com

 

追記

このキャッシュ切れの問題をThundering Herd問題と言うようです。

Kazuho@Cybozu Labs: キャッシュシステムの Thundering Herd 問題

 

4. キャッシュ分散グラデーション時代

「ついに平和が訪れた」と誰しもが確信していました。

しかし、平和というものはそんなに永くは続きませんでした。

上記の対応をした後でも、ごくごく稀にプッシュ時にイワさんから怒られることがありました。まだ敵いたの!?という感じの少年ジャンプみたいな世界観。

f:id:okutaku:20180721232528p:plain

※ ちなみに、イワさんはルフィの心強い味方です

 

原因を精査すると、理由は同様でキャッシュ切れのようです。よくよく考えると、有効期限をグラデーションしたとしても、一つのキャッシュに割り当てられるリクエストは1/nになります。つまり、図で書くとこういうことです。この図で注目して欲しいのは、1つのキャッシュキーに割り当てられるリクエストの割合が、3割強ということです。

f:id:okutaku:20180723133132p:plain

例えば、プッシュ時に10,000のリクエストが来た際にキャッシュ切れが生じると、約3,000リクエストがElastiCacheをすり抜けてDBへ問い合わせることになります。これはまずいぞ、ということになります。

ここでもヒラメキました!

このリクエストを各キャッシュキーに割り当てる配分にもグラデーションをかければいいのではないかと考えました。キャッシュの有効期限が短いキャッシュキーほど少ないリクエストを受け取るよう、いい感じのロジックを作ってリクエスト数にグラデーションをかけることにしました。図にすることこのような感じになります。

f:id:okutaku:20180722140756p:plain

この図が意味するところは、キャッシュキーの後ろにつける数字 (magic number) が小さいほど、頻度 (リクエストが割り当て割れる配分) が低くなるということです。上の図と照らし合わせると、有効期限が最も短いキャッシュキーに割り当てられるリクエストの割合が一番小さいということを意味します。つまり、キャッシュ切れが生じるリクエストの割合が数%になることで、ほとんどのリクエストがDBに問い合わせにいくことがなくなりました。

これを導入してから、キャッシュ切れが生じることは全く無くなりました。

本当に本当の平和が弊社を包み込みました。

 

終わりに

delyでは、技術を使って世の中に幸せを届けたいと一緒に戦ってくれるエンジニアを強く募集しています。どのポジションも貴重なリソースが限られているベンチャーでは、やりたいことが山のようにある中で何に注力するか、そしてそれを如何にして最速でユーザーに届けられるかが重要になります。その中でも自分の強みを活かして頭がちぎれるまで考えて実装することが僕らエンジニアの見せ所だと思っています。

 

一方で、新しいソリューションも積極的に取り入れ、本番環境に適応しています。その取り組みの一つが評価され、AWS Summit Tokyo 2018で弊社CTOが基調講演させていただいた動画を貼っておきます。


Day3 基調講演(日本語同時通訳) | AWS Summit Tokyo 2018

 

僕らはサービスを伸ばしつつ、エンジニア個人が技術的に挑戦したいことに、どんどんチャレンジして、サービスとともに成長していきたいと強く思っています。エンジニアに限らず、そのようなマインドをお持ちの方と一緒に仕事ができる日を心待ちにしております。是非一度お茶でもさせてください。 

 

 デザインブログも始めました。

 

kurashiruの検索UX改善プロジェクト

こんにちは。
delyでISE(In-house System Engineer)をやっている@_skuwaです。
kurashiru[クラシル]のグロース、プロダクト改善の為の基盤の設計・開発を行っています。

今日はユーザーの検索行動のUXを向上させるために立ち上がった、検索改善プロジェクトについて書こうと思います。

検索機能における、UX上の課題点

  • 検索したものの、レシピ数が少ない
  • 豚肉、パスタなどの曖昧なワードでの検索時に結果が多すぎる(ユーザーが求めているレシピの割合が低くなってしまう)
  • 明らかに不適切なレシピが検索結果に混ざり込んでしまっている

その結果、検索後の離脱率(動画再生せずに直帰した割合)が高い状態になっていました。

課題解決へのアプローチ

delyでは基本的にグロースは開発チームが行っています。
しかし、今回の検索はドメインの専門知識があったほうが質の良い仮説が立てられるということで、調理部などの各部署と連携したプロジェクトが立ち上がりました。

やったこと

  • 部署横断での検索改善プロジェクトチームの立ち上げ
  • 料理人の方々がPDCAを回せるように、SQLを書かなくても主要なデータ分析を行えるWebアプリの作成(Mondoru)
  • Mondoru上でElasticsearchの辞書、シノニム管理
  • 数値やKPIのSlack通知
  • 検索数が多いのに結果が少ないキーワードを抽出し、レシピを開発

Mondoru

delyではリポジトリ名やアプリケーション名をワンピースのキャラにするという習慣があるので、今回はそれっぽい『シャーロット・モンドール』から名前を付けました。

Mondoruには主に以下の機能があります。

  • 辞書、シノニムの追加
  • スコアリングの調整
  • 上記チューニングでの検索結果をテストする
  • Elasitcsearchの管理
  • 検索離脱率などの主要データを画面をポチポチして集計、出力する
  • データのビジュアライズ
  • その日の検索データ速報をSlackへ通知

改善でやったこと、成果

基本的には季節ごとの検索傾向や離脱率を見ながら辞書とシノニムを追加していくことになります。
調整した検索ワードの離脱率と動画再生画面への遷移数の変化を見ながら、ベストな検索結果を探っていきます。

f:id:skuwa:20170912210722p:plain

日々の検索データを眺めながらノイズの割合が多いものに関しては辞書とシノニムを活用したチューニングを、検索結果数が少ないものは該当キーワード向けのレシピ開発を行います。

↓検索結果改善によって、検索後に動画再生される割合が急速に増えてきています。 f:id:skuwa:20170912211633p:plain

今後の課題

レシピ開発によってニーズのある検索結果を充実させ、辞書やシノニムで検索結果に含まれるノイズを減らすことに成功しました。

ただ、冒頭に述べたように検索結果が多すぎる時にユーザーに刺さるレシピの割合が少ないという問題は解決できていません。データで見ても、具体的な料理名での離脱率は減少していますが一般的な食材名での離脱率はあまり変化が見られません。

そこで、検索関連のユーザー行動データを元にしたサジェスト機能を実装中です。

サジェストには2種類あって、絞り込みのサジェストと置き換えのサジェストがあります。

絞り込みサジェスト

『パスタ』を入力したユーザーに対して、

  • 『パスタ トマトソース』
  • 『パスタ 魚介』

をサジェストする。

一般的なサジェスト機能です。
キーワードの検索数や、検索結果の件数などを元にスコアリングして表示を行います。

置き換えサジェスト

『豚肉』を入力したユーザーに対して、

  • 『豚こま肉』
  • 『豚ひき肉』

をサジェストする。

『豚肉』といったカテゴリに近い検索ワードは、サジェストでより細かいカテゴリに置き換えすることで検索結果を最適化できるようにします。

冷蔵庫に『豚こま肉』が入っているユーザーが『豚肉』で検索しようとした際に、『豚こま肉』をサジェストすることで、より最適な検索結果にユーザーを誘導することが可能です。

まとめ

ドメインの専門知識を活かすグロース基盤を作ることで質の良い仮説を立てることができ、『超速』でのグロースをすることができました。

delyではその他にもグロースの質と速度を爆速にする基盤やアプリケーションの開発に投資をしており、Mondoruのようなアプリケーションの開発を多数行っています。

世界最速のグロースに挑戦したいという方、是非応募してみてください!

www.wantedly.com

SRE育成プロジェクト(APOLLO計画)でアストロノート1号になってみた!!

iOSエンジニアの西川です! kurashiru(クラシル)のiOSアプリの開発を担当しています。

前回のdely engineering blogで紹介させていただいたAPOLLO計画にアストロノート一号として選ばれた僕がこの計画の感想を書いていきたいと思います。

未来のSREを最速で育てる!dely流SRE育成術、APOLLO計画の紹介

APOLLO計画ではSRE候補者のことをアストロノートと呼んでいます。

f:id:delyjp:20170815144834j:plain

アストロノート1号への道のり

私のdelyでの業務は冒頭でも述べた通りkurashiru(クラシル)のiOSアプリ開発です。 しかし、delyで日々過ごしているうちにkurashiru(クラシル)の大規模トラフィックを捌くSREの業務に興味がでてきました。 そこでSREの業務に興味があることを伝え、今回のこのAPOLLO計画に参加することになりました。

APOLLO計画

1.インフラ構築

APOLLO計画を受ける前の僕のスキルセットはアプリAPI用のVPSサーバーを構築した程度でAWSについてはほとんど知識がない状態でした。

なので事前にAWSの用語の知識を簡単にまとめてくれたものを渡してもらいそれを頭にいれて本番にいどみました。そのおかげでインフラ構築自体はスムーズに進んでいきました。

具体的にはElastic Load Balancerの作成、EC2インスタンスの作成、セキュリティグループやAuto Scalingグループの作成です。

しかし、それぞれがどのように動作していてどういう役割をしているのかを一人で理解することは厳しかったのでその都度質問していました。質問するとすぐに的確な答えがかえってくるのでその場で理解することができました。

またどこまでが正常に動いていてどこが原因で動いていないかを確認するためにNginxのアクセスログやFluentdのエラーログの見方などを最後に教わりました。iOS開発でもデバッグのためにログをみますが、それとは違い”こうゆうところを見るのか〜”ととても新鮮で学ぶことが多かったです。

2.障害対応

いよいよ本番です。課題内容の簡単な説明を受け障害対応スタートです。

今回の障害は

EC2インスタンス内での変更作業によって、正常稼働しないAMIがリリースされました。障害から復旧させ、原因の追求と対応をしてください。

という内容のものでした。

障害対応中はslackで分報チャンネルを作ってそこに今どんな作業をやっていて、どんなことを考えながらその作業をしているのかを書いていきます。これをやることでどの部分で詰まっているかを伝えることができるとともに障害のログを残せるのでもし同じことが起きたときにすぐ対処ができます。

まずはどこにボトルネックがあるのかの検証を行っていきました。具体的にはnginxのアクセスログをtailしながらcurlでリクエストを送ってみてどうなるかや、td-agentのログを追ってみたりしていました。実際にやってみるとエラーをみつけることはできるもののそれがどんな要因でエラーになっているのかをなかなか突き止めることができません。ヒントをもらいながら試行錯誤して動いていない部分を特定していきます。思っていた以上に大変で時間だけがどんどん過ぎていき焦りながら必死でやっていました。結局すべての問題を解決することはできずに終わりましたが、今起きている障害にどういうフローで対処していったらいいのかを実際に体験できたのは本当に貴重な体験になりました。

反省としては今起きている障害がどんな障害かを理解するために時間を使い過ぎたことです。 優先させるべきはいち早く正常な状態に回復させ、被害の影響を最小限に抑えることでした。

3.振り返り

今回このAPOLLO計画に参加して学んだことは以下の2つです。

  • AWSのEC2インスタンスやELBの仕組みが今までより明確になった
  • 障害が起きたときのデバッグの仕方を実際に体験でき、障害が起きそうな部分、ボトルネックになりそうな部分を見分ける思考が少し身についた

このAPOLLO計画を繰り返すことによってSREとしてスキルを最速で身につけることができる可能性を感じました。

delyではAPOLLO計画でSREになってみたいというエンジニアの方、我こそはという最強SREを募集しています。 是非Wantedlyから応募してください!!

www.wantedly.com

www.wantedly.com

未来のSREを最速で育てる!dely流SRE育成術、APOLLO計画の紹介

こんにちは。SREの@_skuwaです。
kurashiru(クラシル)を支えるインフラ作りや、ミドルウェアの開発等をやっています。

今日はdely SREチームが行っている障害対応訓練、APOLLO計画をご紹介します。

f:id:skuwa:20170804000901j:plain

SREチームの課題

クラシルは他に類を見ないスピードで成長しているサービスのため、一般的には半年から数年ごとに行わなければいけないような負荷対策を数週間でどんどん行っていく必要があります。
SREとしては非常に高いスキルや経験を要求されますが、それを持ち合わせているSREが市場的にはあまりいないというのが課題でした。

私はdelyに入社した時点ではSREとしてのスキルは持っていませんでしたが、入社して半年程度でSRE業務を行えるまでになりました。

私がSREとして戦えるまでに培ってきたノウハウを元に作った、最速でSREを育てる教育体制がAPOLLO計画です。

APOLLO計画

APOLLO計画の名前の由来ははNASAのアポロ計画です。
人類の月面着陸という困難なミッションを実現させたアポロ計画のように、SREを短期間で育成するという大きな目標を達成させたいという願いを込めての命名です。

APOLLO計画では大事にしているポイントが3つあります。

  • コマンドやコードに関しての知識など、大前提として必要な知識を身に付けてもらうこと。
  • “事実確認→仮説→検証→再発防止策の検討"というフローを徹底的に叩き込むこと。
  • 障害が起きそうな部分、ボトルネックになりそうな部分を嗅ぎ分ける嗅覚を身に付けてもらうこと。

各ポイントについて、APOLLO計画ではどのように教育しているのかをご紹介します。

1. ペアインフラ構築

APOLLO計画を受けてもらうエンジニアの多くはクラウド環境やインフラ構成についての知識がありません。
私と一緒に本番と同等の環境を作成するところからがスタートです。

どういった経緯があって今の構成になったのか、そもそもロードバランサーってどういう役割なの?みたいなところを解説しながら、0からの構築をしていきます。 その際には、少しでも疑問がある箇所、気になった箇所は必ず質問してもらうようにしています。

また、構築後は簡単なシェルスクリプトやcurlなどを利用して動作の仕組みを追ってもらいます。
ここでLinuxコマンドやアプリケーションの仕組みを知ってもらうとともに、どこまでが動いていて、どこまでが動いていないのかを把握できるようになってもらいます。
Linuxコマンドやシェルを1から覚えるのは非常に大変ですが、実際の業務では一部しか使わないことが多いです。そのため、より実践的なテクニックをペアプロ形式で覚えてもらいます。

2. 小さな課題をたくさんこなす

実際の障害では何かがトリガーとなって連鎖的に問題が発生するケースが多いです。しかし、いきなりそれを解決してもらうのは無理があります。
そこで、まずは1つのトリガーによってどこかが停止してしまうといったケースの課題をたくさん用意し、それを解決してもらうということに取り組んでもらいます。

具体的には設定ファイルの記述ミス、セキュリティグループの設定ミス、最適化されていないコードによるパフォーマンス低下等です。

”こういったことがトリガーになって、こんな現象が発生するのか〜”という知見を得てもらいます。
どこまでが動いていて、どこから動いていないのか。じゃあありえる可能性は?といった思考フローを徹底的に叩き込みます。

”あ、これAPOLLO計画でやった問題だ!”

と、実際の業務で思ってもらえることが理想です。

3. 実際に発生した障害の完全再現

まずは弊社の環境(AWS、Nginx、Fluentd、Rails、Goによるアプリケーションサーバー等)でしっかりとSRE業務を行えるようになることにフォーカスを当てています。
弊社ではオライリーのSRE本を参考にして、障害発生時にはPostmortemを残しています。
(Postmortemに関してはSRE本の15章に詳しく書かれています。英語版しかありませんが、8月12日に日本語版が発売予定です。)

Postmortemを元に実際に発生した障害を完全再現して、それの解決を課題として取り組んでもらっています。
障害を再現するのは運用側のコストがかなりかかるので、VacuumDecayというGo製のコマンドラインツールを作成し、コマンド一つで可能な限り障害を再現させられるような体制を作っています。

ここまで学習してきたことを活かしながら、実際の障害対応と同様の体制で復旧してもらいます。
取り組んでいるエンジニア達は、まずはボトルネックやどこで問題が起きているのかを必死に追っていこうとします。
しかし、これは実際の障害です。まず優先させるべきは正常な状態に回復させること、被害の影響を最小限に抑えることです。
そのため、積極的に経過時間を教えてプレッシャーを与えていきます。

影響範囲がこれぐらいで、ユーザーはもう戻ってこないかもしれないね〜、リテンションめっちゃ落ちそうだよね〜と、どんどんプレッシャーを与えます。
こうした中で課題に取り組んでもらうことで、SREとして大事な"システムを正常稼働させることの重要性"を知ってもらいます。

また、実際の障害対応と同様の体制での作業となるのでPostmortemをしっかり書いてもらいます。 そこで自分の行動の振り返りと、同様のことが二度と発生しないための仕組みづくりや運用方法を検討してもらいます。delyのエンジニアは本当に優秀なので、このレベルになってくると、私もなるほどそういう考えがあったのかと気付かされることも多いです。

この課題に取り組んでもらうことで、インフラエンジニアではなく、SREとしての仕事を覚えてもらいます。

新しい機能の実装は毎日のように行われていますが、障害はあまり発生しません。
そのため、一般的には障害対応やリスク予知のノウハウというのは短時間で身につきにくいものです。
ですが、delyでは障害をPostmortemとして記録し、”資産”として活用することによって、スピード感のあるSREチームを作っています。

最後にSREチームの紹介と募集

delyのSREチームは、超スピードで成長するクラシルを支えるチームです。

  • Rails、Go製アプリケーションの運用
  • 開発チームの作業効率アップのための開発環境作り
  • プッシュ配信基盤等のミドルウェア開発
  • FluentdやKinesisを利用したログ集計基盤、分析基盤の構築
  • CloudWatchやPrometheusでの監視基盤の構築
  • kubernetesなどのコンテナ技術の導入

など、業務内容は多岐にわたります。

組織も、プロダクトも、そしてAPOLLO計画もまだまだ未熟ではありますが、今のフェーズから一緒に世界一イケてるSREチームを作ってくれるメンバーを募集しています。
我こそはという最強SRE、APOLLO計画でSREになってみたいというエンジニアの方!
是非Wantedlyから応募してください!!

www.wantedly.com

www.wantedly.com

1台あたり10,000人を捌くRails製Webサーバのチューニング

SREの深尾です。kurashiru [クラシル] のインフラを担当しています。

タイトルのとおり、クラシルのwebサイトではRailsを使っており、1サーバあたり10,000人程度のアクセスに耐えることができます。実際には余裕を持たせて5,000人/サーバを目安にスケールさせており、TV CMをガンガンやったり、国内外のTV番組で特集されたり、芸能人にSNSで拡散されても動じませんが、実は過去に1度だけWebサイトがダウンしてしまったことがあります。それは2017年3月11日にSmaSTATION!!というTV番組でクラシルが取り上げられた時のことでした。

以下はその時のリクエスト数を表すグラフです。ダウンしてしまったので計測できなかったユーザの数字は含まれませんがそれでもアクセス数は1分で数万人を超えていました。

f:id:motobrew:20170612121013p:plain

それまで、Webサイトの負荷対策はあまり行っておらず、2台のWebサーバをLoad Balancerに繋いで冗長化しているだけでした。まず負荷対策においてサーバの数を増やすだけではダメなのかということについて考えたとき、以下の理由からサーバを増やすだけではなくボトルネックを調査した方が良いと言えます。

  • ボトルネックがわからないとスケーラビリティを保証できない
  • 仕組みを理解していないと障害原因の特定が難しい
  • 1台あたりの限界がわからないと適切にサイジングできない
  • リソースを使い切れないとインフラ費用が何倍にも膨れ上がる

スマステでサービスがダウンした時、CPU使用率が20%を超えることはなく以下のようなエラーログを出し、nginxが502を返していました。

[error] 2820#0: *10321 connect() to unix:/tmp/web/unicorn.sock failed (111: Connection refused) while connecting to upstream,

そこでCPUの利用効率を上げるために同時に捌くリクエストの数を増やすことを考えました。

 Unicornのプロセス数を増やす

Unicornは1プロセスあたり1リクエストずつ捌く設計になっています。そのため1サーバあたり同時に捌けるリクエストはworker_processesで設定された値になります。

デフォルトでは5になっていました。これを増やせば同時に捌くリクエストを増やすことができます。どれくらいが適切かはアプリケーションによって変わります。例えば、リクエストの裏側でDBや全文検索エンジンなど、別のサーバにリクエストを投げてそのレスポンスを待っている間は、Rails側のサーバのCPUは使われません。その分Rails側のCPUは余裕があることになりますが、プロセス数を増やすとメモリ使用量も増えます。仮にメモリ使用率が100%近くになってしまうと、OSが使うファイルシステムキャッシュのメモリが少なくなってパフォーマンスが悪くなってしまうことがあります。そのため、以下のことを考慮しながら、本番環境でレイテンシなどのパフォーマンスを測りながら細かくチューニングすると最適な値が見つかると思います。

  • プロセス数に比例してメモリ使用量が増える
  • コア数に対して多すぎるとロードアベレージが上がり、オーバーヘッドが大きくなる
  • メモリの未使用領域はファイルシステムキャッシュに利用される

プロセス数を増やすことでCPUを使いきれるかと思ったのですが、abコマンドで簡単な負荷テストを実施したところ、実際には同時接続がおよそ1000を超えるとCPUやメモリはまだ余裕があるのにnginxがエラーを返していました。そこで次のチューニングです。

UNIXソケットのバックログを増やす

瞬間的におよそ1000を超えるリクエストがきた場合はエラーになってしまうことがわかりましたが、1台のサーバで同時に処理できるリクエストはUnicornのプロセス数なので、プロセス数を超えるリクエストは1000ぐらいまでどこかで順番待ちをしていることになります。

弊社ではNginxからUnicornへリバースプロキシにUNIXソケットを利用しており、Unicornでこのソケットのbacklogがデフォルトで1024になっているのではないかという仮説を立てました。つまり、backlogを増やせば一時的に数千以上のリクエストが来ても順番待ちをさせて返すことができるのではないかというわけです。そこで環境変数で以下のように設定します。

unicorn socket backlog

backlogを4096に変更したところ、1500や2000以上のリクエストを同時に投げてもエラーにならずに(少し時間はかかるけれど)正常なHTTPレスポンスを返すことができました。また、負荷テストを行ったところ、CPUを100%近くまで使える状態になりました。

最後に

f:id:motobrew:20170621213240j:plain

このようにボトルネックを1つずつ解消するというプロセスを繰り返すことでハードウェアリソースを使い切ることができます。また上記以外にNginxのキャッシュを利用して、瞬間的なアクセスにはNginxのキャッシュを返すことで1台あたり10,000人程度でも高速に捌けるサーバとなっています。最後にサーバのサイズとプロセス数とバックログの参考値を以下に記載します。

  • サーバ:CPU2コア、メモリ8GB
  • worker_processes:24
  • backlog:8192 

 

delyでは、急速なサービス成長を支えるSREを募集しています。

www.wantedly.com

www.wantedly.com