TECH PLAY

dely株式会社

dely株式会社 の技術ブログ

236

こんにちは: ) delyで検索エンジニアをしているsakura ( @818uuu ) です。この記事は dely Advent Calendar 2018 の5日目の記事です。 4日目の記事は、SREの井上の「 超手軽に構築する!サーバレスなWEBパフォーマンス定点観測基盤 」でした。WEBパフォーマンスの定点観測・分析に興味がある方はぜひ読んでみてください。 tech.dely.jp はじめに 私はdelyに入社した2018年1月から検索エンジニアをしています。 delyが運営している料理動画アプリ「クラシル」では訪れたユーザーのほとんどが検索をする、いわば検索がコアになっているサービスです。 検索エンジニアとしてその検索体験を少しでも向上させるべく日々試行錯誤しています。 弊社のエンジニアが検索体験について語っているインタビュー記事もありますので、ぜひご覧ください。 careerhack.en-japan.com 私が入社する前まで弊社には検索エンジニアという役職は存在せず、他の役職のエンジニアが検索部分を掛け持ちで担当していました。 従って、会社にとって検索エンジニアという役職を置くのは初めての試みでしたし、私自身も検索エンジニアという役職についたのは今回が初めてです。 この記事ではクラシルの検索を改善するために、検索エンジニアとして1年間クラシルの検索に向き合った記録をお届けします。 検索エンジニアを目指している方やサービスを運営していて「検索機能は提供しているけどなかなか手をつけれていない」と悩みを抱えている方にとってこの記事が少しでもご参考になると幸いです。 目次 はじめに 目次 検索離脱率の改善 検索結果0件のキーワードの削減 人気のキーワードの機能追加 検索品質ガイドラインの策定 検索絞り込み機能の追加 検索情報のリサーチと共有 さいごに 検索離脱率の改善 結果:辞書シノニムの整備を行い、検索離脱率を5%低下させることに成功しました🎊 検索エンジニアになって特に気にかけたのがこの検索離脱率という指標でした。入社当初は「一般的な検索離脱率が何%くらいなのか」という知見すらなかったため、その数値が高いのか低いのかすら判断ができませんでした。 調査を進めていくうちに、検索離脱率が高い一番の理由は辞書シノニムであることに気づきました。 辞書シノニム管理の運用についての詳細はこちらに記載しています。 tech.dely.jp 検索キーワードごとに検索離脱率を出し、検索離脱率が高いキーワードから優先的に対処していきました。 辞書シノニム改善をはじめてすぐに結果が表れ、運用を地道に続けていると結果的に検索離脱率を5%以上低下させることができました。 検索結果0件のキーワードの削減 結果:検索結果0件のキーワードを1000個以上削減・対応し、ユーザーの検索体験を向上させることが出来ました📒 クラシルには23,000件以上のレシピ動画があり一般的な料理はほぼ網羅していますが、たまに検索結果が0件のキーワードが見つかります。 「今流行りのチーズドッグを作りたい!」と思って「チーズドッグ」と検索してくれたとしても検索結果が0件だと、検索体験は非常に悪いですよね。 検索結果が0件の要因は主に2つあります。 1つは先ほど上述した辞書シノニムが未整備の場合、もう1つはそのキーワードに合致するレシピがそもそも存在しない場合です。 キーワードに合致するレシピがそもそも存在しない場合は、調理部さんと相談してレシピ動画を作成していただきます。 検索結果が0件キーワードの一覧の生データをただ渡すだけだとレシピのかぶりが発生してしまったり、実は既にレシピ作成中だったりして手間が発生してしまうので、調理部さんにも定期的にアドバイスを伺い工夫しながらデータをお渡ししています。 辞書シノニムの改善と新たにレシピを作成してもらったことで1年間で少なくとも1000個以上のキーワードに対応することができました。 『とろーり チーズドッグ』も調理部さんと相談して作成していただいたレシピのひとつです。今ではたべれぽが50件以上ある人気のレシピです。 人気のキーワードの機能追加 結果:人気のキーワードの機能追加により、入力の手間を省きタップするだけで人気のキーワードで検索できるようになりました👆 また、この機能は分析・改善もうまくいっています。 今年新しくリリースされた機能の1つに、"人気のキーワードの表示"があります。 クラシル内でよく検索されているキーワードや急上昇しているキーワードを表示する機能です。 クラシルの人気のキーワード機能🎖 1.検索窓をタップ 2.人気のキーワードが表示されます。今回は「ほうれん草」をタップ👆 3.タップしただけで簡単にほうれん草の検索結果が見れます 入力するのすらめんどくさい方におすすめです pic.twitter.com/DYTZTP4fiW — sakura@検索 (@818uuu) 2018年12月5日 ハロウィンなど特別なイベント時にはそのイベントに関連する人気のキーワードを表示するようにしました。 この施策はとても数値がよかったです🎈 ハロウィンの時の人気のキーワード 人気の検索キーワードでどのキーワードがクリックされやすいかなどを分析し、それに基づいて運用方法を改善したり新たな施策を考えたりしています。そして、その分析結果を定期的に社内共有サービスにレポートしています。 人気の検索キーワード(新規機能)の分析を始めた際に学んだことを共有しておきます。 新規機能をリリースした後「全体の何%にこの機能は使われているのか」や「どれくらいの人がこの機能を使ってくれているのか」などを必ず調べると思います。 しかしリリースしてすぐに分析してしまうと、まだアプリのバージョンをアップデートしていないユーザーも多く正確な概算値を測ることができません。 例えば、人気のキーワード機能はリリースしてから約10日間経ってからイベント数が落ち着きました。 人気のキーワードのイベント数推移 : 2週間ほど経ってから数値が落ち着きました 他の新規機能でも約2週間経ってから数値が落ち着いた事例があります。つまり、 新規機能は2週間以上たってはじめて全体の何%に使われているのかがわかる ということです。 (私はこのことを知らずにリリースして1週間で速報値をレポートしてしまいました😨) クラシルでは開発のサイクルがとてもはやい反面、既にリリースされた機能に対してケアできていないことも何度かありました。 人気の検索キーワードはリリースされてからも継続的に分析することでうまく改善を回せている好例だと思います。 検索品質ガイドラインの策定 結果:検索品質ガイドラインを策定し、クラシルの検索の方向性をドキュメント化しました👣ただし運用に難ありです。 Googleには検索エンジンの品質を評価するために 検索品質ガイドライン を設けています。Googleのを参考にクラシルでも検索品質ガイドラインを策定してみる挑戦もしました。 Google General Guidelines(Google検索品質評価ガイドライン) tech.dely.jp ただ、これは作ったばかりで改善の余地が多いのと運用がままなっていないという現状があります。(それだけ伸びしろがあるということですね…笑) 検索絞り込み機能の追加 結果:検索絞り込み機能がリリースされましたが、想定より使用してくれるユーザー数が少なかったです😔 今年から検索絞り込み機能も増えました。 含めたくないキーワードやジャンル、15分以内で作れる料理など特定の条件に応じて絞り込むことができる機能です。 検索絞り込み機能は、ユーザーからこういう機能がほしい!とアプリレビューやCSを通して要望のあった機能のひとつです。もちろん社内でもこんな機能があればいいね〜と案で上がっていました。 私は主に分析を担当したのですが、実際にリリースしてみると想定より利用者数がものすごく少なかったのです。 具体的にいうと検索ユーザーの数%(一桁)しか絞り込み機能を利用していなかったのです。 ジャンルの精度が悪いのかな?と思い精度をほぼ100%にしましたがそれでも使ってくれるユーザーの数は増えませんでした。 数値を見守り続けたところ、少ないのではなくこれが定常的な値ということがわかりました。 検索をした後になにかタップする障壁がいかに高いのかを思い知らされた反面、通常の検索でユーザーが求めているものを検索結果を叶えなければ…と強く感じるのでした。 クラシルの検索絞り込み機能の紹介🎄 1.まずはキーワード検索!今回は「豚肉」で検索します 2.「絞り込み」をタップ👆 3.豚肉の主食レシピを探したいので「主食」を選択! 4.簡単でおいしい豚肉の主食レシピを見ることができます😋 pic.twitter.com/HXZsqHxbu9 — sakura@検索 (@818uuu) 2018年12月5日 検索情報のリサーチと共有 結果:情報収集を地道に続けた結果、クラシルの検索ログを深い視点で見ることができるようになりました! 検索エンジニアはユーザーがどんなキーワードで検索しているか毎日チェックします👀 クラシルの検索キーワードのログから以下のことを確認します。 ユーザーがどんな食材でレシピを探しているのか どんなキーワードが急上昇しているか いまトレンドの食材/メニューはなにか 検索下位にはどんなキーワードが入っているか(辞書シノニムが未整備の可能性が高いため) 検索ログの意味合いをきちんと読み取るために、世間一般の食にまつわる情報をたくさん検索し情報収集しています。 ユーザーはクラシル以外にも無数の食との接触点を持っています。 「新大久保で流行っているメニューをクラシルでも作りたい」「テレビで〇〇がダイエットに効くって評判だけどクラシルでもその食材を使ったレシピあるかな?」などそこから食べたいものや作りたいレシピを決めることも多いでしょう。 食との接触点一覧 Twitter instagram Youtube グルメサイト ニュースサイト ブログ 流行りの飲食店 など 「この情報収集する作業本当に価値を生んでいるのかな?」と思うときもあったのですが、地道に続けているとクラシルの検索ログがどのキーワードでどのくらい影響を受けるのかが少しずつわかってきました👏その他にも副次的に良い効果がたくさん生まれました! 上記で調べた情報は社内に定期的に共有しています📘 検索情報共有の一部 さいごに クラシルの検索を改善するために、検索エンジニアとして1年間クラシルの検索に向き合った記録をお届けしました。 いかがでしたでしょうか。 この記事を一言で伝えると、 「自社の検索の性質をよく把握し、その性質に合わせて検索改善・分析をすることが重要」 ということです。 検索エンジニア1年目を振り返ると、ものすごく手探りで自分の無力さを感じた1年間でした。 経験・技術力不足でなかなかうまくいかなかったり、周りに同じ職業の相談相手がいなくて悩むことがとても多かったり…… 。 でも、この1年間やってきたことは無駄ではなかったと思います。無駄ではなかったと思いたいし、思い切りたいです。 検索エンジニア2年目は1年目よりももっと検索をよくできると信じています。 そして、いつか胸を張って検索エンジニアですといえるようになりたいです。 明日は、弊社のAndroidエンジニアの川口より「 Androidにいい感じの動きをさせていく話(ViewPagerとその他Viewとの連動) - dely engineering blog 」の話が書かれます。是非、お楽しみにしていてください!
アバター
はじめに 本記事はdely Advent Calendar 2018の4日目の記事です。 dely Advent Calendar 2018 - Adventar dely Advent Calendar 2018 - Qiita 昨日は弊社の機械学習エンジニアの辻がNixOSについての記事を書きましたので興味のある方は是非読んでみてください。 tech.dely.jp こんにちは!delyでSREをやっている井上です。 本記事では、WEBパフォーマンスの定点観測の仕組みを手軽に構築出来るようにしたのでその紹介をしたいと思います。 本記事では下記のサービスやツールを利用しています。 AWS Terraform sitespeed.io 前置きはいいから構築方法だけ知りたいという方は こちら! 目次 はじめに 目次 WEBパフォーマンスについて sitespeed.ioについて 主な機能 各種スコア Waterfall Visual Metrics Browser Metrics レンダリング時の動画再生 スコアに対するアドバイスの表示 レスポンスの遅いリクエスト サイズの大きい画像やjavascript 主な特徴 定点観測について 定点観測の方法 CodeBuildのメリット 定点観測基盤の構築方法 構成図 前提 準備 1.レポジトリのフォーク 2.Terraformのtfstateファイル管理用のS3バケットの作成 3.terraform.tfvarsの作成 4.backend.tfvarsの作成 5.Terraform作業ディレクトリの初期化 6.対象サイトの指定 構築 sitespeed.ioによる計測結果の参照 HTML SQL VIEW作成 Redashによる可視化例 カスタマイズ sitespeed.ioの試行回数の調整 計測間隔の調整 料金について さいごに WEBパフォーマンスについて 2018年7月のGoogleのスピードアップデートでも分かる通り、WEBページの読み込み速度はPCかモバイルかに関わらずより一層重要視されてきています。 読み込み速度の遅いWEBページを改善しようとなったとき、まずはじめに現状を把握する必要がありそのためにはWEBのパフォーマンス計測を行う必要があります。 現在、WEBのパフォーマンスを計測する手段としては google chromeのLighthouse機能 WebPageTest( https://www.webpagetest.org ) PageSpeed Insights( https://developers.google.com/speed/pagespeed/insights/ ) などいくつかありますが、本記事では現在delyで利用しているsitespeed.ioというツールを紹介したいと思います。 sitespeed.ioについて sitespeed.ioはオープンソースのWEBパフォーマンス計測ツールです。 www.sitespeed.io 主な機能 sitespeed.ioを使うことで下記のようなデータをWEBブラウザで閲覧することが可能になります。 各種スコア Waterfall Visual Metrics Browser Metrics レンダリング時の動画再生 スコアに対するアドバイスの表示 レスポンスの遅いリクエスト サイズの大きい画像やjavascript 主な特徴 また前段で挙げたWEBパフォーマンス計測ツールと違い、下記のような特徴を持っています。 Dockerによる簡単な実行 公式のdocker imageがあるので、下記のコマンドによって簡単に実行することが可能です。 $ docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:7.7.2 https://www.sitespeed.io/ 結果のhtml出力 実行後、計測結果がhtmlで出力されているのでWEBブラウザで簡単に閲覧することが可能になっています。 ※ YYYY-MM-DD-HH-mm-ss は実行時刻が入ります $ open sitespeed-result/www.sitespeed.io/YYYY-MM-DD-HH-mm-ss/index.html 他にも下記のような機能を標準で備えているので、柔軟な運用が可能になっています。 htmlをS3にアップロードする機能 結果をSlackに通知する機能 結果のメトリクスをjson形式で出力する機能 定点観測について WEBのパフォーマンスにおいてボトルネックを特定するだけであれば、その時点で数回の計測を実施すればよいですが、ボトルネックに対して対策を行った後、どの程度改善したのかについて知りたい場合は対策後に再度計測を実施する必要があります。 複数の対策を長期的に実施していく場合などは、対策を実施する度に計測を実施する必要があり非常に大変です。 また、上記とは反対に何かのタイミングで意図せずパフォーマンスが悪くなっていないかをチェックしたいときやパフォーマンスが悪くなった前後でまたそのタイミングで何が原因かなどは遡って確認したいこともあります。 そういった要件を達成するためには、一定間隔で繰り返しWEBパフォーマンス計測を実施する必要があります。 定点観測の方法 定期的にWEBのパフォーマンス計測を行うには下記のような方法が考えられます。 定期的に手動で実施 一番単純なのは定期的に手動で行うことです。 ただ現実的には手作業で行うため忘れることがあったり、そもそも同じ時間に実行するのが難しかったりします。 サーバを構築してcronやCIツールで定期的に実行 次はサーバを立ててcronで実行したりJenkinsなどのCIツールで定期的に実行する方法です。 この方法は、他の処理が裏側で動いていてパフォーマンス計測に影響があることを考慮する必要があったり、 そもそもサーバの管理が必要になってしまうことにデメリットを感じてしまいます。 ECSのタスク実行機能やAWS Batchで定期的に実行 理論的には実現できそうですが、WEBパフォーマンスの定点観測を行うだけなので少し大げさな気がします。 上記に3つほど方法を挙げて見ましたが、どれもいまいちという感じです(主観)。 そこで提案したいのがAWSのCodeBuildによる定期実行です! CodeBuildのメリット CodeBuildは本来CIで利用するサービスですが、サーバレスな実行環境としても優れていると思います。 WEBパフォーマンス計測の実行環境として使うことにおいては下記のようなメリットがあります。 サーバレスなのでリソースを気にしなくてよい 実行時間による従量課金 CloudWatch Event連携でcronフォーマットによる定期実行も可能 定点観測基盤の構築方法 ここからは具体的な構築方法を紹介します。 本記事で使用するコードは全てGithubにあげています。 github.com AWSのリソースをTerraformを使ってコード化しているので、定点観測基盤を手軽に構築することが可能です。 構成図 CodeBuild sitespeed.ioを実行する実行環境として利用します。 CloudWatch Event CodeBuildを定期的に実行するスケジューラとして利用します。 S3 sitespeed.ioによる計測結果の格納と、HTMLのホスティングとして利用します。 Glue sitespeed.ioによる計測結果のスキーマを定義するために利用します。 Athena sitespeed.ioによる計測結果に対してSQLを実行するために利用します。 前提 基盤構築に伴って最低限下記が実行環境に設定されている必要があります。 awscliのインストールとcredentialの設定 Terraformの利用に伴ってAWSのアクセスキーとシークレットキーの設定が必要です。 Terraformのインストール IAMに対するTerraformを実行するための十分な権限設定 AWS CodeBuildとGitHubの接続 AWSコンソール上のCodeBuildにおいてプロジェクト設定時のページで下記のように、「GitHubアカウントを切断」と表示されている必要があります。されていない場合は「GitHubに接続」ボタンからGitHub連携を済ませてください。 CodeBuildとGitHubが接続されていないとTerraformのapply時にエラーになりますのでご注意ください。 準備 1.レポジトリのフォーク 下記のレポジトリをご自身のアカウントにForkします。Cloneして新しくレポジトリを作成しても大丈夫です。 GitHub - gomesuit/webperf-by-codebuild 2.Terraformのtfstateファイル管理用のS3バケットの作成 空のS3バケットを1つ作成します。 既存のものでも大丈夫ですが、tfstateファイルのkeyをコードにべた書きしているので新規でS3バケットを作成することをおすすめします。 3.terraform.tfvarsの作成 sitespeed.ioの出力したhtmlをS3のホスティング機能を使って参照するため、自身のIPでアクセス制限をかけます。 またCodeBuildがソースを取得する先を、手順で作成したレポジトリのURLに変更します。 サンプルファイルがあるのでコピーしてから、 $ cd terraform $ cp terraform.tfvars.sample terraform.tfvars ファイル内のIPアドレスとレポジトリを変更します。 # S3のアクセス元IP my_ip = "XXX.XXX.XXX.XXX/32" # 作成した自身のGitHubレポジトリ git_repository = "https://github.com/<user-name>/<repository-name>" 4.backend.tfvarsの作成 Terraformのtfstateファイル管理用のS3バケットを terraform init のタイミングで指定するためファイルを作成します。 サンプルファイルがあるのでコピーしてから、 # ./terraform ディレクトリで実施 $ cp backend.tfvars.sample backend.tfvars ファイル内のバケット名を変更します。 # tfstateを格納するS3バケット bucket = "自身で作成したS3バケットの名前" 5.Terraform作業ディレクトリの初期化 下記のコマンドでTerraformの作業ディレクトリを初期化します。 エラーがでなければOKです。 # ./terraform ディレクトリで実施 $ terraform init -backend-config backend.tfvars 6.対象サイトの指定 計測するページのURLをurls.csvに記載します。 一行に ドメイン名,対象URL,カテゴリ の順で記載します。 複数行記載すると1回のCodeBuildの実行で複数のURLが計測されます。 カテゴリ名は対象URLをカテゴライズするために付与します。任意の文字列を入力してください。 例えば https://www.kurashiru.com/ を計測対象URLとする場合、下記のようになります。 トップページなのでカテゴリはtopとしました。 計測対象のWEBサイトはご自身が管理されているものを記載してください。 www.kurashiru.com,https://www.kurashiru.com/,top 構築 Terraformを実行します。 # ./terraform ディレクトリで実施 $ terraform apply しばらくすると下記のようにコンソールに出力されるので、確認の後に「yes」と入力するとリソースの生成が始まります。 Plan: 16 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: 上記コマンドで生成したリソースを削除する場合は下記コマンドを実行します。 # ./terraform ディレクトリで実施 $ terraform destroy sitespeed.ioによる計測結果の参照 最大1時間待つか設定済みのCodeBuildを手動で1度実行するとS3に計測結果が置かれます。 HTML S3バケットにhtmlが出力されているのでブラウザで開くことで計測結果を閲覧することができます。 例えばURLは下記のようなものになります。 https://s3-ap-northeast-1.amazonaws.com/webperf-by-codebuild-d85de1e5e348/raw/www.sitespeed.io/desktop/top/2018/12/01/15/38/index.html SQL AWSコンソールのAthenaでSQLを実行すると結果が取得できるようになっています。 SQL例 SELECT * FROM "webperf_by_codebuild_d85de1e5e348"."json" limit 10; VIEW作成 このままでも問題はないのですが、SQLを作成するときにSQLが複雑にならないようにVIEWを設定するのをおすすめします。 例えば下記のようなVIEWを生成することでS3のURLとtimestampのカラムを事前に定義しておくことが可能です。 SQLの下記の部分を自身でTerraformを実行した結果に置き換えてください。 「<Terraformによって生成されたS3バケット>」 「<Terraformによって生成されたデータベース>」 CREATE OR REPLACE VIEW " <Terraformによって生成されたデータベース> " . " site " AS SELECT CAST ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " YEAR " , ' - ' ), " MONTH " ), ' - ' ), " DAY " ), ' ' ), " hour " ), ' : ' ), " minute " ) AS timestamp) " time " , " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( ' https://s3-ap-northeast-1.amazonaws.com/<terraformによって生成されたS3バケット>/raw/ ' , " domain " ), ' / ' ), " device " ), ' / ' ), " category " ), ' / ' ), " year " ), ' / ' ), " month " ), ' / ' ), " day " ), ' / ' ), " hour " ), ' / ' ), " minute " ), ' /index.html ' ) " link " , * FROM " <Terraformによって生成されたデータベース> " . " json " 置き換え例 CREATE OR REPLACE VIEW " webperf_by_codebuild_d85de1e5e348 " . " site " AS SELECT CAST ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " YEAR " , ' - ' ), " MONTH " ), ' - ' ), " DAY " ), ' ' ), " hour " ), ' : ' ), " minute " ) AS timestamp) " time " , " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( " concat " ( ' https://s3-ap-northeast-1.amazonaws.com/webperf-by-codebuild-d85de1e5e348/raw/ ' , " domain " ), ' / ' ), " device " ), ' / ' ), " category " ), ' / ' ), " year " ), ' / ' ), " month " ), ' / ' ), " day " ), ' / ' ), " hour " ), ' / ' ), " minute " ), ' /index.html ' ) " link " , * FROM " webperf_by_codebuild_d85de1e5e348 " . " json " 結果例 SELECT * FROM " webperf_by_codebuild_d85de1e5e348 " . " site " limit 10 ; URLとtimestampのカラムが追加されていることを確認できます。 Redashによる可視化例 弊社では例えば下記のSQLを実行することによって、 SELECT * FROM " <Terraformによって生成されたデータベース> " . " site " WHERE device = ' desktop ' AND time >= current_timestamp - interval ' 1 ' month AND category = ' top ' AND domain = ' www.kurashiru.com ' ORDER BY time desc ; 下記のような結果を得ています。 また上記の結果を下記のようにRedashを使ってグラフ化しています。 上記は弊社での例ですが、sitespeed.ioによって様々なメトリクスが取得されているので色々な指標の相対的な変動を計測することが可能になっています。 カスタマイズ sitespeed.ioの試行回数の調整 WEBサイトのパフォーマンスはサードパーティーコンテンツによって影響を受けるのでリクエストするたびにパフォーマンスにずれが発生することが多いです。 そのため1回の計測で数回施行することが一般的です。 sitespeed.ioは設定ファイルのパラメータを変更することで試行回数を調節することができます。 設定ファイルはレポジトリのルートにあるconfig.jsonというファイルで、iterationsというパラメータを3から変更することで施行回数を調節することが可能です。 { " browsertime ": { " iterations ": 3 , " speedIndex ": true } , " s3 ": { " region ": " ap-northeast-1 " } , " crawler ": { " depth ": 1 } , " plugins ": { " load ": [ " analysisstorer " ] } } 計測間隔の調整 計測のトリガーはCloudWatch Eventで行っています。そのためCloudWatch Eventの設定を変更することで計測間隔の調整をすることが可能です。 Terraformの該当ファイルは terraform/codebuild_trigger.tf です。schedule_expressionの cron(0 * * * ? *) を変更することで間隔を変更することが可能です。 反映するにはもう一度 terraform apply を実行します。 料金について 本記事の設定で実際にかかっている料金をご参考までにお伝えします。 現在4つのURLの計測を1時間毎に実行していますが、CodeBuildの該当料金は1ヶ月に約$40になります。 決して安くはないですが、サーバの管理がいらないため負荷や障害を気にせず運用することが可能です。 さいごに WEBパフォーマンスの定点観測についてお話しました。 手間をかけずにとりあえずパフォーマンス計測を始めたいという要件にもってこいではないでしょうか。ぜひ皆様のWEBパフォーマンス分析の参考にしていただけたらと思います。 明日は検索エンジニアのsakura (@818uuu) が「クラシルの検索エンジニアが検索をよくするために取り組んだこと まとめ」というタイトルで投稿します!お楽しみに! tech.dely.jp
アバター
 こんにちは。 開発部の辻です。普段はデータサイエンティスト・機械学習エンジニアをやっています。   本記事はdely Advent Calendar 2018の3日目の記事です。 adventar.org   昨日12月2日は、弊社delyの開発部プロダクトーナー兼GM 奥原の記事でした。 この1年でdelyは「kurashiru」の成長とともに体制も大きく変化しました。そんな中、若きプロダクトオーナー兼GMとなった彼の苦悩や意気込みなどが赤裸々に綴られています。 どうぞご一読ください。   tech.dely.jp   さて、今回は特定のテーマに沿う必要はないとのことだったので、機械学習とは関係なく、ぼくが普段使っているクライアントOSの話をさせてもらおうと思います。ふつう、クライアントPCで使うOSといえば、MacOSやWindowsを使ってる方が多いかと思いますが、エンジニアの方ですと、なんらかのLinuxディストリビューションを使っている人もそれなりに多いのではないでしょうか?ぼく自身もとあるLinuxをクライアントOSとして3年ほど使っていまして、ちょっと クセの強いOS ではあるのですが、使っていくうちにだんだんと安心感を感じてくる面白いOSなので、この機会にぜひご紹介させて頂きたいと思います。   ぼくとNixOSとの出会い ぼくは NixOS (最新バージョンは18.09)というOSを使っています。多分ほとんどの方がご存じないと思います(笑)。ぼくはこのOSを2015年の中頃から使い始めて、最初の頃はMac上のVirtualBoxに割り当てて使っていたのですが、ある日ふと気がついたら、自宅で使っているほぼすべてのPCがNixOSになってしまいました。もちろんdelyでの開発に使っているクライアントマシンもこのNixOSです。   NixOS Linux      NixOSについて NixOSのサイトに行くと、この様な記載があります。   The Purely Functional Linux Distribution NixOS is a Linux distribution with a unique approach to package and configuration management. Built on top of the  Nix package manager , it is completely declarative, makes upgrading systems reliable, and has  many other advantages .  つまり、 純粋関数型Linuxディストリビューション なのが売りと言っています。この説明にもあるように、NixOSはNixパッケージマネージャーの上に構築されていて、完全に宣言的に扱うことが可能となっています。というわけでまずは Nixパッケージマネージャー についてご紹介したいと思います。   Nixパッケージマネージャー Nix: The Purely Functional Package Manager   こちらのNixのサイトに行くと、次のように記載されています。   The Purely Functional Package Manager Nix is a powerful package manager for Linux and other Unix systems that makes package management reliable and reproducible. It provides atomic upgrades and rollbacks, side-by-side installation of multiple versions of a package, multi-user package management and easy setup of build environments. やはり、 純粋関数型 なんですね。要するに、NixOSというのは純粋関数型パッケージマネージャー上で動くOS、だから純粋関数型OSというわけなんです。このNixパッケージマネージャーついては、いわゆる通常のパッケージマネージャーの一つですので、aptやyum、homebrewなどと同じようにパッケージを管理するのに扱えます。以下のコマンドでmacOSにインストールすることも可能です。  インストールコマンド curl https://nixos.org/nix/install | sh   Nixパッケージマネージャーは、 nixpkgs (the Nix Packages Collection)に登録されているライブラリの中からパッケージをインストールします。ソースはgithubでリポジトリ管理されていて、 Hydra という継続ビルド管理システムで管理されています。   github.com     Nixの仕組み  もう少しNixの仕組みについて見ていきたいと思います。  こちらがNix研究の元となった Eelco Dolstraさんの論文です 。非常にエキサイティングな内容となっています。 https://grosskurth.ca/bib/2006/dolstra-thesis.pdf     全体としてはこのような外観となっています。   [仕組み全体の外観]    [主な仕組み] パッケージレシピ関数ファイルが一意に管理される(導出コンパイル用) パッケージレシピ関数は Nix言語 (Nix Expression Language)で記述する Nix言語はパッケージ管理用DDL NixはNix言語のコンパイラの役割 パッケージレシピ関数ファイルは Nixストア にインストールされる 導出コンパイル = パッケージレシピ関数のビルド + Nixストアへインストール   [Nix言語] 構文例はこちら Nix Expression Language - NixOS Wiki Nixにおけるすべての設定関数はこのNix言語で記述します。nix repl を使って構文を試すことができます。 nix-repl> rec{x = 1; y = 3; z = x + y;}.z = > 4   [Nixストア] インストールパッケージは必ずNixストアにインストールされる ストア内に同じパッケージの複数バージョンが共存できる(※なので、HDDの結構ボリュームが消費されます。それらを整理するためにGC機能があります。) インストールパッケージは、ストア内の導出ハッシュ(一意に生成されたハッシュ)によって特定されたフォルダにインストールされる この一意なハッシュ名フォルダにインストールパッケージのすべてのデータが格納される(以下はtreeコマンドの例) $ readlink $(which tree) /nix/store/ zzljry2r5w14rmvg3p9lz7ym326rfcpp -tree-1.7.0/bin/tree $ tree /nix/store/ zzljry2r5w14rmvg3p9lz7ym326rfcpp -tree-1.7.0/ /nix/store/ zzljry2r5w14rmvg3p9lz7ym326rfcpp -tree-1.7.0/ ├── bin │   └── tree └── share └── man └── man1 └── tree.1.gz   [Nixチャンネル( nix-channel )について] Nixチャンネルは特定の条件を満たすnixpkgsのバージョンを提供する仕組み Hydraでビルドとテストが成功している場合に提供される Nixチャンネルの種類 安定チャンネル、例 nixos-18.09 不安定(開発): nixos-unstable サーバ向け、例 nixos-18.09-small, nixos-unstable-small Nixストア内にはNixチャンネルの最新nixpkgsが入っていてNixに利用される Hydraでインストールパッケージがビルドされ、自動的にバイナリパッケージが生成される(これが特定のチャンネルとして提供される)    NixOSの仕組み NixOSは、このようにNixパッケージマネージャーを中心で構成されているOSなのでした。まとめるとNixOS自体はこのような外観をしています。   NixOS全体の外観      NixOSを使うと以下のような恩恵を受けられます。 モジュールシステム(宣言型設定) Dependency Hell からの脱却 ロールバック 軽量コンテナー (systemd-nspawn利用) Dev/Opsツール (NixOps)   モジュールシステム(宣言型設定)   なんと言ってもNixOSといえば これ という機能です。 様々な設定やパッケージ管理、システムの自動化などを宣言的に管理することができます。モジュールシステムはnixpkgs内に含まれていてNix言語を使って記述します。   NixOSのメインとなる情報はこのファイルに宣言的に記載します。   /etc/nixos/configuration.nix   宣言的に設定した例をいくつか紹介します。  プロビジョニングの例 environment . systemPackages = with pkgs ; [ firefox termite tmux ];  systemdのサービス systemd . services . ircSession = { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { Type = "forking" ; User = "username" ; ExecStart = '' ${ pkgs . tmux } /bin/tmux new-session -d -s irc -n irc ${ pkgs . irssi } /bin/irssi'' ; ExecStop = '' ${ pkgs . tmux } /bin/tmux kill-session -t irc'' ; }; };  cronのサービス systemd . services . cron = { enable = true ; systemCronJobs = [ "30 00 * * * sh hogehoge.sh" ]; };   Dependency Hell からの脱却   インストールパッケージは、ストア内の導出ハッシュによって特定されたフォルダにインストールされるので、たとえばバージョンの異なる2つのパッケージが上書きされることもなく(別のハッシュ値が生成されるため)、またパッケージが依存する別のパッケージについてもこの導出ハッシュを用いてビルドされるため、相互的に依存し合うパッケージは存在しません。ですので、インストール時に毎回、環境依存に怯えながらインストールすることはなくなります。(この点に関してはDockerを使えば問題は解決すると思いますが、NixOS上でもDockerはもちろん使えますので、ぼくの場合は用途によって適宜使い分けています。)   ロールバック   なんらかのsystem設定を間違えてしまったために、OSが起動しなくなってオワタという経験も、NixOSを使えばさっとロールバックできるから安心です。  コマンドでロールバック可能 ブート画面でロールバック可能    GRUB画面で前のリビジョンを選択して起動する   NixOSの再構築 nixos-rebuild コマンドで変更した設定を適応する $ nixos-rebuild switch 設定をビルドのみする場合(コンパイルチェック) $ nixos-rebuild build 宣言的にOSを管理できるので、 configuration.nix を別のPCにコピーし、この手順で適応すれば同じ環境を再現できる   NixOps Dev/Opsツール   NixOSのDev/OpsツールとしてNixOpsがあります。NixOSはDev/Opsを大変重要視した設計思想となっていて、NixOpsを使うことで、デプロイ先のハードウェアや仮想マシンに対してNixOSのセットを展開することができます。また、NixOSのシステム構成管理はクライアントPCとシームレスに管理でき、宣言的なアプローチをネットワークにも拡張し、さらにそのプロビジョニングを追加することができます。ぼくが所有している複数のNixOSマシンについてもNixOpsを用いて設定構成をネットワーク拡張して使用しています。   NixOSの不便なところ  NixOSはとても便利かつ安心して利用できるOSなのですが、すべての人にとってそう言えるかといえばなかなかそれは難しい面もあります。 たとえばこんな点です。 クセが強いので慣れるまでに時間がかかる 利用できるパッケージが他のパッケージより少ないので、ないパッケージは自分でビルドする必要がある(ものによっては結構難しい。。) ちょっとしたことを試すのが面倒(そんなときこそDockerを使いましょう) Dockerのサービス起動もたったのこれだけです。 { virtualisation = { docker . enable = true ; }; }   終わりに いかがでしたでしょうか? NixOSの特性についてざっくりとご紹介させて頂きました。NixOSを使うことで、いわゆる”Dependency Hell”から開放され、 依存関係の完結が保証されることで、堅牢な管理や安全なロールバックが可能となります。NixOS には今回ご紹介できなかった機能がまだまだたくさんありますので、もしご興味を持っていただけたらぜひお試し頂けると嬉しいです。   不定期開催ですがMeetupやってます! www.meetup.com   余談ですが、今年のNix Conference(Nixcon 2018)で、Nixを使ったデータパイプラインの設定についてのセションがあって個人的にとても興味深かったです。機械学習やパイプライン基盤にもNixOSを使うと面白そうですね。   Georges Dubus - Nix for data pipeline configuration.pdf - Google ドライブ   Georges Dubus - Nix for data pipeline configuration   さて、dely Advent Calendarの明日のタイトルは、『超手軽に構築する!サーバレスなWEBパフォーマンス定点観測基盤』です。お楽しみに! 
アバター
こんにちは! dely, Inc.でプロダクトマネージャー兼開発部ジェネラルマネージャーをしている奥原 ( @okutaku0507 ) といいます。この記事はdely Advent Calendar 2018の2日目の投稿です。 先日は、弊社のAndroidエンジニアでマネージャーを勤めている梅森から「AWS CodeBuild+AWS SAM(Lambda)+Slackで最高なAndroid CI環境を作る」というタイトルで投稿がありました。梅森はDroidKaigi 2019にも登壇する予定です。 tech.dely.jp 弊社が運営しているレシピ動画サービスの kurashiru はよく知っているけど、開発チームは何しているのかわからないという方に、少しでも弊社の開発部のことを知っていただければ幸いです。もし、同職種や弊社に興味を持ってくれた方がいましたら、僕の twitter のDMでも良いので、連絡していただけたら嬉しいです。   1. はじめに 僕はdely, Inc.に入社した2016年から元々はずっとサーバーサイド (Ruby on Rails) のエンジニアをしていました。そして、今年 (2018年) の5月ごろからプロダクトマネージャーになり、ゴリゴリと施策などを回しています。 良くも悪くも、急成長するベンチャーではあらゆる役職がそもそも存在していなかったり、ほいほい空いたりします。奇しくも、弊社にはプロダクトマネージャーという役職は存在していませんでした。CTOがその業務を担っていた状態でした。しかしながら、CTOは執行役員も勤めているので、組織のことや技術的な視点から会社の未来を創造しなければなりません。プロダクトのことを常に考えるにはキャパがオーバーしてしまいます。そのため、プロダクトマネージャーの登用は急務でした。されど、kurashiru規模のプロダクトマネージャーを外から採用するにはかなりのハードルがあり、採用は暗礁に乗り上げていました。そこで、僕が言われた言葉は、、、   「おっくー、明日からプロダクトマネージャーで!」   そう、これが僕が未経験ながらプロダクトマネージャーのとてつもなく重い門を叩くことになった転機でした。なぜ、僕がプロダクトマネージャーになろうと思ったのか、今後のキャリアや成長に関する考えは後述しますので、もし同じようなキャリアに悩んでいる方がいれば、少しでも参考になれば幸いです。   2. プロダクトマネージャーとは 日本でも当たり前になったプロダクトマネージャーですが、会社、組織、プロダクトにいよって、本当に様々なことをする役職です。ですが、その本質をざっくり定義すると「プロダクトの成長に責任を持つ人」だと想っています。 残念なことに有料の記事ですが、この記事はわかりやすく書かれていて、とっかかりやすかったです (ステマではありません...) 。 newspicks.com この記事にもあるようにプロダクトマネージャーには「ミニCEO」としての責務があると思っています。ちょっと表現が強いですが、えふしんさん ( @fshin2000 ) のツイートも核心をついていると思っています。 プロダクトマネージャになりたい人、ミニCEOとしての期待だから労働時間はチーム連携に必要な分を除くと短いとか会社来ないとかは割とどうでもよくて、それより24時間のマインドシェアを捧げられる人じゃないと多分無理です。マインドシェアはいくら獲得してもブラックとは言われないですね。 — えふしん@12/1 WebSig24/7登壇! (@fshin2000) November 6, 2018  以下の記事の中でYamottyさん ( @yamotty3 ) がおっしゃること、そのままだと思います。 PMの役割や必要なスキルは、会社やチームによって全然違います。10Xであれば、僕以外のメンバーは全員エンジニアなので、僕は社長業に加えてPMとして、デザインや顧客開発を含むエンジニアリング以外のプロダクトに関する全ての仕事をやります。逆に、あらゆる職種が揃っている会社なら、PMは調整力や巻き込み力のほうが重要かもしれません。 www.fastgrow.jp また、以下の記事にあるように、メルカリのように同じプロダクトを触っていたとしても一つの領域をまるっと任せるというスタイルもあります。 www.fastgrow.jp では、dely, Inc.ではどのような業務をプロダクトマネージャーがこなしているのかというと、「何でも屋」という役割が大きいです。ざっくりプロダクトマネージャーとして僕がこなしていることを箇条書きで書きます。 ユーザーインタビューやデータ分析を通して、解決すべき課題を特定する ユーザー、ビジネス、グロース、デザインの課題に対して解決策を発案する 課題とそれに対する解決策をプロダクトに反映させるために開発プロセスを回すファシリテートをする カスタマーサクセスを行う コードを書く KPIの報告を行う ドキュメントおじさんとなって開発仕様書を書く 部署間調整を通して優先度付けを行う 意思決定の説明を行い、チームから納得を得る エンジニア/デザイナーの採用を行う twitterでエゴサする etc... そんなこんなを行なっていると、一瞬で太陽が沈みます。 前述した、Yamottyさんの記事の引用にもあるように、dely, Inc.では人は全く足りてませんが、あらゆる職種が最低一人は揃っていて、部署が存在します。10人も満たない組織で、一人一人が全ての領域の細部まで把握して意思決定して行動していく像とは、現実的には異なっています。そのため、限られた開発リソースの中、部署間の調整を行い、部署横断的な視点を持って、今会社にとって何をすべきかを考えて優先度を決定し、仲間の納得を得てチームをドライブさせていく力が求められています。 僕は元々はサーバーサイドのエンジニアであるため、現時点ではエンジニアリングの領域に強みを持っています。そのため、直近で行なっている僕の仕事としては、不確実性に向き合う組織を創り上げて、リーンなプロダクト開発フローを確立していくことが挙げられます。 リーンなプロダクト開発について、メルペイのプロダクトマネージャーをされている川嶋さん ( @tsumujikaze ) が書かれたnoteがとても参考になります。 note.mu 今では、このリーンなプロダクト開発も軌道にのってきて不確実性に向き合う強いチームになったと思っています。このリーンなプロダクト開発をどのように弊社で実践しているのかを AWS Dev Day 2018 で登壇させていただく機会に恵まれたので、その時の資料を掲載しておきます。 speakerdeck.com   3. 「明日からプロダクトマネージャー」と言われたら ここまできて本題について書きたいと思います。エンジニアから経験もしたことないプロダクトマネージャーになった僕のケースですので、ツッコミどころ満載かと思いますが、そこらへんはご容赦していただければ幸いです。 組織文化にもよりますが、dely, Inc.では様々な役職をぽーんっと任せることがよくあります。僕が任されたプロダクトマネージャーもそんな感じで渡ってきました。そして、 前述しましたが、弊社にはプロダクトマネージャーがいませんでしたので、実質的に引き継ぎなどはありませんでした。 3.1 形から入る そのため、僕がまず着手したのは「プロダクトマネージャーとは?」を調査し、自分の中のプロダクトマネージャー像を確立させることでした。 まずは、ググりまくりました。「プロダクトマネージャー とは」と。そして、様々な記事や先駆者の言動を見ていく中で、プロダクトマネージャーは総称であり、今では当たり前だと思っていますが、様々な形態があることがわかりました。つまり、プロダクトマネージャーとは、プロダクトの成功に責任を持ち、なんでもやる役職だということがわかりました。なので、弊社のkurashiruというサービスのプロダクトマネージャーは自分で形成していくしかなかったのです。誰も正解を教えてはくれません。「さて、困ったぞ。何をすればいいんだ。」という状態でした。 3.2 整理する 何をすればいいのかわからないというというカオスな状態だったので、まずは整理が必要だと思いました。具体的には、プロダクト開発に直接的に関わっている部署、弊社でいうとタイアップ広告事業部、マーケティング部、ブランド部 (ブランド醸成に関わり、主にコンテンツの制作を行なっている)、そして開発部として、プロダクトに変更を加える施策として何を考えているのか、ヒアリングすることから始めました。 元々、サーバーサイドエンジニアとして様々なデータを扱っていたり、管理サイトの構築などを通して他部署とのコミュニケーションがあったので、部署間でどのような施策を考えているのかは肌感はありましたが、さらに具体的に聞くことで、工数見積もりやそれぞれの部署でのプライオリティの高さなどを聞くことで、施策の解像度を高めていきました。 3.3 優先度を決める 全ての部署が一つのプロダクトを良くしようと動いていたとしても、基本的には開発リソースは一つで、ほとんどの場合それもパツパツだと思います。その中で、何をすべきかを考えることは容易ではないし、その意思決定はその時の気分に左右されることなく、再現性がなければなりません。 そのために、執行役員でCTOである大竹 ( @EntreGulss ) にBizサイドの諸々をコーチングしてもらう時間を設けてもらいました。プロダクトの行く末を左右するプロダクトマネージャーには会社のほぼ全ての情報を頭にインプットしておく必要があります。そのため、経営層の考えを聞くことは不可欠でした。 各部署の責任者であるジェネラルマネージャーは部署間で情報を共有できていたとしても、プロダクトに変更を加える必要がある各部署の大小ある施策の全て頭に入れておくことは難しいと思います。そのため、プロダクトマネージャーは全ての情報を自分に集積し、今会社にとって解決しなければならない課題は何かを決定しなければなりません。各部署で優先度が高い施策であっても、それがプロダクトにすぐに反映できるとは限りません。その優先度を決めた背景をロジカルに説明する責任も同時にプロダクトマネージャーが負っているわけです。 3.4 実行する なぜ、今その施策をするのか。施策のwhyを開発に関わる全ての人が知っていることはとても重要です。納得感を持って開発できるかが施策の良し悪しを左右することもあると考えています。その責任もプロダクトマネージャーにはあります。そのためには、ドキュメンテーション能力が不可欠です。僕は、ここで自分の意思決定を言語化することにとても苦戦しました。あらゆることを総合して考えて意思決定を行なっているので、それらを順序立てて、全ての人に理解してもらえる粒度で言葉に落とすことはとても難しいです。そのため、自分のドキュメントで納得していなさそうなメンバーが入れば席まで行って、納得してもらえるまで議論することもありました。 そして、実際の開発では今まで実装段階ではアジャイルを取り入れていたものの、要件定義をウォーターフォールで行なっていたがために、課題はあっていても解決策が間違っているということが度々あり、それが開発での課題となっていました。要件定義もアジャイルで行おうという、前述したリーンな開発プロセスの定着にメンバーとともに注力しました。要件定義フェーズでは、最初は細部まで介入して、それらが上手く回るようにフローの改善を行なったりしていました。 そして、部署間で実行する施策や全社規模で注力する機能については、スケジュールを常に更新、可視化し、ステークホルダーには通知するなどを行い、部署間の摩擦や間違ったものを作ってしまうようなリスクを減らすことも行いました。 3.5 プレイヤーから離れる 自分がプレイヤーから離れる、これが一番難しかったことです。最近では新卒からプロダクトマネージャーを任せるケースやプロダクトマネージャーが一般化したことで転職するケースもあると思います。しかしながら、内部からエンジニアあるいはデザイナーからプロダクトマネージャーになるケースが多いのではないでしょうか。その時に、しっかりと自分のプレイヤーとしての業務を引き継ぐ時間があれば良いのですが、弊社のようにいきなりプロダクトマネージャーを任されるケースでは、そうはいきませんでした。 僕の場合は、サーバーサイドを書きながらプロダクトマネージャーとしての業務を遂行する必要があります。自分がコードを書くことが好きで、開発リソースがパツパツ且つやりたいことがたくさんある中でついつい自分を戦力としてカウントして、様々なことをこなしてしまいがちでした。しかしそれではどっちつかずになってしまい、自分もキャパオーバーになりかけていました。心を鬼にして、自分がプレイヤーから離れる決断をしなければなりませんでした。幸いなことに、弊社サーバーサイドチームは少数ながら精鋭が集まっており、徐々に自分が担当するタスクを減らしていくことで、フェードアウトすることができました。   4. プロダクトマネージャーとして大切なこと 半年間という短い期間ながら、プロダクトマネージャーを経験していて、大切だと思った考えをお伝えしたいと思います。 4.1 執着し、諦めない 前述した、えふしんさんのツイートにもあるように、プロダクトマネージャーには24時間のマインドシェアをプロダクトに費やすことができるかが資質なのではと思います。費やすという表現も適切ではなくて、気がついたらプロダクトのことを考えていたくらいがちょうどいいと思います。自分でサービスを立ち上げたことがある人なら思い当たる節があるのではないでしょうか。気がつけば、自分のサービスを使ってもらうにはどうすればいいのかを考えていたということがあるはずです。 そして、プロダクトは順調な時ばかりではありません。長く苦しい時期に差し掛かる時もあります。苦しい時でもプロダクトに寄り添い、更なる成長ができることを諦めない。頭がちぎれるまで考える。時には自分も泥臭いことをする。そんな諦めの悪さも必要だと思っています。 4.2 自分の枠組みだけで考えない つい自分の枠組みで考えてしまうことがあります。例えば、どうやればDAUをあげることができるかという課題に対して、開発側で考えたら、リテンションを上げるための施策を考えてしまいがちです。それ自体は間違いではないのですが、その答えはプロダクト開発の外にあるかも知れません。もしかしたら、リアルイベントを開催したら思わぬ盛況ぶりで、SNSでバイラルしまくり結果として、自分のプロダクトをグロースさせることができるかも知れません。つまり、DAUを上げるためにはプロダクト変更はいらずに、マーケの施策で解決できるかも知れません。そうなると、マーケティング的な視点など自分の枠組みの外で考えることも必要になるわけです。 そのためには、常に社内外でアンテナを高く貼り、様々なサービスの施策をみて自分たちのプロダクトにローカライズさせるとどうなるかを考えておく必要があります。ふとした瞬間に自分の頭の中に一つ一つのピースがぴったりと繋がることもあるのです。 4.3 逆算思考 それはちゃんと目標から逆算して考えているのか、と自分に常に問うことは大切です。スマートニュースのプロダクトマネージャー をされている前田さんの記事から引用します。 また、大きなジャンプを生むために、目標は逆算で設定しています。 「1年後にユーザーあたり収益を3倍にする」といった厳しい目標を立てて、そこから逆算して考えるということを日々行う。これは、非常に重要な状況設定だと思います。 seleck.cc この記事にあるように、例えば1年後にユーザーあたりの収益を3倍にするという目標を立てたとします。この値は事業計画から引っ張ってくるのでいいと思います。今の実際の成長率を厳しめにみて、成長曲線を引いた時に、1年後には収益は2倍にもいかないことがわかったとします。そうすると、このままやっていては目標が達成できないことに気が付くわけです。そうすると、違う手を考えなければなりません。その中で思わぬ手が思いつき、一気にグロースさせることができることもあります。  4.4 解決すべき課題設定で全てが決まる 今僕らが全力を掛けてでも解決すべき課題は何であるかを考えることがプロダクトマネージャーの本質だとも思っています。その施策が成功し、プロダクトを大きくグロースあるいは改善させることができるかは課題を設定した時点で決まっていることもあります。そのため、ユーザーあるいはビジネスにおける課題を発見し、それらに優先順位をつけて解決していく、そこにプロダクトマネージャーの手腕が問われていると思います。 4.5 難しい課題にフォーカスする エンジニアあるいはデザイナーのリソースはとてつもなく貴重です。プロダクトには大小様々な問題があります。改善リストを作りちょっと運用しただけでも、かなりの数の解決しなければならない課題が見つかることでしょう。その時によく思い出すメタファーは、以下の記事にある瓶と石と砂とコーヒーです。 tabi-labo.com この記事で言っていることはプロダクト開発にも適応されます。瓶の中に、最初から砂を詰めていると、肝心な時にそれらに邪魔されて石を詰めることができなくなってしまいます。プロダクト開発においても、目先の小さい施策に気を取られてしまっては、本当に僕らが解くべき、難しく大きな課題を解けないまま時間だけが過ぎてしまうのです。小さい機能でも、アプリはリリースされたら取り返しがつかない場合が多いので、隅々までチェックすると思います。結果として、大小関わらず一つの機能をリリースするには、ある程度の工数がかかるわけです。その中で、解くべき大きな課題に対して全力で取り組む、このマインドを持っておくことが大切だと感じています。   5. プロダクトマネージャーになろうと思った理由 なぜ、プログラムを書くことが大好きな僕がプロダクトマネージャーになろうと思ったかを簡単に書いておきます。 僕自身のエンジニアになろうと思った理由が「テクノロジーを通して世の中を幸せにしたい」という人生の目標があるからです。その中で、自分はプログラムを書くことにハマっただけで、それは手段に過ぎないと思っています。世の中を幸せにするプロダクトを自分の手で創っていくためには、一人のエンジニアとしては限界があると思っていたし、意思決定をしていくためには自分のレイヤーを上げていくしかないと考えていました。そのための選択として、一番良いと思ったのがプロダクトマネージャーになることでした。   6. 最後に 「明日からプロダクトマネージャー」と言われた自分の経験を通して、弊社のプロダクトマネージャーの役割やマインドセットについて紹介しました。いかがでしたか。 プロダクトマネージャーは何でも屋であるが故に、大変な場面も多々あります。しかしながら、世の中をより良くしていく実感は強く、やりがいのある役職だと思っています。 弊社では、ユーザーあるいはビジネスの課題に対して成果に執着していけるプロダクトマネージャーを募集しています。これからさらに、弊社と弊社が運営しているプロダクトはどれも面白くなると自負しています。プロダクトマネージャー以外の役職でも、もし興味があれば、僕の twitterアカウント でも何でもいいので、声をかけていただけると幸いです。是非お茶でもしましょう。 明日は、弊社の機械学習エンジニアの辻より自身が使っているNixOSの話が書かれると思います。是非、お楽しみにしていてください。 tech.dely.jp
アバター
こんにちは。Androidエンジニアのうめもりです。 この記事はdely Advent Calendar 2018の1日目の記事です。 アドベントカレンダーについてはQiitaでもAdventarで公開していますので、ほかの記事については是非そちらから見てください。 Qiita: https://qiita.com/advent-calendar/2018/dely Adventar: https://adventar.org/calendars/3535 DroidKaigi 2019楽しみですね。自分も3つほどセッションを投稿したのがめでたく1つ採択されまして、今からしっかり準備せねばと発表内容を作っている日々です。 「Android Vitals徹底活用」というタイトルで発表しますので、興味ある方いれば是非聞きに来てもらえるとうれしいです。 さて、今日は投稿したもので採用されなかったものうちの1つについて、せっかくですのでブログ記事としてまとめておこうと思います。 提出したセッションの概要についてはこんな感じでした。 AndroidでCIしてますか? 2018年現在では活用できるCIサービスが様々存在することもあり、AndroidのCI環境を構築することは特に複数人が開発にかかわるような環境では一般的なことになっていることと思います。その中でもAWS CodeBuildは1分単位の実行時間での課金体系、同時実行タスク数の制限が他サービスに比べてゆるい、など特徴的なCIサービスであることもあり、一度は導入を検討した方もいるのではないでしょうか。 しかし、AWS CodeBuildは特に他サービスとの連携周りの機能が貧弱なこともあり、様々な利点がありながらも導入されない、といったことが多いのではないかと思います。本発表ではAWS SAMを活用してAWS CodeBuild最大限に活用する方法について説明します。なお、本発表内のAmazon LambdaのコードはGolangを使用しています。 Androidのカンファレンスですが、実際はあんまりAndroidの話はしてないですねw では内容に入っていきたいと思います。 delyのAndroid開発ではこんなことをCIbotにやってもらってます 2018年現在Android開発においてはCIを導入していないということはそうそうないことだと思うのですが、delyのAndroid開発でももちろんCIを導入しています。 Slack上のbot、そしてGitHubのWebHookからほとんどの機能を呼び出すように作っており、2018年12月1日現在ではこんなことができるようになっています。 GitHub上でPullRequestを作る、更新するたびにビルド、自動テスト、Lintを行う機能 Slack上からUIテスト(AWS DeviceFarm)を呼び出す機能 Slack上からDeployGateに自動ビルドされたAPKをアップロードしたうえでSlackにURLと最新のRelease Noteを通知する機能 Slack上からリリースブランチ、ホットフィックスブランチをオープンし、(メジャー/マイナー/パッチ)リリースごとにバージョン番号を自動でインクリメントする機能 最新バージョンをリリースする際にGitHubのリリースページを作成し、Release Noteを自動で生成し登録する機能 Slack上からGoogle Playに自動ビルドされたAPKとProguardのマッピングファイルをアップロードし、アルファテストトラックに公開する機能 Slack上から特定のブランチのコードを使ってDockerのビルド用イメージを生成し、Amazon ECRにアップロードする機能 以上の機能がSlackやGitHubのWebHookごしに呼び出せるようになっています。これらの機能が自動で呼び出せる環境は、2018年現在においてもそこそこ頑張っているような気がしますが実際どうなんでしょう?(もしかして当たり前だったりしますか?) このすべてのやり方をここに書いてしまうととても長くなってしまうので、実際に肝になる部分の一部だけ今回は記事として共有しますが、また別途細かく説明する記事を書こうかと思っています。 これらの機能をどのように実現しているか では実際、これらの機能をどんなツールやサービスを使って実現しているか紹介します。 CIをやるにはとりあえずビルド用の環境が必要なのですが、それは以下のものを使って実現しています。 AWS CodeBuild Amazon ECR AWS CodeBuildは優秀なのにデフォルトの外部連携などが貧弱なせいで(Code Pipelineを使う方法やPush Hookはありますが、正直不足ですよね)あまり話題になっていない印象がありますが、delyのAndroid開発ではこちらをフル活用しています。CodeBuildを使う上で必要不可欠なのがAmazon ECR(Dockerのリポジトリサービス)なのですが、こちらにアップロードしたイメージをCodeBuildから使うと、ビルド用のDockerイメージのダウンロード速度が爆速になります。CircleCIを使っていた際もオリジナルのビルドイメージを使っていると5分~10分待たされたりすることがありましたが、こちらの環境に切り替えてからそういった待ち時間はほとんど発生しなくなりました。delyのAndroid開発ではなるべく依存関係を一緒くたに入れたイメージをAmazon ECRにアップロードすることで、いきなりビルドやテストから走るようにして、ビルド時間を削減しています。 AWS SAM(Lambda) CodeBuildの連携周りの貧弱さを補うために、AWS SAMを活用しています。Slackからの呼び出しや、GitHubのWebHookをこちらで構成したAPI Gatewayで受けて、実際のCodeBuildの呼び出し等を行っています。 全体の構成図 全体の構成はこんな感じになっています。 実際はGoogle Play Developer APIを使っていたりもしますが、今回はAWS CodeBuild周りの仕組みについて、そちらを採用した理由について説明します。 AWS CodeBuildを採用した理由 1分単位で使った分だけ課金 大抵のCIサービスだと、同時ビルド実行可能数を増やすごとに月額料金が増えるという形になっているところが多いと思いますが、AWS CodeBuildはシンプルにビルド時間が1分かかるごとに課金される課金体系になっています。 料金表はこちら https://aws.amazon.com/jp/codebuild/pricing/ ですが、一番大きいインスタンスサイズであるbuild.general1.large(15GBメモリ、8vCPU)でも、ビルド1分当たりの料金は0.020USDです。1回自動ビルドやテストを回すたびに数十円程度の料金体系なので、まず導入してみる、という時に非常に良いのではないでしょうか。(結局そのまま本採用しました) ゆるい同時実行ビルド数の制限 https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/limits.html こちらにAWS CodeBuildの制限について書いてあるのですが、他のCIサービスに比べて圧倒的に同時実行ビルド数の制限が緩いです。導入直後は5個のビルドを同時実行できますが、通常は20個のビルドを同時実行できるようになっています。複数人でゴリゴリ開発しても、キューイングされたビルドを待つことはあまりないですね。 Dockerイメージの取得が爆速(Amazon ECRを使った際) 前述したとおりですが、CodeBuildとECRを連携するとDockerイメージの取得が爆速になります。これだけでもCodeBuildを採用する価値はあるのではないでしょうか。 CodeBuild上でのビルドの仕方は基本的な使い方をしているだけですが、Dockerイメージ構築の際に気を付けていることについて共有しておきます。(といってもDockerイメージを使ってビルドする際には当たり前の話ですが…) ビルド用Dockerイメージ構築の際に気を付けていること 依存ライブラリもイメージに含めてしまう もちろんCodeBulidにもキャッシュ機能があるので依存ライブラリはそちらに入れてしまう、というのもありなのですが、キャッシュの展開にもそれなりの時間がかかります。先にGradleのビルドを一回走らせてしまうことで、ビルドに必要な依存ライブラリは全てビルドイメージ上に展開された状態にしています。 Docker Multi-stage buildでイメージサイズを圧縮する 上記のことをやると、どうしても無駄はソースコード等がイメージに残りがちです。 https://docs.docker.com/develop/develop-images/multistage-build/ Docker 17.05から導入されたこちらのDocker multi-stage bulidの機能を使って、余計なファイルがイメージ上に残らないように工夫しています。 ビルド用dockerfile 以下が弊社で使っているビルド用のdockerfileです。 dangerやマーケティング用のツール、jsonパース用のjqなどを使うため、それらの依存関係も含んでいます。こういったものをイメージに含めておくこともできるのはDockerイメージを自前で管理していることの利点ですね。 FROM openjdk:8u141-jdk-slim AS intermediate ENV ANDROID_SDK_FILENAME=sdk-tools-linux-3859397.zip ENV JQ_PATH=/usr/local/jq ENV ANDROID_HOME=/opt/android-sdk-linux ENV ANDROID_SDK_URL="http://dl.google.com/android/repository/${ANDROID_SDK_FILENAME}" \ ANDROID_API_LEVELS=android-16,android-26,android-27,android-28 \ ANDROID_BUILD_TOOLS_VERSIONS=27.0.3,28.0.2 ENV APKTOOL_PATH=/usr/local/apktool ENV ADJUST_DTT_PATH=/usr/local/adjust_dtt ENV PATH=${PATH}:${ANDROID_HOME}/tools/bin:${JQ_PATH}:${APKTOOL_PATH}:${ADJUST_DTT_PATH} ENV GRADLE_USER_HOME=/usr/local/gradle RUN apt-get -y update && \ apt-get -y --no-install-recommends install libc6-i386 lib32stdc++6 lib32gcc1 lib32ncurses5 lib32z1 \ uni2ascii python-pip python-yaml \ ruby \ wget git curl ssh && \ apt-get -y install ruby-dev make gcc && \ gem install bundler && \ gem install ruby-ll && \ pip -q install awscli && \ mkdir $JQ_PATH && cd $JQ_PATH && \ wget -q -O- https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 > jq && chmod +x $JQ_PATH/jq && \ mkdir $APKTOOL_PATH && cd $APKTOOL_PATH && \ wget -q -O- https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool > apktool && chmod +x $APKTOOL_PATH/apktool && \ wget -q -O- https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.3.4.jar > apktol.jar && chmod +x $APKTOOL_PATH/apktool.jar && \ mkdir $ADJUST_DTT_PATH && cd $ADJUST_DTT_PATH && \ wget -q -O- https://raw.githubusercontent.com/adjust/android_sdk/master/tools/adjust-dtt > adjust-dtt && chmod +x $ADJUST_DTT_PATH/adjust-dtt && \ mkdir $ANDROID_HOME && cd $ANDROID_HOME && \ wget -q ${ANDROID_SDK_URL} && \ unzip ${ANDROID_SDK_FILENAME} && \ rm ${ANDROID_SDK_FILENAME} && \ mkdir ${GRADLE_USER_HOME} && \ echo y | sdkmanager $(echo ${ANDROID_BUILD_TOOLS_VERSIONS} | sed 's/,/\n/g' | sed -E 's/(.+)/build-tools;\1/g' | tr '\n' ' ') "platform-tools" "extras;android;m2repository" "extras;google;m2repository" "extras;google;google_play_services" $(echo ${ANDROID_API_LEVELS} | sed 's/,/\n/g' | sed -E 's/(.+)/platforms;\1/g' | tr '\n' ' ') WORKDIR /workspace # Resolve Dependencies FROM intermediate AS dependencies COPY . /workspace RUN ./gradlew --project-cache-dir=${GRADLE_USER_HOME} clean lintDevDebug assembleDevDebug # Resolve Bundle FROM intermediate COPY Gemfile /workspace RUN bundle install # Copy Gradle Caches COPY --from=dependencies /usr/local/gradle /usr/local/gradle AWS CodeBuildをAPIからキックして動かす AWS CodeBuildは、デフォルトではAWS CodePipelineからの呼び出し、またはGitHub等と連携してPush時に自動でビルドを行うことが可能です。なんとなくCIを回してテストコードを走らせる、あるいはデプロイパイプラインとして使うにはこれだけでも十分なのですが、例えば PullRequestを作ったタイミングでビルドを回し、Danger等を使って自動でPullRequestのレビューを行う デバッグ用のビルドと本番用のビルドを同じブランチから分岐してビルドさせる 通常と違うパターンのビルドに対応させる などは以上の手法を使っていると困難です。幸い、CodeBuildのAPIには外からビルドタスクを任意の設定で走らせるものがあり、こちらを使うことで細かいビルドタスクに対応することが可能です。 SDKから呼び出す https://docs.aws.amazon.com/codebuild/latest/APIReference/API_StartBuild.html 実際にビルドタスクを呼び出すためのAPIはこちらです。弊社はLambda上のコードから呼び出していますが、GoのSDKでの呼び出し方はこんな感じになっています。 out, err := m.codeBuild.StartBuildWithContext(ctx, codebuild.StartBuildInput{ ProjectName: aws.String( "kurashiru-android-ci" ), SourceVersion: aws.String(branchName), EnvironmentVariablesOverride: []*codebuild.EnvironmentVariable{ { Name: aws.String( "CI_SPLIT_APK" ), Value: aws.String(strconv.FormatBool(splitApk)), }, { Name: aws.String( "CI_BUILDTASK" ), Value: aws.String(buildTask), }, { Name: aws.String( "CI_BRANCH_NAME" ), Value: aws.String(branchName), }, ... }, }) 基本的な設定はコンソールで行っておくとして、肝はSourceVersionとEnvironmentVariablesOverrideの部分です。SlackやGitHubからビルドタスクを呼び出す際は、SourceVersionにブランチ名などを指定し、EnvironmentVariablesOverrideにビルドごとに異なる環境変数を設定し、実際にCodeBulid上で走るシェルスクリプトでその環境変数を利用して処理内容を分岐しています。 AWS SAMを使ってSlackとGitHubからCodeBuildを動かす 上記のAPIを使い、SlackやGitHubからCodeBuildのビルドタスクを走らせています。基本的には、 $ sam init からCloud Formationのテンプレートを生成し、そこからLambdaの構成を作っていきます。 生成されたyamlを使って実際にAWS上に環境を構築するコマンドはこんな感じです。 $ aws cloudformation package \ --template-file template.yml \ --s3-bucket <ビルドされたlambda用バイナリのアップロード先のS3バケット> \ --s3-prefix <ビルドされたlambda用バイナリのアップロード先のS3のパスのprefix> \ --output-template-file .template.yml $ aws cloudformation deploy \ --template-file .template.yml \ --stack-name <デプロイ先にstack名> \ --capabilities CAPABILITY_IAM 実際はこれをシェルスクリプトに記述してそれを実行するようにしています。 CloudFormationのテンプレートファイルの例 弊社のCI botを構成しているCloud Formationのテンプレートを共有しておきます。 Google Play Developer APIのための記述も入っているので少し長いのですが、AWS SAMではLambdaのパーミッションの定義やAPI Gatewayの定義も一緒に書くことができるのが特徴です。 CI botの構成をすべてここで表現できるので、メンテナンスもしやすくていいですね。 AWSTemplateFormatVersion : "2010-09-09" Transform : 'AWS::Serverless-2016-10-31' Parameters : SlackToken : Type : AWS::SSM::Parameter::Value<String> Default : /rebecca/slack_token GitHubToken : Type : AWS::SSM::Parameter::Value<String> Default : /rebecca/github_token GoogleApiCredentials : Type : AWS::SSM::Parameter::Value<String> Default : /rebecca/google_api_credentials Resources : LambdaIamRole : Type : AWS::IAM::Role Properties : AssumeRolePolicyDocument : Version : "2012-10-17" Statement : - Effect : Allow Principal : Service : lambda.amazonaws.com Action : "sts:AssumeRole" Policies : - PolicyName : "rebecca_lambda" PolicyDocument : Version : "2012-10-17" Statement : - Effect : "Allow" Action : "s3:ListBucket" Resource : "arn:aws:s3:::anonymous-ci-artifacts" - Effect : "Allow" Action : "s3:GetObject" Resource : "arn:aws:s3:::anonymous-ci-artifacts/*" - Effect : "Allow" Action : - "codebuild:StartBuild" - "codebuild:StopBuild" - "codebuild:ListBuildsForProject" - "codebuild:BatchGetBuilds" Resource : "arn:aws:codebuild:ap-northeast-1:xxxxxxxxxxxxx:project/*" - Effect : "Allow" Action : - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:DescribeLogGroups" - "logs:DescribeLogStreams" - "logs:PutLogEvents" - "logs:GetLogEvents" - "logs:FilterLogEvents" Resource : "*" SlackApp : Type : 'AWS::Serverless::Function' Properties : Handler : lambda-rebecca-slack Runtime : go1.x CodeUri : build/lambda-rebecca-slack.zip Timeout : 300 Role : !GetAtt LambdaIamRole.Arn Environment : Variables : REBECCA_SLACK_TOKEN : !Ref SlackToken REBECCA_GITHUB_TOKEN : !Ref GitHubToken REBECCA_GOOGLE_API_CREDENTIALS : !Ref GoogleApiCredentials Events : Post : Type : Api Properties : Path : /slack Method : post GitHubApp : Type : 'AWS::Serverless::Function' Properties : Handler : lambda-rebecca-github Runtime : go1.x CodeUri : build/lambda-rebecca-github.zip Timeout : 300 Role : !GetAtt LambdaIamRole.Arn Environment : Variables : REBECCA_SLACK_TOKEN : !Ref SlackToken REBECCA_GITHUB_TOKEN : !Ref GitHubToken REBECCA_GOOGLE_API_CREDENTIALS : !Ref GoogleApiCredentials Events : Post : Type : Api Properties : Path : /github Method : post さて、それでは説明に入っていきます。 API GatewayのエンドポイントをCloud Formationから作成し、Lambdaと紐づける SlackApp : Type : 'AWS::Serverless::Function' Properties : Handler : lambda-rebecca-slack Runtime : go1.x CodeUri : build/lambda-rebecca-slack.zip Timeout : 300 Role : !GetAtt LambdaIamRole.Arn Environment : Variables : REBECCA_SLACK_TOKEN : !Ref SlackToken REBECCA_GITHUB_TOKEN : !Ref GitHubToken REBECCA_GOOGLE_API_CREDENTIALS : !Ref GoogleApiCredentials Events : Post : Type : Api Properties : Path : /slack Method : post こちらがAWS SAMの中で、SlackのWebHook用のLambdaの記述です。Properties.Events以下がAPI Gateway用の定義になっています。 Events : Post : Type : Api Properties : Path : /slack Method : post 重要なのはType: Apiの指定、そしてPathとMethodをPropertiesで指定することです。これによりこのCloudFormationで構成されたAPI Gatewayの/slackのURLをSlackのWebHookのURLとして指定することで、実際にSlackのWeb Hookを受け取ることができるようになっています。 Goでこちらのリクエストを受け付けるには、 func handleRequest(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error ) { ... } func main() { lambda.Start(handleRequest) } こんな感じのコードで受け付けられます。シグネチャがシンプルでいいですね。 AWS Systems ManagerのParameter Storeを使って環境変数の設定項目をリポジトリに入れなくて済むようにする CloudFormationのテンプレートファイルにAPIのトークンを入れてしまうのは、このテンプレートファイル自体をGitリポジトリ等で管理することを考えるとあまりよくはありません。弊社はAWS Systems ManaerのParameter Storeの機能を使うことで、デプロイ時にAPIのトークンを設定してデプロイするようにしています。 詳しくはこちらに書いてありますので、こちらをご参照ください。 https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-paramstore.html 実際にAWS SAMの中でこちらで定義したパラメータを適用するには、 Parameters : SlackToken : Type : AWS::SSM::Parameter::Value<String> Default : /rebecca/slack_token GitHubToken : Type : AWS::SSM::Parameter::Value<String> Default : /rebecca/github_token GoogleApiCredentials : Type : AWS::SSM::Parameter::Value<String> Default : /rebecca/google_api_credentials こちらでパラメータ名を定義しておき、 Environment : Variables : REBECCA_SLACK_TOKEN : !Ref SlackToken REBECCA_GITHUB_TOKEN : !Ref GitHubToken REBECCA_GOOGLE_API_CREDENTIALS : !Ref GoogleApiCredentials このように環境変数を設定する側で、あらかじめ定義していたパラメータ名を参照するだけで出来るようになっています。 Slack用、GitHub用に走らせるLambda用のバイナリを切り分けてデプロイする方法 CI botとなるとやはりSlack、GitHubなど、様々なところからのトリガーでビルドを走らせたくなりますが、それをすべて同一のLambdaのエンドポイントで処理するのは保守性の面から考えてもあまりよくありません。弊社はそのそれぞれを別のバイナリとして切り分けてビルドしています。 といっても、単に + / + /github - main.go + /slack - main.go とそれぞれmainパッケージを作り、別々にデプロイパッケージを作っているだけです。 https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-go-how-to-create-deployment-package.html こちらにGoを使った場合のデプロイパッケージの作り方が書いてあります。 最後に注意点 大分長くなってしまったので、今回は最後にCI botを実装するうえでハマったポイントをご紹介しておきます。 SlackのWebHook連携上の注意点 SlackのEvents APIのWebHookを実装する上で一つ問題になるのが、 3秒でレスポンスを返さないとエラーになり、リトライのリクエストが飛んでくる という点です。どうしてもCI周りのタスクは即座にレスポンスを返せないタスクもあり、レスポンスだけ返して処理をするという方法も、例えば別のLambdaを呼ぶなどの方法で実現できますがどうしても煩雑になりがちなので、リトライリクエストを無視するという力技で対応しています。 if req.Headers[ "X-Slack-Retry-Reason" ] != "" { // リトライ時は無視 return events.APIGatewayProxyResponse{ StatusCode: 200 , }, nil } X-Slack-Retry-Reasonがついてたら成功レスポンスを即座に返してしまうという雑な方法ですねw Events APIの仕様については https://api.slack.com/events-api ここに詳しいので、実際に実装する場合はここをよく読んだうえで実装しましょう。 締め さて、AWS SAMとAWS CodeBuildでCI環境を作るうえでのポイントについていくつか書きました。この辺を押さえておけば、あとはがんばってGitHubやGoogle PlayなどのAPIをゴリゴリ呼んで実装するだけです。皆さんもAWS SAMとAWS CodeBuildで最高なCI環境を作りましょう。 記事の内容に質問などある方は、 Twitterで @kr9ly に気軽に聞いてください。 さて、dely Advent Calendarの明日のタイトルは、 「明日からプロダクトマネージャー」と言われたら です。楽しみですね。 2日目の記事です tech.dely.jp
アバター
こんにちは。 ストリーム・パイプラインエンジニアの辻です。   早速ですが、ストリーム・パイプラインってご存知でしょうか? 社内でもよく「ストリーム・パイプライン」って何だ?「ストリーム・パイプラインエンジニア」ってどんなことをするポジション?というふうに聞かれる機会が多いです。同じエンジニアでさえその実態を正確には把握できないという事もあり、改めてその仕事の内容について少しご紹介したいと思っています。 実は知られていないというのもそのはずでして、日本にはまだこのポジションを明確に示す名前がありません。(ですので、dely内ではこのように呼んでいます。)しかしながら、海外のデータを重要視している会社などでは、状況によってデータサイエンティストよりも重要なポジションと考えられることもあるほど、とても有益でやりがいのある とてもエキサイティングなポジション だと思っています。   ストリーム・パイプラインとは まずはストリーム・パイプラインについて少しご説明したいと思います。 よく、 データの流れ は 水の流れ に例えて説明されることが多いかと思います。 たとえば、 データの流れのことを『 データストリーム』 と呼んだり、 データを貯めるストレージを『 データレイク 』と呼んだりします。     これはなぜでしょうか? この 水 という存在は、私たちの生活の中において全く欠かせないものです。飲料用、食事用、食器を洗う、手を洗うなどなど、蛇口をひねればいつもそこに水が出るという状態というのは、日本に住んでいるとなんだか当たり前のように感じますが実は当たり前ではないんです。 これを実現している仕組み、それこそが パイプライン です。それでは水のストリーム・パイプラインをもう少し詳しく見ていきましょう。   水のストリーム・パイプライン   横浜市水道局の図をお借りしてご説明させていただきたいと思います。 山に降り注いだ雨水は地中を通りやがて川となり低地へと流れて来ます。その川の水が各家庭の蛇口へと供給されるわけですが、多くの家庭に供給すべく供給量を一定に保持するために川の水をせき止めて ダム を作ります。ダムから一定的に放出される水は、 導水施設 を通って浄水場で 浄化 され、生活用水としての基準を満たしたのち 配水管・給水管 (時には増圧ポンプなどを利用)を通して各家庭へと供給されます。   データのストリーム・パイプラインも実はこれと同じなのです。つまり、蛇口をひねると 水 ではなく データ が供給されるのがデータのストリーム・パイプラインというわけです。もし、日常的に供給されるべきタスクを、データレイクに対して毎回SQLを発行しているとしたら、例になぞらえて、あなたは水を飲むためにわざわざダムへと赴き、コップをダムへと延々垂らして汲み上げ、持参したろ過装置でろ過して飲んでいるようなものと言えると思います。(しかし、アドホック分析は定常的なタスクには該当しないので、さしずめ水質検査のようなものと考えられます。)            (厳木ダムのイメージをお借りました)           (ろ過装置の例)   つまり、水とデータのパイプラインにはこのような対応関係にあると言えます。   水 ⇆ データ ダム ⇆ データレイク 導水施設 ⇆ メッセージキューサービス 浄化 ⇆ ETL処理 配水池 ⇆ データストア 配水管・給水管  ⇆ ジョブフロー 蛇口   ⇆ API、Batch、分析ツール   ストリームパイプラインの具体的なデータの流れはこのようになります。 データはまず データレイク に保存されて、 メッセージキューサービス(MQS) を通して、データの流れ(つまり ストリーム )が制御されます。 MQS にエンキューされたデータは、 サブスクライバ や トピック によってデキューされその後ETL処理に譲渡されます。ETL処理済み(つまり 浄化された )データは ジョブフロー に乗り、それぞれが必要な データストア へと運ばれていきます。データストアに保存されたデータは随時、最新状態に更新され、 API や Batch のインプットとなったり、あるいは機械学習の学習時の データセット に用いられたり、 分析ツール のコンソールなどから直接SQLで参照されたりして日常的に活用できる状態を維持することが可能となるのです。   しかし、一方でこのような疑問があると思います。   「データレイクに直接SQLを投げても実現できるのでは?」 はい、もちろん実現はできます。しかし、現実に目を向けてみるとどうでしょうか?使い捨てのアドホック分析であれば可読性の低いSQLであってもさほど支障はないでしょう。しかし、定点観測していきたい指標の集計処理や、機械学習に用いるデータを毎回複雑なSQLで取得するとしたら、データレイクにストアされているデータ量に比例して処理コストも高くなりパフォーマンスも落ちてしまいます。(何10ペタバイトのデータレイクは今ではそれほど珍しい話ではないですよね。)また、SQLは運用が長くなると うなぎのタレ的 に複雑な構成が追加されていくものなので、改修にかかるコストは高くなり、いずれバグの温床になりかねません。そうならないためにも、健全で安定的に必要なデータをデータストアに供給する仕組みというのは非常に重要な責務を担っていると考えられます。   「データレイクに直接SQLを発行した方が加工処理を加えるよりデータの信頼性が高いのでは?」 これも確かに一理ありますが、時間的コストと精度とのトレードオフになると思います。ペタバイト、エクサバイト級のスキャンを何度も実行すると料金コストが大変なことになりますし、レスポンス時間が処理時間全体のボトルネックになります。この課題において一番重要なことは、 加工時の冪等性を保証する ということになると思います。そのためには、アウトプットの項目数の一致やデータ量の変化に対するバリデーターをETL処理終了後に実行し結果をSlackで通知したり、あるいは場合によって後続処理を停止するなどの配慮が必要になります。いやいや、そんなことをするくらいならSQLを発行した方がやっぱりいいのでは?と思うかもしれませんが、昨今では特にこれらのETL処理に加え、機械学習の文脈においてデータ量が決定的に扱えない課題もありますので、トレーニング処理や前処理を行うインスタンスサイズを可変にして、コストを抑えたいという思いもあり、このこともより柔軟なパイプライン設計が求められる背景となっていると考えます。   tech.dely.jp ストリーム・パイプラインを構築する 例としてストリーム・パイプラインをAWSで構築した場合をご紹介したいと思います。    AWSでの構築例   AWSでの構築例です。 ユーザのイベントデータは、Kinesis Firehoseを通してS3に定期的に保存されます。このデータは、AWS Glueがクローリングしてカタログ化しETL処理を行ったのち、再びS3にparquetフォーマット等で保存します。また、それと同時にlambda経由でAmazonMQへとパブリッシュされます。AmazonMQにはTopicとQueueがETL単位でサブスクライブされていますので、エンキューしたイベントログに対してデキューを行いStepFunctionsやAWS Batchのジョブフローを通し所定のデータストアに保存されます。データストアはAthenaでアドホック分析を行ったり、lambdaからの利用やアプリでの利用の結果として、再びイベントデータとしてデータレイクへと保存されていきます。 また、管理画面などからRDSに登録したデータについてもAWS Glueがクローリングしてカタログ化しETL処理でユーザのイベントデータと統合されて、所定のデータストアに保存されます。 AmazonMQはデッドレターキュー配信アラームを設定したり、上流プロセスの失敗を受けて下流のサブスクライバのデキューを止めたり、逆に下流の処理が追いつかない場合にバックプレッシャーをかけるなど、まさに ダム や パイプラインのバルブ のような役割を担っています。   GCPでの構築例   こちらはGCPでの構築例です。 ユーザイベントはFirebaseを通じてBigQueryに保存されます。BigQueryのデータはpub/subを経由してdataflowにてETL処理を行います。処理済みの( 浄化された )データは所定のデータストアに保存されます。BigQueryでアドホック分析した結果やCloud Functionsやアプリの利用結果はやはり再びデータレイクへと循環していきます。   このように、 データストリームは常々循環し 必要なデータストアを満たし様々な用途として利用されていきます。利用するAPIや機械学習のBatch処理などにとってそれは、あたかも蛇口をひねってデータが供給されるように、常時最新のデータを利用することが可能となります。ダッシュボードなどでKPIを見る際、安心して利用できる、または、APIやBatchのインプットが常に最新状態を維持されていることは、サービス全体の品質を上げ、現時点よりグロースするための基礎として必要不可欠だと考えています。また、機械学習では循環したデータのフィードバックを再びインプットデータとすることにより、推論精度の定常的向上が期待できます。   まとめ   今回は、ストリーム・パイプラインエンジニアについてご紹介しました。 ストリーム・パイプラインが何か?、あるいは、ストリーム・パイプラインエンジニアの仕事についてなんとなくご理解いただければ大変嬉しいです。 水が人にとってライフラインであるように、データ活用は会社が運営するサービスにおけるライフラインだと思っています。そのために、常に新鮮で浄化されたデータを供給することは安定的にサービスを運営する上での根幹となります。そして、このペタバイト級のデータの流れを円滑に管理し、運用コストを抑え、サービス向上に貢献することはとても難しく高い技術力を要しますが、その分とてもやりがいのあるポジションであると思っています。   最後に   今delyでは、このとてもエキサイティングなポジション 「ストリーム・パイプラインエンジニア」 を募集しております。しかし弊社においてもストリーム・パイプラインの取り組みはまだまだ始めたばかりで、まったく手付かずな点も数多くあります。この取り組みに興味をお持ち頂けた方、あるいは機械学習も併せて今後ご活躍したいと検討されている方にはうってつけのポジションだと思っておりますので、少しでもご興味を持って頂けた方はぜひご連絡お待ちしております!    
アバター
こんにちは.開発部のsakura ( @818uuu )です. 普段はレシピ動画サービス クラシルの検索を担当しています. 今回はクラシルの検索品質評価ガイドラインを作成したお話を書きます. はじめに 検索品質評価ガイドラインの作成方法 1. General Guidelinesを真似る 2. 品質評価項目の作成 3. 評価テストの実施 最後に はじめに みなさんは何かを検索した時に「思った結果とちょっと違うな……」と感じた経験があると思います.そういう検索の精度や品質管理はどのように保ち,また,改善されているのでしょうか. Googleだと General Guidelines という検索品質評価ガイドラインがあり,それに基づき多くの評価者が検索品質の評価を行っています. 今回クラシルでもその試みに取り組んでみようと思いクラシルの検索品質評価ガイドラインを作成しました. まだ検索品質評価ガイドラインすべてを公表できるほど整ってはいないのですが,作成方法を一部公開します. 検索品質評価ガイドラインの作成方法 1. General Guidelinesを真似る まずはGoogleの General Guidelines という検索品質評価ガイドラインを真似てざっくりガイドラインのテンプレートを作成しました. 2. 品質評価項目の作成 次に検索の品質を評価する項目を作成しました. General Guidelines には品質評価項目が数十個ありますが,今回はほぼ踏襲はしませんでした.なぜなら,Googleとクラシルは検索の性質や検索の校正,評価する軸などが大きく異なるからです. そこで,クラシルの検索に合わせて一から評価項目を作成し(ミニマムではありますが)今回は4つの項目を作成しました. その中の一つを紹介すると「そのレシピが定番に近いか」という項目を作りました. 例えば(どちらも素敵なレシピですが),『[変わり種!ミニもずくハンバーグ]』と『[簡単デミグラスソースのハンバーグ]』だと後者のほうがより一般的に定番のハンバーグと感じると思いますし,定番さというのはレシピを選ぶ一つのポイントになると考えたからです. レシピを選ぶポイントはつまりクラシルの検索結果で選ぶポイントにもつながってきます. www.kurashiru.com www.kurashiru.com また,1つの項目につき5段階評価(-2,-1,-0,1,2点)をつけれるように設定しました. これで1検索キーワードにつき4項目*5段階をつけるので最低-8点〜最高8点の17段階の評価をつけれるようになりました. 3. 評価テストの実施 評価項目を作成し終えた後に,それで果たして数値がうまく付くのかをテストする必要があります. 今回は1人なので評価テストにかかる時間も考慮して評価する検索キーワードの標本調査を行い評価テストを実施しました. 評価テストを実施した結果,評価項目・評価方法ともに概ねよさそうだということを確認できました. ミニマムではありますがこれで検索品質評価ガイドラインを作成することができました. 最後に クラシルの検索品質評価ガイドラインを作成してみると難しいことだらけでした. しかし,本当に難しいのはこれを適切に運用しそれを検索の改善にいかすことだと思います. 検索品質評価自体取り組んでいるところは少ないかもしれませんがなにか知見を共有できれば幸いです. delyではエンジニアを募集しています.よろしくお願いします. www.wantedly.com
アバター
こんにちは、機械学習エンジニアの辻です。   先日、SageMakerの活用事例で登壇させて頂きました。 machinelearningnighttokyo20181.splashthat.com     機械学習で一番時間の掛かる作業といえば、やはり 前処理 ですよね。データレイクからデータを取得して、必要に応じて様々なデータストアからデータをかき集めて、加工して、また別のデータストアに入れてと。。。 大量のデータをあっちにやったりこっちにやったりして、もう一体最初は何がしたかったのかさっぱりわからなくなってしまうことがあります。    それに加えて、機械学習では、学習したモデルに対して推論しなければなりませんが、またこの推論結果が必ずしも 決定的ではない という課題も含んでいます。 この、 決定的ではない とはどういうことでしょうか? 動作が予測可能なもの、つまり、入力したものに対して、常に同じ経路で計算を行い、同じ結果を出力する、こういう振る舞いを決定的と 言います。なのですが、機械学習において、トレーニングで生成されるモデルというのは、その学習データによって、同じ責務を担ったモデルであっても振る舞いが変わってしまいます。 たとえば、同じ100万件のデータについて3クラス分類を行う場合に、昨日推論したときには、クラスAが33万件、クラスBが33万件、クラスCが33万件と綺麗に分類されていたとします。しかし、今日同じデータで推論したらクラスAが80万件、クラスBが15万件、クラスCが5万件と全然違う結果を出力したりして、まさに 予測不可能 なわけです。   MLストリームパイプライン・プラットフォームの構築 そこで、こんなストリームパイプライン・プラットフォームを構築しました。   アーキテクチャーの変遷 実は、元々データレイクがBigQueryなので、構築の設計当初はGCP Cloud PubSubとDataflowを使って構築しようとしたんですね。親和性が高いし使いやすいので。   まさにこんな感じですよね。    しかし、アプリからCloud PubSubに対して、イベントをパブリッシュするためのSDKが提供されていなかったんです。RESTのエンドポイントは用意されてあるんですが、バッファリングやリトライについてはSDKに任せたいなぁという思いがありました。      そして次に、AWS Batch、Step Functionsを検討しました。AWS Batchは非常に簡単に使えますし、大量のデータでも比較的高速に処理することが可能です。しかしワークロードが固定されてしまうことで柔軟なスケール変更が望めないことと、ワークロードの管理とコードの管理が別々になってしまうので、MLプラットフォームでのDevOpsが一元化できないという課題がありました。     Step Functionsは、私たちが思い描くイメージにもっとも近い形のワークフローを提供してくれるサービスではありましたが、AWS lambdaのみワークフローステップに指定することが可能ということで、やはりMLプラットフォームのDevOpsが一元管理できない課題を解消することはできませんでした。   しかし、これらのマネージドなサービスについては、必要に応じて今後もどんどん使っていきたいですし、クロスプラットフォームで提供される様々なサービスについても、良いサービスは 適材適所 で使っていきたい。そんな欲張りな思いもありました。   Kubernetesの採用 そんなわけでKubernetesを採用しました。KubernetesにはJobs、CronJobというのがありましてバッチを起動することができます。特にCronJobはスケジュール起動することが可能です。この機能を利用してストリームパイプラインを組もうと考えたわけです。こうすれば、もちろんクロスプラットフォームでも使えますし、 決定的な ワークロードのでもいけそうと判断したなら、GCP Cloud Dataflow、AWS Batch、Step FunctionsをCronJobから呼び出して使うといった恩恵も受けられます。現状でもコンテナ内から、AWS GlueのELTジョブに対してpySparkのスクリプトに投げて処理を移譲させたり、AWS Athenaのクエリに計算を任せたりと活用しています。 また、機械学習の 非決定という課題 ですが、推論結果のサイズによって次の処理で利用するインスタンスのサイズを変えたり、スケールアウトしたりと柔軟な対応が可能となります。 そしてもう一つ、Kubernetesを採用するに至った大きな理由がありました。 Amazon SageMakerの活用 それが、Amazon SageMakerの活用です。 Amazon SageMakerは、機械学習モデルをあらゆる規模で、迅速かつ簡単に構築、トレーニング、デプロイできるようにする完全マネージド型プラットフォームです。SageMakerが提供するビルトインアルゴリズムと、DeepLearningフレームワークはすべて、コンテナでトレーニングJOBが起動できます。つまり、トレーニングJOBというコストの大きな処理をコンテナに移譲させることができるので、ワークロードのインスタンス自体は比較的小さくて済むということなんです。       Kubernetesの運用 kubernetesの運用の中でいくつか工夫している点がありますので、少しご紹介したいと思います。    argo   1つ目は、argoの導入です。argoはワークフローを細かくデザインできるだけでなく、直列処理、並列処理もYAML一つに記述することができるのでとても便利です。管理画面を立ち上げてワークフローが可視化できるのもいいですよね。       kubewatch   2つ目はkubewatchの導入です。これにより、jobの開始と終了、エラーなどについては、こんな感じでkubewatchを通してicebergさんが知らせてくれてます。     DataDog   そして3番目はDataDogの導入です。DataDogエージェントでPodの状況の監視を行っています。複数のnodeの状況を一元的に監視できるのでとても助かっています。     waterコマンドの開発 そして4つ目として、waterコマンドというのを開発しました。 kubernetesやminikubeなどコマンドがたくさんあって運用する上で把握するのが大変ですよね。argoの導入でargoコマンドまで増えてもう覚えきれません。そこでwaterコマンドで必要なコンテキスト単位にコマンドをまとめることで、コマンドのユーザビリティの向上に取り組んでいます。   installerでwater コマンドでインストールします。   Usageが表示されます。   デプロイコマンド $ water deploy -e env -p project_name こんな感じで新しいプロジェクトの作成からデプロイ、Podの編集などもwaterコマンドで一元的に統一的な操作が可能になっています。(全部ではないんですけど。。)   今後のロードマップ 現在、パイプラインの制御やPodのUp/Downについては、専用のPod(Control Room)を立てて制御を行ってるのですが、これをAmazon MQを使ってQueueによるイベントドリブンな設計を検討しています。    Amazon MQ   必要に応じて学習エンジンも 適材適所 使っていきたいと考えています。   Amazon SageMaker GCP AutoML GCP ML Engine   最後に   delyでは今回紹介しましたMLストリームパイプラインを格好良くグロースしてくれるエンジニアを募集しています!   www.wantedly.com
アバター
こんにちは.開発部のsakura ( @818uuu )です. 普段はレシピ動画サービス クラシルの検索を担当しています. 今回は辞書シノニム管理の運用について述べます. 一般的にもなかなか知見が共有されていない部分なので担当者の方は見て損はないと思います. では,さっそく内容に入っていきます. 作業内容 1.キーワードの発見 ユーザーが検索したキーワードログから見つける キーワードごとの離脱率から見つける 気づいた方から直接教えてもらう 本物の辞書を使う 2. キーワードの登録 更新頻度 作業時間 モチベーション 余談 気になっていること 自動化 Sudachi 最後に 作業内容 辞書シノニム作業に必要な手順は主に2つです. キーワードの発見 キーワードの登録 1.キーワードの発見 そもそも辞書シノニムがきちんと整ってないキーワードをどうやって発見するかという話があります. 自社では色々なポイントから見つけているのですがいくつかご紹介します. ユーザーが検索したキーワードログから見つける クラシルでは毎日ユーザーが検索したキーワードをチェックしています. そのキーワードを検索回数が少ない順にソートして辞書シノニムがまだ未対応そうなキーワードを発見します. なぜ検索回数が少ない順にソートするかというと,検索回数が多いものは既に対応済みであるからです. キーワードごとの離脱率から見つける これも1つ上の方法と似たものですが,キーワードごとの離脱率もチェックしています.それほど珍しい単語でもないのに離脱率が高いと辞書シノニムが未対応な可能性も高いのでここから見つけることもあります. 気づいた方から直接教えてもらう 社内の方やcsのフィードバックから教えていただき気づくこともあります. 本物の辞書を使う この本物の辞書というのは検索の意味合いではない広辞苑といった一般の人が使う辞書のことです. どうしても上記3つの方法だと検索後の対応になってしまいます.いままでに一度も検索されたことのないキーワードには対応できません. そこで本物の辞書(食材辞典のようなもの)をあいうえお順に見て珍しそうなキーワードも登録している時期もありました.今ではコスパが悪いのでやっていません. 他にも色々な方法で辞書シノニム未対応のキーワードを見つけることが出来ます. 2. キーワードの登録 クラシルには自社で開発したMondoruというサービスがあります. tech.dely.jp Mondoruを使って該当キーワードを辞書・シノニムに登録しています. 登録した後は,テスト環境で一回検索することも必ず行っています. 更新頻度 クラシルではほぼ毎日辞書シノニムのメンテナンスを行っています. 作業時間 登録するキーワードの数に依存しますが,毎日平均して15分もかかっていないと思います. 辞書シノニムの運用を引き継いだ当初は4時間くらいやっていた時期もありました. モチベーション 辞書シノニムを修正してきれいに形態素解析されて検索できた時はめちゃくちゃ気持ちいいです. ここ半年辞書シノニムを担当していますが幸せです. 余談 検索が好きだったので入社する前に何百個かのキーワードでクラシルの検索を調査してメモしていました. その時のデータも辞書シノニムの改善に大いに役立ちました. 入社前クラシル検索調査のメモ 気になっていること 自動化 辞書シノニムは一概に自動化できない部分があるのが難しいところです. なぜなら辞書シノニムには意外にも専門的な知識が必要だったりするからです. 例えば私は辞書シノニムを担当しててはじめて「ふき味噌」=「ばっけ味噌」ということを知りました.こういうのをどうやって自動化するんだろう,と思っています. 辞書シノニムの自動化について知見をもっている方がいればぜひ教えていただきたいです. Sudachi Sudachiというanalyzerが気になっています.一般的にはanalysis-kuromojiを使っているところが多いと思うのですが(自社もそうです),Sudachiのスライドを見る限り高品質+高機能そうなのでとても興味があります. Sudachi ❤︎ Elasticsearch - Speaker Deck 最後に 辞書シノニム管理をきちんと運用することで地道ではありますが確実に検索はよくなっていきます. 今回書いた内容がどなたかの参考になって検索がよくなっていけば嬉しいです. delyではエンジニアを募集しています.よろしくお願いします. www.wantedly.com
アバター
データ可視化推進室の深尾です。 delyではkurashiruリリース当初からデータ分析を重要視してきました。でも初めから使い勝手の良い分析基盤があった訳ではなく、これまでに何度も改良し今は4世代目となる分析基盤を中心に利用しています。今日はその分析基盤のお話です。まずこれまでの歴史から振り返ってみたいと思います。   クラシル分析基盤の歴史 第1世代:Google Analytics クラシルのリリース初期から導入していたのはGoogle Analyticsです。これは皆さんも使っていることが多いんじゃないかと思います。基本的には無料でログ収集・分析・可視化まですべてやってくれるので導入コストが低く便利です。 その一方でカスタマイズ性は低く、例えば独自の指標を取得してSQLで分析することは無料ではできません(有料サービスを使えばBigQueryにエクスポート可能)。また当初から、いずれA/Bテストを実施することは計画されていたので、そのための分析機能も必要でしたが、Google Analyticsはできることが限られます。そこでカスタムメトリクスを収集してSQLを実行するための第2世代の独自分析基盤を構築しました。   第2世代:fluentd + BigQuery + Redash                      モバイルアプリから行動ログを送り、それをfluentdで収集してBigQueryやS3に送ります(僕が入社して初めに取り組んだのがこれでした)。可視化にはRedashを使っています。この基盤は現在でもレガシー基盤として使い続けていますが、fluentdの運用コストがかかることや複雑なSQLを書ける人が限られてしまうことから、より使いやすい分析基盤を模索しており、そこで目をつけたのがFirebase Analyticsでした。   第3世代:Firebase Analytics ログの収集はFirebaseがやってくれますし、BigQueryにエクスポートする機能もついているので、第2世代の利点を活かしたまま、既存の課題も改善できる予定でした。ところが実際に使って問題となったのはカスタムで設定できるパラメータ数に上限があることでした。つまり独自の指標を分析するのにいずれ限界がくる可能性があります。またBigQueryにエクスポートした時にデータが特定のカラムに偏っており、クエリのスキャンコストが従前より高くなる懸念もあったため、第4世代のデータレイクへ切り替えることになりました。   第4世代: Kinesis + Athena + Redshift + Metabase +  社内製ダッシュボード これが現在使っているデータ分析基盤です。構成図には載っていませんがログ収集基盤にはAmazon Kinesis Data FirehoseやAWS Glueを使っています。Sunnyと書いてあるのはSPAで作った社内製ダッシュボードです。   ログ収集基盤・データ集計基盤・ダッシュボードのそれぞれをリプレースしています。社内の人的リソースを確保できたことも背景にありますが、独自ダッシュボードを開発するに至った具体的な理由があります。   独自ダッシュボードの開発に至る経緯 構成図にもあるようにRedashに加えてMetabaseというオープンソースのダッシュボードも導入しています。当初はこのMetabaseだけを新ダッシュボードとして使う予定でした。   Redash &  Metabase Redashに対するMetabaseの利点は、動的にクエリを実行しやすいことが1つあるかと思います。例えばダッシュボードに2つの日付を入れるところがあって、O月O日からX月X日までのグラフを表示するといった使い方ができます。そしてRedshiftにデータマート(サマリーテーブル)を構築しておけば、簡単なSELECT文でデータを集計できるので、これまでより多くの人に使ってもらえるというのが開発前のぼくの考えでした。 しかし、実際にユーザーとなる各部署の社員にいろいろ話を聞いてみると、それまでの考えでは浅かったことに気がつきました。   クラシル用語で作られたダッシュボード データ分析基盤のユーザーが求めていたものはクエリが簡単に書けるダッシュボードではなく、クラシルの用語で構成された画面でした。例えばYahooの社内で使われているダッシュボードだったら「ヤフオク」とか「Yahoo!ニュース」といったメニューがあってそれぞれのメトリクスを見ることができるそうです。   クラシルのダッシュボードなら「検索キーワード」や「再生数」「すべてのレシピ」といったメニューがあれば、SQLを使わないフードコーディネーターやクライアント担当者でもデータを利用できますし、これまでSQLを使ってきたマーケターやエンジニアが分析にかける時間コストを短縮することもできます。   分析基盤のアーキテクチャ データ分析基盤では、サービスがスケールした時でも集計クエリにかかる時間や費用を抑えられることが求められると思います。そして短期間で開発しなければならない事情もあったので3ヶ月以下で構築する方法を考えました。この辺りはエンジニアのスキルセットとの相性や、事業・組織のフェーズも影響があると思うので、どんなアーキテクチャやフレームワークで構築するのが一番良いということはないと思います。ぼくの場合はRedshiftを2014年ごろから使っておりメリットとデメリットもわかっていたので、これをうまく使ったり、弊社のDevOpsでワークフローサーバとして使ってきたRUNDECKをここでも使ったりすることで全体の開発工数を抑えることができたと思います。ここで社内製ダッシュボード周りのアーキテクチャを紹介します。   (CoreUIのイメージ) ダッシュボード CoreUI (Vue.js) 製 Single Page Application Kubernetes上のNginxコンテナで静的ファイルを配布 IP制限 OAuth2でGoogle認証 WebAPI Express (Node.js) RESTful API RedshiftやElasticsearchに接続 IP制限 集計バッチ Python製 AthenaとRedshiftでSQLを実行 クエリの結果を別のAthenaテーブルとして使うための独自フレームワークを実装(最近CTAS機能ができましたね^^) RUNDECKサーバでスケジュール実行+ワークフロー管理 DWH Amazon Athena + Redshift Redshift SpectrumでAthenaのテーブルにアクセス AWS Data Migration ServiceによりAuroraのDBと同期 インフラ、CI/CD Kubernetes on EC2 AWS CodeBuild  今後のロードマップ 画面追加 ワークフローのコード化 ユーザーごとの権限管理 こんな感じでいろいろあるんですが、もし新しい人に入ってもらえたら自分がやりたいところをやってもらったら良いんじゃないかなと個人的には思っています。データ可視化の面白いところは、SPA、UI/UXデザイン、集計バッチやワークフロー管理、Kubernetesなどを広く触れることがあると思います。もし興味があればぜひ一度オフィスに遊びにきてください。   delyでは現在エンジニアを大大大募集中ですよ!      
アバター
こんにちは。 普段はdelyのAndroidアプリの設計、実装とかその他もろもろを担当している梅森です。 最近弊社では、長期的な技術的な意思決定や技術的課題の可視化・解決の支援など、技術的な側面においてdelyの開発チームが成長していけるような取り組みを考えて実施しています。本稿ではそのうちの取り組みの一つをご紹介します。 エンジニアは普段の機能実装と並行して長期的なプロダクト改善を行うための施策を行っていたりしますが、アーキテクチャの移行やリファクタリング、自動テストといった施策は短期的には成果が見えにくいということもあり、業務的に近いエンジニアや成果が分かりやすいことをやっているエンジニアがどんなことをやっているかは分かるが、あまり仕事で関わらないエンジニアは何をやっているかよく分からないという状態になったり、会社としてはどのような技術的な意思決定を推奨しているかということが分からない(あるいはそもそも知見が存在しない)という状態になりがちです。 開発チームの規模が大きくなっていけば、例えばiOSやAndroid、サーバーサイド、SREといった技術領域ごとにチームを組織化してそこで知見を共有したり溜めたり、技術的な意思決定をしたりといったことを計画的に行っていくことになると思いますが、弊社くらいの規模ですとそこまでの取り組みを本格的に始めていくというのはまだまだ難しいものがあります。 そこで、手始めに各技術領域ごとで現在取り組んでいることや技術的な課題感、ロードマップ、最近興味のある技術領域などを定期的に開発チームにヒアリングして、Qiita:teamにまとめて共有するという取り組みを始めてみました。(delyは全社的にドキュメントをQiita:teamにまとめる文化に最近はなってます) こんな感じです 参考にしたのは、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などは省き、必要最低限の構成を描くとこんな感じでした。     Railsアプリケーションが一つのデータベースを参照するという、とてもシンプルな構成です。この構成で問題なのが、全てのリクエストでデータベースへ問い合わせを行なっている点です。たとえ同じクエリが走って同じ結果が得られたとしても、全て問い合わせが行われているため、とても効率が悪いです。  そして、サービスがグロースしていく中で一つの問題が起きはじめました。  プッシュ打つとサーバー落ちる不可避問題。 全端末をターゲットにプッシュを打つとサーバーが落ちるという問題に行き着きました。その原因として、様々な原因が考えられられ、メモリやCPUなど様々なメトリクスを調べましたが、わからず仕舞いでした。 そんなこんなあり、夜も眠られない日々が続きました... ※ 「ゆうすこ」は弊社代表です     そして、DBがボトルネックになって結果的にレイテンシが上がり、LBのヘルスチェックが落ちているのではないかという考えに至り、キャッシュを導入することになりました。 何はともあれ、 CTOが徹夜でコードを書く という涙ぐましい努力により、僕らはいっときの平和を手にしました。   後にSREがジョインし、調べるとリソースが効率的に使われてなかったことも原因だとわかりました。 tech.dely.jp   2. オーソドックスなキャッシュ時代 AWSのマネージドサービスであるElastiCacheを導入しました。 クライアントに返すAPIレスポンス(json)を、パラメータを含めたエンドポイント毎にElastiCacheにキャッシュするようにしました。そうすることで、DBへ過度に不要な問い合わせが発生しなくなりました。簡単ですが、構成は以下のようになります。     これによりしばらくは枕を高くして寝ることができました。 しかし、現実はそんなに甘くはありません。 弊社ではインシデント級のアラートはイワさんが教えてくれます。これはレイテンシが1秒を超えてるよという恐怖の通知です。この通知がプッシュの時に頻繁にくるようになりました。 このインシデントについて調査していくと、ElastiCacheのGetHitsというメトリクスが引っかかりました。 当時は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...> 当初、一つのリクエストに対して一意にキャッシュを取得するためのキーが決まるようになっていました。プッシュでホームを開かせるように、特定のリクエストにアクセスが集中する場合、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のロジックによるため、均等に分散させることはできませんが、目的は果たすことができました。 こうして、再び弊社に平和が訪れました。 ※ 現在のメトリクスをスクショしたため上と異なります   3. キャッシュ分散時代 キャッシュキーの後ろに数字をつけることで、いい感じにキャッシュが分散され、イワさんから怒られることは劇的に減りました。 しかし、まだまだ現実は甘くありませんでした。 たまーにですが、プッシュ時にイワさんから怒られることがありました。 詳しく調査をすると、どうやらイワさんから怒られる時は、ElastiCacheのキャッシュミスが急激に増加していることがわかりました。つまり、キャッシュ切れです。分散させたのはいいですが、 全て同じ有効期限を設定していたが故に、切れる時は分散された全てのキャッシュが切れていた ということです。 ならば、有効期限を長くすればいいという話になりますが、 タイアップ広告が指定された時間に公開されること キャッシュを長くしてデータの不整合がおきること などの理由によりそれは難しい状況でした。 また、設計はシンプルに保ちたかったので、ユーザーから来るリクエストのエコシステムの中でいい感じにやってくれることを目指した方が賢明です。 そこで、ヒラメキました! 先ほどの分散されたキャッシュキーに対して、 有効期限のグラデーション をつければいいのではないか。つまり、図で書くとこのような感じです。 この図において、 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. キャッシュ分散グラデーション時代 「ついに平和が訪れた」と誰しもが確信していました。 しかし、平和というものはそんなに永くは続きませんでした。 上記の対応をした後でも、ごくごく稀にプッシュ時にイワさんから怒られることがありました。まだ敵いたの!?という感じの少年ジャンプみたいな世界観。 ※ ちなみに、イワさんはルフィの心強い味方です   原因を精査すると、理由は同様でキャッシュ切れのようです。よくよく考えると、有効期限をグラデーションしたとしても、一つのキャッシュに割り当てられるリクエストは1/nになります。つまり、図で書くとこういうことです。この図で注目して欲しいのは、1つのキャッシュキーに割り当てられるリクエストの割合が、3割強ということです。 例えば、プッシュ時に10,000のリクエストが来た際にキャッシュ切れが生じると、約3,000リクエストがElastiCacheをすり抜けてDBへ問い合わせることになります。これはまずいぞ、ということになります。 ここでもヒラメキました! このリクエストを各キャッシュキーに割り当てる配分にもグラデーションをかければいいのではないかと考えました。キャッシュの有効期限が短いキャッシュキーほど少ないリクエストを受け取るよう、いい感じのロジックを作ってリクエスト数にグラデーションをかけることにしました。図にすることこのような感じになります。 この図が意味するところは、キャッシュキーの後ろにつける数字 (magic number) が小さいほど、頻度 (リクエストが割り当て割れる配分) が低くなるということです。上の図と照らし合わせると、 有効期限が最も短いキャッシュキーに割り当てられるリクエストの割合が一番小さい ということを意味します。つまり、キャッシュ切れが生じるリクエストの割合が数%になることで、ほとんどのリクエストがDBに問い合わせにいくことがなくなりました。 これを導入してから、キャッシュ切れが生じることは全く無くなりました。 本当に本当の平和が弊社を包み込みました。   終わりに delyでは、技術を使って世の中に幸せを届けたいと一緒に戦ってくれるエンジニアを強く募集しています。どのポジションも貴重なリソースが限られているベンチャーでは、やりたいことが山のようにある中で何に注力するか、そしてそれを如何にして最速でユーザーに届けられるかが重要になります。その中でも自分の強みを活かして頭がちぎれるまで考えて実装することが僕らエンジニアの見せ所だと思っています。   一方で、新しいソリューションも積極的に取り入れ、本番環境に適応しています。その取り組みの一つが評価され、AWS Summit Tokyo 2018で弊社CTOが基調講演させていただいた動画を貼っておきます。 Day3 基調講演(日本語同時通訳) | AWS Summit Tokyo 2018   僕らはサービスを伸ばしつつ、エンジニア個人が技術的に挑戦したいことに、どんどんチャレンジして、サービスとともに成長していきたいと強く思っています。エンジニアに限らず、そのようなマインドをお持ちの方と一緒に仕事ができる日を心待ちにしております。是非一度お茶でもさせてください。     デザインブログも始めました。 delyのデザインブログで新しい記事書きました!📝 Appleの「Essential Design Principles」の事例をまとめました。 https://t.co/tsFsp8obWl — 大竹 雅登 / dely (@EntreGulss) July 19, 2018  
アバター
こんにちは。 delyでISE(In-house System Engineer)をやっている @_skuwa です。 kurashiru[クラシル]のグロース、プロダクト改善の為の基盤の設計・開発を行っています。 今日はユーザーの検索行動のUXを向上させるために立ち上がった、検索改善プロジェクトについて書こうと思います。 検索機能における、UX上の課題点 検索したものの、レシピ数が少ない 豚肉、パスタなどの曖昧なワードでの検索時に結果が多すぎる(ユーザーが求めているレシピの割合が低くなってしまう) 明らかに不適切なレシピが検索結果に混ざり込んでしまっている その結果、検索後の離脱率(動画再生せずに直帰した割合)が高い状態になっていました。 課題解決へのアプローチ delyでは基本的にグロースは開発チームが行っています。 しかし、今回の検索はドメインの専門知識があったほうが質の良い仮説が立てられるということで、調理部などの各部署と連携したプロジェクトが立ち上がりました。 やったこと 部署横断での検索改善プロジェクトチームの立ち上げ 料理人の方々がPDCAを回せるように、SQLを書かなくても主要なデータ分析を行えるWebアプリの作成(Mondoru) Mondoru上でElasticsearchの辞書、シノニム管理 数値やKPIのSlack通知 検索数が多いのに結果が少ないキーワードを抽出し、レシピを開発 Mondoru delyではリポジトリ名やアプリケーション名をワンピースのキャラにするという習慣があるので、今回はそれっぽい『 シャーロット・モンドール 』から名前を付けました。 Mondoruには主に以下の機能があります。 辞書、シノニムの追加 スコアリングの調整 上記チューニングでの検索結果をテストする Elasitcsearchの管理 検索離脱率などの主要データを画面をポチポチして集計、出力する データのビジュアライズ その日の検索データ速報をSlackへ通知 改善でやったこと、成果 基本的には季節ごとの検索傾向や離脱率を見ながら辞書とシノニムを追加していくことになります。 調整した検索ワードの離脱率と動画再生画面への遷移数の変化を見ながら、ベストな検索結果を探っていきます。 日々の検索データを眺めながらノイズの割合が多いものに関しては辞書とシノニムを活用したチューニングを、検索結果数が少ないものは該当キーワード向けのレシピ開発を行います。 ↓検索結果改善によって、検索後に動画再生される割合が 急速に 増えてきています。 今後の課題 レシピ開発によってニーズのある検索結果を充実させ、辞書やシノニムで検索結果に含まれるノイズを減らすことに成功しました。 ただ、冒頭に述べたように検索結果が多すぎる時にユーザーに刺さるレシピの割合が少ないという問題は解決できていません。データで見ても、具体的な料理名での離脱率は減少していますが一般的な食材名での離脱率はあまり変化が見られません。 そこで、検索関連のユーザー行動データを元にしたサジェスト機能を実装中です。 サジェストには2種類あって、絞り込みのサジェストと置き換えのサジェストがあります。 絞り込みサジェスト 『パスタ』を入力したユーザーに対して、 『パスタ トマトソース』 『パスタ 魚介』 をサジェストする。 一般的なサジェスト機能です。 キーワードの検索数や、検索結果の件数などを元にスコアリングして表示を行います。 置き換えサジェスト 『豚肉』を入力したユーザーに対して、 『豚こま肉』 『豚ひき肉』 をサジェストする。 『豚肉』といったカテゴリに近い検索ワードは、サジェストでより細かいカテゴリに置き換えすることで検索結果を最適化できるようにします。 冷蔵庫に『豚こま肉』が入っているユーザーが『豚肉』で検索しようとした際に、『豚こま肉』をサジェストすることで、より最適な検索結果にユーザーを誘導することが可能です。 まとめ ドメインの専門知識を活かすグロース基盤を作ることで質の良い仮説を立てることができ、『超速』でのグロースをすることができました。 delyではその他にもグロースの質と速度を爆速にする基盤やアプリケーションの開発に投資をしており、Mondoruのようなアプリケーションの開発を多数行っています。 世界最速のグロース に挑戦したいという方、是非応募してみてください! www.wantedly.com
アバター
iOSエンジニアの西川です! kurashiru(クラシル)のiOSアプリの開発を担当しています。 前回のdely engineering blogで紹介させていただいたAPOLLO計画にアストロノート一号として選ばれた僕がこの計画の感想を書いていきたいと思います。 未来のSREを最速で育てる!dely流SRE育成術、APOLLO計画の紹介 APOLLO計画ではSRE候補者のことをアストロノートと呼んでいます。 アストロノート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の @_skuwa です。 kurashiru(クラシル)を支えるインフラ作りや、ミドルウェアの開発等をやっています。 今日はdely SREチームが行っている障害対応訓練、APOLLO計画をご紹介します。 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
アバター
SREの 深尾 です。kurashiru [クラシル] のインフラを担当しています。 タイトルのとおり、クラシルのwebサイトではRailsを使っており、1サーバあたり10,000人程度のアクセスに耐えることができます。実際には余裕を持たせて5,000人/サーバを目安にスケールさせており、TV CMをガンガンやったり、国内外のTV番組で特集されたり、芸能人にSNSで拡散されても動じませんが、実は過去に1度だけWebサイトがダウンしてしまったことがあります。それは2017年3月11日にSmaSTATION!!というTV番組でクラシルが取り上げられた時のことでした。 以下はその時のリクエスト数を表すグラフです。ダウンしてしまったので計測できなかったユーザの数字は含まれませんがそれでもアクセス数は1分で数万人を超えていました。 それまで、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%近くまで使える状態になりました。 最後に このようにボトルネックを1つずつ解消するというプロセスを繰り返すことでハードウェアリソースを使い切ることができます。また上記以外にNginxのキャッシュを利用して、瞬間的なアクセスにはNginxのキャッシュを返すことで1台あたり10,000人程度でも高速に捌けるサーバとなっています。最後にサーバのサイズとプロセス数とバックログの参考値を以下に記載します。 サーバ:CPU2コア、メモリ8GB worker_processes:24 backlog:8192    delyでは、急速なサービス成長を支えるSREを募集しています。 www.wantedly.com www.wantedly.com
アバター