Quantcast
Channel: クックパッド開発者ブログ
Viewing all 726 articles
Browse latest View live

クックパッドのiOSアプリ開発を加速させるスクリプト群

$
0
0

こんにちは、技術部モバイル基盤グループの茂呂(@slightair)です。

今回は、ちょっと地味ではありますが、クックパッドのiOSアプリ開発を支えているスクリプト群について書きたいと思います。

日々iOSアプリ開発を行うとすれば、Xcodeまたはその他のお気に入りのエディタでコードを書き、ビルドと実行を繰り返して開発を進め、アプリが完成したらサブミット、めでたくリリースという流れになると思います。 場合によってはこうした開発の所々をサポートするツールを使うこともあるでしょう。クックパッドでもいくつかのツールを使っていますし、場合によっては自作することもあります。

ツールを導入することで解決できることであればそれでよいですが、もうちょっと気の効いたことをして欲しい、リリースフローなど自分たちのアプリ開発の進め方の都合で発生する繰り返しタスクを省力化できないか、というような比較的小さな問題を解決するために、僕たちは今回紹介するようなスクリプトを用意しています。

開発支援系

アプリ開発を支援するスクリプト群を紹介します。

利用ツールのバージョンチェック

クックパッドのiOSアプリ開発では CocoaPods, Carthage, clang-format, SwiftLint, SwiftFormatなどのツールを使っています。 数が多い上にこれらのツールの更新頻度はバラバラで、バージョンによって動きが違ったりします。複数人でアプリを開発しているので、環境によって期待している効果が得られないと困ってしまいます。そのため、各開発者の環境に期待しているバージョンがインストールされているかチェックするスクリプトを用意しています。

ライブラリ群のインストール

クックパッドのアプリは CocoaPods と Carthage を併用しています。 CocoaPods は Objective-C で書かれた静的リンク可能なライブラリ群を、Carthage は Swift のライブラリ群をインポートするために使っています。 何故このように使い分けているかというと、CocoaPods で導入しているライブラリの一部が静的ライブラリであって use_frameworks!が使えないからです。 またフレームワークが増えるほどアプリ起動に時間がかかってしまう問題を防ぎたいというのも理由です。

CocoaPods と Carthage のコマンドを叩き、Carthage がチェックアウトしたライセンスファイル群を CocoaPods の acknowledgements.plistにマージするスクリプトを用意しています。

また、Carthage にはこの記事を書いている時点では更新のあったライブラリだけをビルドし直す仕組みがなかったので、ビルド時間を抑えるために更新が必要なライブラリだけをビルドするスクリプトを用意していました。 この問題への対応はすでに PRが出てマージされているので、新しいバージョンではこの処理は必要なくなりそうです。

前述のツール群のバージョンチェックとあわせて make でこの操作を実行できるようにしています。 各開発者は手元へ最新のコードを fetch した後に makeコマンドを実行することで、チームで期待されている開発環境にそろえて開発に取り掛かることができるようになります。

コードフォーマッタ

コードフォーマッタに clang-format と SwiftFormat を使っています。 直接これらのツールを使ってもよいのですが、ちょっとした工夫をしています。

clang-format はオプションで format をかけたい範囲を指定できるので、開発者が変更を入れた箇所にだけフォーマッタをかけるスクリプトを用意しています。ファイル単位でも良いのですが、PRを出した時に変更とは関係のないフォーマッタによる修正が混ざるとレビューする側がつらくなってしまうので、こうしています。

また、フォーマッタにかけるのを git の commit hook などに仕込むこともできると思いますが、意図的にしていません。これは、フォーマッタによって自動的に変更されたコードを開発者に確認してもらいたいからです。

リリースフローに関係するタスク補助系

クックパッドのリリースフローに含まれるタスクのうちスクリプトで解決しているものを紹介します。

アプリのバージョンを繰り上げる

アプリのバージョンを変更するというただそれだけのスクリプトを用意しています。特に面白みのないものですが、Extension を複数持つアプリだとそれに対応する info.plist がいくつもあるので地味に面倒な作業であることがわかると思います。クックパッドではこの部分の変更作業を複数人で回しているので、スクリプトで行えるようにしているのは作業する人によって微妙に違う内容になってしまうのを避ける目的もあります。

開発に関わった人ごとのマージコミットをリストにする

リリースフローに、次にリリースしようとしているバージョンのコードに自分がいれた変更が正しく含まれているか各開発者が確認する手順があります。このチェックリストをつくるスクリプトがあります。 単純に git のコマンドを利用してマージコミットのリストを作るだけのものですが、このような単純な作業こそスクリプトにしやすく、単調で間違いやすいタスクなので、スクリプトで片付けるようにしています。

開発環境の計測系

開発に直接関わるものではなく、開発環境がどういう状態であるか計測するためのスクリプトもあります。

Swift 移行率の計測

毎日リポジトリの master ブランチに含まれる iOS アプリのコードのうち Swift のコードの割合がどれくらい変化しているか計測・記録し、グラフに描画するようにしています。 単純な興味もありますが、Objective-C のコードが残っていると Swift の便利な機能が使えない場面が多くでてきてしまうので、必要に応じて Swift への書き換えを進めています。この進行具合が可視化されると、少しだけやる気がでたり達成感が得られます。

ビルド時間の計測

アプリのビルドにどれくらい時間がかかっているのか、バージョンを重ねるたびに変に伸びたりしていないか計測しています。ビルドに時間がかかっているということは、それだけ開発者を待たせてしまい生産性を下げていることになるので、状況を把握するために記録しています。ここで計測している時間をもとに、ライブラリの選定や場合によっては直接的な対策を行います。特に Swift を採用してからは書き方によってビルド時間が変に長くなってしまうこともあるので xcprofilerなどを使って具体的な場所を特定したりもします。


これらのスクリプトはだいたい Ruby で書かれています。 Makefile などから呼び出されているスクリプトもありますが、最近は fastlaneをよく使っているので、fastlane action として実装し直そうという動きもあります。今回紹介したようなスクリプトの中で汎用的な action として分離できるものがあれば、公開していきたいと考えています。

クックパッドではiOS/Androidに詳しいモバイルアプリ開発エンジニアはもちろんのこと、このようなモバイルアプリ開発をより効率よくするために活躍できるエンジニアを募集しています。


クックパッドにおける最近の機械学習について

$
0
0

f:id:yoshiaki-0614:20170303110052p:plainこんにちは、研究開発部の山田(@y_am_a_da)です。

去る2月16日、「Cookpad Tech Kitchen #5 クックパッドにおける最近の機械学習について」と題して、機械学習に関わっている方々向けの技術交流イベントを行いました。

https://cookpad.connpass.com/event/49324/

定員が70名のイベントでしたが、告知してから30分ほどで応募者数が定員超えの100人近く集まり、最終的には400人を超す方々にお申込みいただきました。これまでに開催したTech Kitchenの中でも過去最高の申込数であり、機械学習への関心の高さを感じました。 昨年7月に発足したばかりの研究開発部では、現在クックパッドに投稿されている250万品以上のレシピを始めとするさまざまなデータに対して、機械学習を活用したサービス開発を行っています。このイベントでは、研究開発部の現在の取り組みとそこで得られた知見について発表を行いました。

この記事では、その様子についてお伝えします。

クックパッドと自然言語処理 (Cookpad and NLP)

f:id:yoshiaki-0614:20170303120244p:plain

まず、原島から、クックパッドでの自然言語処理の活用事例について紹介がありました。

この発表では、自然言語処理による

  • レシピの分類
  • レシピの翻訳

などについて紹介をいたしました。具体的には、現在「おまかせ整理」として提供されているレシピのカテゴリ分類機能の実装方法と、日本語のレシピを英語のレシピに変換するためのモデルについて紹介をしました。

発表内容の一部は

でもご覧いただけますので、ご興味あるかたはあわせてご覧ください。

また、2年前に公開したレシピデータセットとその利用状況についても紹介をいたしました。

Food image object detection and classification: Challenges and solutions (part1 - object detection)

f:id:yoshiaki-0614:20170303115507p:plain

次に、レシェックから、画像認識を用いた写真中の料理領域の認識について紹介いたしました。

この発表では

  • ILSVRCの歴史
  • 「料理があるかどうか」だけでなく「どこに料理があるのか」を認識するための手法

についての紹介をいたしました。

具体的には、You Look Only Once、略してYOLOと呼ばれる手法による動画中からリアルタイムでの料理認識について触れ、発表の最後ではそのデモを行いました。

Food image object detection and classification: Challenges and solutions (part2 - classification)

f:id:yoshiaki-0614:20170303120245p:plain

最後に菊田から、画像認識を用いて写真が「料理」か「非料理」かを分類する手法について紹介いたしました。

この発表では

  • クックパッドで提供している「料理きろく」のアーキテクチャ
  • 「料理きろく」で行っている写真の「料理」「非料理」を分類するための試行錯誤

についての紹介をいたしました。

具体的には、発展著しいディープラーニングモデルの比較検討や二値分類問題の多クラス化拡張、など様々な試行錯誤をしながらサービスレベルの向上に取り組んだことについて紹介をしました。

質疑応答

質疑応答は、休憩時間中に質問を紙に書いてボードに貼ってもらう形式だったのですが、時間内に全て答えきれないほどの質問数で、大変盛り上がっていました。内容としては

質問1

料理/非料理の画像判別モデルの改善において様々な工夫をしたということだが、どんな取り組みが最も性能向上に貢献したか

回答1

CNNのモデルをより良いものにアップグレードすることと、二値判別問題から間違えやすいクラスを追加した多クラス問題に拡張したことの寄与が大きかった。データセットを拡充するというのも基本的かつ効果的だと思われるが、アノテーションのコストもあるため徐々に進めている。

質問2

研究開発をする部署にとって研究と開発の棲み分けは重要だと思うが、クックパッドの研究開発部ではどのように棲み分けをしているのか

回答2

それぞれが密接に関連しているものなので、明確に棲み分けてはいない。ただし個々人の興味や要求されるスキルが異なるため、研究の方に重きを置くメンバー、開発の方に重きを置くメンバー、というようにメンバーによって比重は異なっている。

などがありました。

懇親会

発表会の後は、懇親会を行いました。

懇親会では、弊社の研究開発部メンバーと参加者と用意されたご飯を食べながら様々な意見交換や雑談をしました。 今回の料理の目玉は、下のようなクックパッドロゴの入ったライスケーキでした。 f:id:yoshiaki-0614:20170303110047p:plain

まとめ

いかがでしたでしょうか。 クックパッドでは、一緒に機械学習を行っていくエンジニアを募集しています。ご興味ある方はぜひ遊びにいらしてください。 画像認識エンジニア | クックパッド株式会社 採用情報

ストレスフリーなGitHubのIssue生活

$
0
0

こんにちは。サービス開発部の丸山@h13i32maruです。

今日はGitHub/GHE(GitHub Enterprise)で快適なIssue生活をおくるために作ったJasperというツールと、それを実際にどうやって使っているかを紹介させていただきます。

ストレス

GitHub/GHEを日々の業務の中心として使っていると、すごくたくさんのIssueやPull Request(以下PR)が流れてきます。 これらのIssueを処理する方法としては主に「メール」と「通知ページ(github.com/notifications)」の2つだと思います。 僕もこれらの方法を使っていたのですが、以下の点ですごく困っていました。

  • 多すぎてメンションされたものやコメントしたものを見逃してしまう
  • あとで見ようと思って、忘れる
  • ブラウザのタブを大量に開いた状態になる
  • 知らないところのIssueで議論が進んでいて気づけ無い
  • チームの人がIssue上でどんな活動をしてるのかわからない

こういうことに日々気をつけながら、仕事をしていると凄いストレスを感じてしまいます。 そこで、これらの問題を解決するためにJasperというGitHub用のIssue Readerを作りました。

f:id:h13i32maru:20170313110353p:plain:w600

Jasperとは

JasperはGitHubのIssue/PRを柔軟にかつ効率的に閲覧・トラッキングするためのIssue Readerです。 と、これだけではどんなものなのか想像しづらいと思うので、以下のスクリーンショットをみてください。

セットアップが終わると、このような画面になります。左のペインから「Stream一覧」「Issue一覧」「Issue本体」となります。

f:id:h13i32maru:20170315081116p:plain:w600

この「Stream」というのがJasperのコア機能になります。 例えば「x10/cooking-logというリポジトリでryo-maruyamaがアサインされているIssueをみたい」と思った場合、以下のようなStreamを作成することになります。

repo:x10/cooking-log assignee:ryo-maruyama is:issue

f:id:h13i32maru:20170313110451p:plain

Streamを作成して、数秒待つと、以下のように当該の条件にあうIssueの一覧を見ることができます。

f:id:h13i32maru:20170313110501p:plain:w200

Issue生活

では、実際に私は仕事でどんなStreamを作ってJasperを活用しているか簡単に紹介します。

内容 Stream例
自分が関係したIssue/PRを把握する
(メンションされた OR コメントした OR アサインされた OR 作成した)
involves:ryo-maruyama
自分のGitHubチームあてにメンションされたIssue/PRを把握する team:tech/all team:android/all
自分が作成したIssueを把握する is:issue author:ryo-maruyama
自分が作成したPRを把握する is:pr author:ryo-maruyama
自分がメインで活動しているリポジトリのIssue/PRを把握する repo:ha/kiroku-g repo:x10/cooking-log
グループメンバが出しているPRを把握する is:pr author:member1 author:member2 author:member3
グループメンバのIssueでの活動を把握する is:issue involves:member1 involves:member2 involves:member3
隣のグループのIssueでの活動やPRを把握する involves:other1 invovles:other2 involves:other3
他部署の活動を把握する is:issue user:research user:partner-alliance
直近のリリースについて進行を把握する repo:android/android-cookpad milestone:v17.3.1
影響がありそうな問題をすぐに把握する repo:x10/cooking-log label:important label:bug
重要であったり、特に見ておいたほうがよいIssueを把握する is:issue label:注目
社内エゴサーチ(※日本語のエゴサーチはできなさそう)redshift OR dwh

これらを見て気づいたかもしれませんが、StreamにはGitHubの検索クエリがそのまま使えます。 Streamの使い方、GitHubの検索クエリについて詳しくは以下のURLをご参照ください。

どうなったか?

Jasperを使うことによって、私は仕事でIssue/PRの見落とし防止や他の人の活動を格段に把握しやすくなりました。 また、社内でも多くのエンジニア、デザイナ、ディレクターの人が使っています。 ダウンロード数ベースでは約100人程が使ってくれています。中にはこういったフィードバックをくれる方々もいました。

f:id:h13i32maru:20170313110518p:plain:w800

f:id:h13i32maru:20170313110522p:plain:w700

f:id:h13i32maru:20170313110554p:plain:w400

また、GitHubの中の人にも使っていただけているようです。

技術的な紹介

ここまでは、主にJasperの使い方を紹介したので、ここでは簡単に技術的な紹介をします。

JasperはElectronと呼ばれるデスクトップアプリを作るためのOSSを使って作られています。 このElectronはGitHub社が主に開発しており、クロスプラットフォームで動くことを売りにしています。 JapserもMac版/Windows版/Linux版を提供しています。

Electronでアプリを作るためにはネイティブアプリの実装方法を覚える必要はなく、JavaScript/HTML/CSSを使って実装することができます。 そのためWebの開発をある程度分かる人なら、すぐにでもアプリを作り始めることができます。 Jasperも初期のプロトタイプは土日だけで作ることができました。 また、ECMAScript2015以降やasync/awaitもトランスパイルなしに使えるのも魅力的です。

Electronで作られている有名なアプリとしては、Atom、Visual Studio Code、Slackなどがあります。 この他にも多くのアプリが作られており、実績もあり活発に開発されているので、デスクトップアプリの開発に興味がある方にはオススメです。

まとめ

JasperにはIssue/PRを閲覧しやすくするために、Stream以外にも更新通知、未読管理、未読コメント管理、フィルタ、スター、キーボードショートカットなど、便利な機能があります。 なので、毎日たくさんのIssue/PRを閲覧するのに苦労している人もそうじゃない人も、是非一度Jasperを使ってみてください。

以下は個人ブログでJasperについて記事です。よかったら読んでみてください。

知っていれば広がるグラフの見方とクエリの書き方のちょっとしたコツ

$
0
0

こんにちはサービス開発部のエンジニアの鈴木達矢です。

最近サービス開発においてエンジニアだけでなくディレクタがSQLを書く機会やBI Tool*1を元に意思決定する機会が増えてきました。クックパッドでもモバイルアプリに新機能を足すなどの各施策前後のKPI比較や施策後の経過を見るためにディレクタやエンジニアがクエリを書いてRedashを用いてグラフ化する光景が日常化しました。

モチベーション

この記事に書かれている内容は統計学を学んでいればごく入り口にあたる初歩的なことです。しかしながら、その初歩的な統計を学んでいない人にとっては、ビジュアライズされたデータからその特性をひと目で即座に把握できないことがあったりします。そのような状態ではグラフ付きの資料をゆっくり読む分には良いのですが、例えばミーティングやカンファレンスなどでグラフの意図をすぐに理解して意見を述べるのは難しいです。また、自分がグラフを起こす側である場合でも、特定のデータに対してどのような集計をしてグラフ化すれば良いのか検討するのに時間を要してしまいます。

そこで今日は既にウィンドウ関数を使ったことがある方を対象にクエリを書いてRedashでグラフ化するまでの間に知っていると便利なコツをご紹介しようと思います。数式は用いずそれぞれの基礎知識の用途や特性について説明しますが、あくまで導入として捉えていただき、実用に際しては数式を理解していることをお薦めします。

クエリのちょっとしたコツ

突然ですが、ウィンドウ関数は使っていますか?ウィンドウ関数を利用すると行動ログを利用して新機能開発前の意思決定や開発後の評価が簡単に行なえますね。ここでは背景にある統計学的な基礎知識を紹介しつつRedshiftのウィンドウ関数を例にとってコツを紹介しようと思います。

Redshiftのウィンドウ関数

AVG() - 平均値を取得する関数

代表値(データ値をデータ集合との関係で表した値)の一つ。データ値をデータ数で割った値を取得します。

使用例

Redshiftのリファレンスの例では販売IDごとに販売数(qty)の移動平均(この場合は初日からの販売数(qty)の累計/販売ID数(salesid))を算出しています。読者がウィンドウ関数の基本的概念や構文を理解している前提としてここでは解説はしません。

select salesid, dateid, sellerid, qty,
avg(qty) over
(order by dateid, salesid rows unbounded preceding) as avg
from winsales
order by 2,1;

salesid |   dateid   | sellerid | qty | avg
---------+------------+----------+-----+-----
30001 | 2003-08-02 |        3 |  10 |  10
10001 | 2003-12-24 |        1 |  10 |  10
10005 | 2003-12-24 |        1 |  30 |  16
40001 | 2004-01-09 |        4 |  40 |  22
10006 | 2004-01-18 |        1 |  10 |  20
20001 | 2004-02-12 |        2 |  20 |  20
40005 | 2004-02-12 |        4 |  10 |  18
20002 | 2004-02-16 |        2 |  20 |  18
30003 | 2004-04-18 |        3 |  15 |  18
30004 | 2004-04-18 |        3 |  20 |  18
30007 | 2004-09-07 |        3 |  30 |  19
(11 rows)

引用元

このようにソートした状態の以前のレコードに対して平均を出せるのがウィンドウ関数の便利なところですね。

代表値を何にするのか?

私たちは普段何気なく平均を使いますが、いつでも平均を使えるのでしょうか?例えば、ある会社Aの求人で平均年収700万円で、もう一方の会社Bの求人では平均年収が600万円だったとします。あなたがそれらの求人を見ているとして、平均年収だけでAを選ぶべきでしょうか?

蓋を開けて個別のお給料を見てみます

  • 会社A 500 + 500 + 550 + 550 + 1400 = 7500

  • 会社B 600 + 600 + 500 + 700 + 600 = 7000

会社Aは一人の年収が1400万円で残りの人は500万円台です。Aに入社したとしてお給料が500万円台になる確率が高いです。平均値には次ような特徴があります。

  • データの中に大きく外れた値があると信頼度が下がる
  • 平均 x データ個数 = 合計 であることが保証されている

平均を読み解く側も、利用する側もこれらの特徴に注意する必要があります。では、代表値の他の選択肢にはどのようなものがあるのでしょうか?

MEDIAN() - 中央値を取得する関数

中央値:データを小さい順に並べた時にデータ数の二等分する(中央に位置する)データの値

先の給与の例で言うと中央値はそれぞれ会社Aで550万円、会社Bで600万円になります。

中央値にはこのような特徴があります。 - データの中に大きく外れた値があっても信頼度が下がりづらい

使用例

Redshiftのリファレンスの例では販売者ID(sellerid)ごとに販売数の中央値を算出しています。

*.sellerid:3のデータはデータが偶数なので中央の15と20を足して割った17.5が中央値です

select sellerid, qty, median(qty) 
over (partition by sellerid) 
from winsales
order by sellerid;


sellerid    qty median
---------------------------
1       10  10.0
1       10  10.0
1       30  10.0
2       20  20.0
2       20  20.0
3       10  17.5
3       15  17.5
3       20  17.5
3       30  17.5
4       10  25.0
4       40  25.0

引用元

NTILE() - データを順位に応じて集団に分ける

さてここまで代表値を扱ってきましたが、NTILEはあるデータが全体のどの分布に入っているのかを大まかに把握するためのものです。データの分布をN個の集団に分けた時に、あるデータの所属集団を見つける時に使います。

例えば自分のマラソンのタイムがトップ集団に入っているか確認したいときなどに使うことができます。

使用例

Redshiftのリファレンスではハムレットという同じ演目に支払われたティケット料金を4つのグループ(ntile(4))に分類しています。

select eventname, caldate, pricepaid, ntile(4)
over(order by pricepaid desc) from sales, event, date
where sales.eventid=event.eventid and event.dateid=date.dateid and eventname='Hamlet'
and caldate='2008-08-26'
order by 4;

eventname |  caldate   | pricepaid | ntile
-----------+------------+-----------+-------
Hamlet    | 2008-08-26 |   1883.00 |     1
Hamlet    | 2008-08-26 |   1065.00 |     1
Hamlet    | 2008-08-26 |    589.00 |     1
Hamlet    | 2008-08-26 |    530.00 |     1
Hamlet    | 2008-08-26 |    472.00 |     1
Hamlet    | 2008-08-26 |    460.00 |     2
Hamlet    | 2008-08-26 |    355.00 |     2
Hamlet    | 2008-08-26 |    334.00 |     2
Hamlet    | 2008-08-26 |    296.00 |     2
Hamlet    | 2008-08-26 |    230.00 |     3
Hamlet    | 2008-08-26 |    216.00 |     3
Hamlet    | 2008-08-26 |    212.00 |     3
Hamlet    | 2008-08-26 |    106.00 |     3
Hamlet    | 2008-08-26 |    100.00 |     4
Hamlet    | 2008-08-26 |     94.00 |     4
Hamlet    | 2008-08-26 |     53.00 |     4
Hamlet    | 2008-08-26 |     25.00 |     4
(17 rows)

引用元

他にも統計のごく入り口の知識さえあれば有効に使える関数がRedshiftには元から用意されています。 ウィンドウ関数

ここまででSQLの作成に役立つ知識について解説しました。

グラフ

ここからはRedashを例に表現したいグラフを作るのに役立つ知識について解説します。サービス開発の際には線グラフ、棒グラフ(ヒストグラム)、箱ひげ図、散布図などがよく使われます。ここではこれらに絞ってデータの種類に応じてグラフにする方法を解説します。

グラフの選び方

あるデータの集合があるとき、それらをどのように並べるとより直感的に伝わりやすいのでしょうか?

例えばある施策によって利用デバイスごとのページの回遊が増えたかを確認したいとします。

  • A:一回の訪問で訪れるページ数をデバイスごとにグラフに表したい
  • 施策:ページ間の回遊を増やす新機能を追加した
  • B :一回の訪問で訪れるページ数をデバイスごとに施策前後で比較したいので時間的経過と共にグラフに表したい

Aであればヒストグラムの一つの棒をデバイスの種類として、縦軸を1回の訪問で訪れるページ数にすることで表現できます。もしくは円グラフでデバイスごとに割合を表すと分かりやすいでしょうか。

f:id:tatsuya-suzuki-cookpad:20170317095037p:plainf:id:tatsuya-suzuki-cookpad:20170317095055p:plain

ではBの施策後の時間的経過を把握しやすく表現したいときはどうすればよいでしょうか?例えばヒストグラムの一本の棒をデバイスごとの訪問数で色分けして、その一本を特定の期間として横軸で時間経過を表します。

f:id:tatsuya-suzuki-cookpad:20170317095122p:plain

このように表したいデータの内容や特性に応じてグラフやその先の表現方法(ここでは色分けのこと)を選ぶとより分かりやすいグラフを作ることができるようになります。

以下はRedashのVisualisation Editorでの作業になります。

ヒストグラム

あるグループを一つの棒に表しグループごとの分布を比較するのに適しています。

例. あるレシピサイトを訪れる人をX軸で日にちでY軸で訪問回数で表す

Redash

Visualization Type: Chart、Chart Type: Barを選択します。

f:id:tatsuya-suzuki-cookpad:20170317102242p:plain

レシピサイトを訪れる人数は土曜日と月曜日に多いように見えます。一見なんの規則性も無いように見えます。

Group by

General tabのGroup byでは表示分けをする集団を分けることができます。 この例の場合は性別で分けてみます。

Stacking

General tabのStackingでは1つの棒にグループを上乗せ(スタックする)ことができます この例の場合は性別を上乗せしてみます。

  • enabled 性別を色分けする。棒の高さは訪問者数の合計を表すので曜日によって異なる
  • percent 性別を色分けする。棒の高さは1日の100%を表すのですべての曜日で同じ

この例の場合は日にちごとの合計数が知りたいのでenabledを選択しました。 f:id:tatsuya-suzuki-cookpad:20170317095212p:plain

これでも割合は分からなくはないですががグラフを分けると山が見えてきます。

  • 女性

f:id:tatsuya-suzuki-cookpad:20170317095237p:plain

  • 男性

f:id:tatsuya-suzuki-cookpad:20170317095306p:plain

女性は週末にかけて山があるのに対して、男性はなぜか3月13日月曜日に山があるのがわかります。このレシピサイトの男性の訪問数が平日に多いのは珍しいことです。どうやら3月14日のホワイトデーのお返しを作っている人が多い日だったということがわかりました。

先述のような性質の異なる分布を一つに表している状態を多峰性の分布といいます。このようにビジュアライズ後に規則性が分かりづらかったグラフでも分解することで分かりやすくなることが有ります。クエリにおいてはなるべく集計前の状態をRedashに渡して、後々Redashのグラフ機能に集計や表示軸を設定するのを任せるという方法を取ると編集がしやすいです。

箱ひげ図

与えられたデータを小さい順に並べ、データ数を最小値、上位75%、中央値、上位25%、最大値で区切られる集合に4等分します。そのときの中央値の2つの集合を箱で表し、両端の集合はそれぞれ線(ヒゲ)で表します。データ数で等分するので、ヒゲや箱の大きさが小さいほどその集合のデータ密度が高いという事になります。この様に箱ひげ図では一つの箱ひげでヒストグラムで行っていた様なデータの分布具合を大まかに表現できます。

箱ひげ図について詳しくはこちらを参考にしてください

Redash

Visualization Type: Boxplot

f:id:tatsuya-suzuki-cookpad:20170317095816p:plain

散布図

縦軸、横軸に2つの分布の量や大きさ等をそれぞれ対応させ、データを点で表した図。2つの分布の相関を調べるために使います。

Redash

Visualization Type: Chart, Chart Type: Scatter

f:id:tatsuya-suzuki-cookpad:20170317095859p:plain

疑似相関に注意

ある要因がXの分布の理由でありYの分布の理由でもあるとき、XとYに因果関係がある様に見えること。例えば冷やし中華がよく売れるときにはビールも良く売れるお店があったとして、冷やし中華がよく売れる原因は冷やし中華ではなくて暑かったからといった場合です。散布図で示せるのは相関関係であり因果関係ではないのでデータから意思決定をする際は注意しましょう。

その他

ここでは取り上げられませんでしたがその他にも様々なビジュアライゼイションがRedashには用意されています。

  • Pivot Table

ExcelやGoogle Spread Sheetで使ったことがある人には馴染み深いと思いますが、クエリの結果をクロス集計したものを表にします。

  • Word Cloud

クエリの結果に出現する単語数を頻度に応じて大きくワードクラウドに表示します。検索によく使われるキーワードなどを表示するのに使うと便利そうです。

  • Counter

クエリ結果の特定のカラム値を表示します。単一の数値をKPIとして常にダッシュボードに表示しておきたいときなどに便利そうです。

まとめ

この様にデータの特性やグラフの特性を知っていれば自分が作りたい資料を作る力や資料を読み解く力はずっと良くなります。と同時に、日常生活においてもニュースで提示される資料を疑う力もおまけで付いてきます。いきなり統計学を学ぶとなると初めての人にはステップが大きいですが、知っているとすぐに役に立つことから学習とリターンのサイクルを小さく回してちょっとずつグラフへの理解を深めてみてはいかがでしょうか?

*1:ビジネスインテリジェンス・ツールとは企業活動に伴うビッグデータを収集して分析するためのツールです

この gem を使っているアプリケーションを探す

$
0
0

技術部開発基盤グループの鈴木 (id:eagletmt) です。 Ruby アプリケーションが使っている gem を一覧できる GemCollector というツールを作りました。 今回はその GemCollector の機能と、GemCollector の利用シーンの拡大について紹介したいと思います

なお GemCollector は Rails Engine の形で gem として公開しています。 https://github.com/cookpad/gem_collector

GemCollector 作成の動機

GemCollector を作った元々の動機は我々開発基盤でメンテナンスしている gem を更新したいとき、とくに非互換を含む変更を行いたいときに、変更の影響を受けるアプリケーションを把握することでした。 社内でよく使われている gem であれば、事前に社内での使われ方を調査し、現実的に修正可能かどうか調べることができます。 そして現実的に修正可能であり実際に変更を行った後も、どのアプリケーションで更新作業が終わっていないかを把握しやすくなります。

GemCollector の動作

通常の Rails Engine を使うときのように、適当な Rails アプリケーションを用意し gem_collector gem をインストールして GemCollector::Engine をマウントして使います。 それに加えて PostgreSQL を用意したり GitHub のアクセストークンを設定したりする必要がありますが、詳しい手順は READMEをご覧ください。

GemCollector の動作はとてもシンプルで、GitHub の webhook の push イベントを契機としてそのリポジトリの最新のリビジョンを取得し、そこに含まれている Gemfile.lock の情報を DB に入れるだけです。 webhook は GitHub の設定ページから https://gem-collector.example.com/github-webhookを追加してもよいですが、GemCollector の Web UI からも追加できるようになっています。

New repository

それぞれの gem を利用しているリポジトリは Web UI から閲覧できるようにしています。実際に GarageClientをすべてのアプリケーションで更新したいときがあり、GemCollector を見ることで GarageClient を利用しているアプリケーションを把握することができました。

Repositories using garage_client

またこの利用シーンを拡張する形で、ある gem をバージョン X 以下で使っているリポジトリにバージョンアップのお知らせ issue を立てる機能が @taiki45によって追加されました。 バージョンでフィルタした上で、作成する issue のタイトルと内容を指定できます。

Create news issue on repositories

GemCollector の使い道

最初は非互換な gem の変更の影響を受けるアプリケーションを把握するために書かれたツールでしたが、実際に利用してみるとある gem を使っているリポジトリを把握できると便利な場面が他にもありました。

  • 自分のアプリケーションでその gem を使おうとしているが、困っている部分があるのでそこをどう回避しているのかを社内で実際に動いているコードで見ることができる
  • 自分がメンテナンスしている gem で Rails 3.x のサポートを切りたいが、まだ Rails 3.x を使っているアプリケーションがあるか調べることができる
  • ある gem で security fix を含むバージョンがリリースされたが、社内でアップデートが済んでいないアプリケーションはどれか把握できる

gem の最新度

社内の Gemfile.lock の情報が集まってくると、今度は「自分のアプリケーションが使っている gem はどれだけ最新か」というのも見積もれるようになってきます。 技術部では開発者の生産性向上を目標の1つとしており、生産性に寄与する指標の1つとしてどれだけ最新の gem を使っているかを数値化して使えないだろうかと考えていました。 そこで社内の他のアプリケーションが使っている gem のバージョンと比較してどれくらい上位にあるかのポイントを独自の基準で計算して、Up-to-date Point という数値として表示するようにしました。

https://github.com/cookpad/gem_collector/blob/v1.0.0/app/models/gem_collector/repository.rb#L17-L53

以下のスクリーンショットは GemCollector を動かしているアプリケーションでの実際の例です。概ね最大値である 1.0 に近い値になっていますが、社内で Rails 5.1.0.rc1 を使っているアプリケーションがあるため、Rails 系の gem に対するポイントは 1.0 ではなくなっています。

Repository gem_collector

また、登録されている各リポジトリのポイントを日次で InfluxDB に入れることで、Grafana でリポジトリ毎のポイントの推移を確認できるようにしています。 見易さのために比較的良いスコア (> 0.8) と悪いスコア (< 0.5) に分けて閲覧できるようにしています。

Up-to-date Point chart

Up-to-date Point はまだ実験的な値で、社内で適用したところ大まかな指標としては間違っていなさそうですが、まだまだ改善点は多そうです。 たとえばこの方法だと、社内で利用者が少ない gem の場合に高得点になりがちです。そのアプリケーションでしか使ってない gem の場合、バージョンに関係なくその gem に対するポイントは 1.0 になってしまいます。 RubyGems.org では週次の PostgreSQL のダンプデータを公開しており、これを利用することでより正確に gem の最新度を見積もれるかもしれません。 https://rubygems.org/pages/data

まとめ

社内のアプリケーションが使っている gem の情報を集める GemCollector と、それの利用シーンについて紹介しました。 GemCollector は単純なしくみですが、これによって見えるようになった情報はとても役に立っています。 GemCollector のようにアプリケーションに関するデータを集め、それを見える状態にしたり数値化してみるのはいかがでしょうか。

Webpackerを使ったRailsでのJavaScript開発

$
0
0

こんにちは。マーケティングプロダクト開発部の長田です。

この記事では、私が現在進めているプロジェクトで、Webpackerを使ったJavaScriptのモジュール管理を導入したので、それについて紹介したいと思います。

Webpackerとは

Webpackerとは、Webpackを用いてRails上でJavaScript開発をするために必要な一連の流れを提供してくれる、Rails organizationで開発されているgemです。

これまで、Rails上でJavaScriptのパッケージをどのように管理するか、また、モジュール依存をどのように解決するかについて、多くの選択肢があり、それらをどう組み合わせて使うのかについて悩まされてきました。 このブログでも過去に何度か記事が投稿されており、その中でも複数の選択肢が上げられています。

私のプロジェクトでは、ユーザーが入力するフォームが多く、「リッチなフォームで使いやすい入稿システムを作りたい」という要件があったので、JavaScriptのパッケージをいくつか組み合わせて作ろうと思っていて、Yarn、Webpackを使って実現しようとしたのですが、考えることが多いなという印象でした。*1

このような問題に対してWebpackerを使うと、ある程度Railsの開発プロセスにのったJavaScriptの開発ができるのではないかと思い、これを機に導入を試みました。

導入

Webpackerのセットアップ方法や使い方等は READMEに載っているので、そこを参考にしつつ進めることで導入できました。

以下では、導入するにあたって検討したことについて、主に次の2点について紹介します。

  • JavaScriptファイルの配置の方針
  • フロントエンドのテスト

JavaScriptファイルの配置の方針

導入にあたって、悩んだのはディレクトリ構成をどうするかでした。

ポイントは以下の2点です。

  • app/assets/javascriptsの扱い
  • エントリーポイントとしてビルドするファイル

app/assets/javascriptsの扱い

Webpackerでモジュールを管理できるようになったとはいえ、RailsはSprocketsを捨てる訳ではないらしいので、viewごとのアセットは今まで通り、app/assets/javascripts以下に置いても良さそうなのですが、個人的には、モジュールの管理がプロジェクト内で分散するのは避けたかったため、全てのファイルをapp/javascripts以下に置くことにしました。

幸い、私のプロジェクトはスタートしたばかりで、ファイルが少なかったので、app/assetsにあるものを難なく移行するとこができました。

エントリーポイントとしてビルドするファイル

現時点で、 rails webpacker:install で生成されるWebpackの設定では、app/javascripts/packs直下に配置したファイルがそれぞれエントリーポイントとしてビルドされるような設定になっています。

私のプロジェクトではapp/assets/javascriptsに置いていたファイルはSprocketsで1つのファイルにまとめていたので、それと同じようにするには、app/javascripts/packs以下にapplication.jsだけを配置して、そこから、他のモジュールを読み込むようにする必要がありました。

しかし、以下の点から複数のエントリーポイントを作るようにしました。

  • JavaScriptが必要なviewごとにview modelを作る方針*2
  • 全てのviewにview modelがあるわけではない
    • むしろ少ない

という状態だったため、view modelごとにエントリーポイントを作り、必要なviewからjavascript_pack_tagで読み込むようにしました。

これにはメリット・デメリットはありますが、メリットとして大きいと思うのは、

  • JavaScriptの挙動の影響範囲を特定しやすくなる

という部分です。

1つのファイルにまとまっている場合、コードベースが大きくなると、ロジックがどこで定義されているのか、また、ロジックがどこに影響するのかの見通しが悪くなってしまいがちです。特にViewごとにJavaScriptを書いていて、それが1つのファイルにまとまっていた時、ファイルをまたいでDOMのidやclassが被ってしまうと予期せぬ挙動を与えてしまうこともありえると思います。

なので、複数のエントリーポイントを作り、必要なviewごとに読み込む方針は上のような問題に対しては有効だと思いました。

一方で、デメリットとして大きい思うのは、

  • エントリーポイントごとに、パッケージがバンドルされ、ファイルサイズが大きくなりがち

というのが挙げられますが、

  • エントリーポイントに含まれるパッケージが、まだそれほど大きくない
  • Webpackの設定で、共通のパッケージだけ別でビルドして最適化できる

という点から、デメリットは顕著に現れないだろうと考え、packs以下にview modelを配置して、それぞれエントリーポイントとしてビルドする方針にしました。*3

フロントエンドのテスト

フロントエンドのテストを書く手段として、

  • E2Eテストで、ブラウザの振る舞いをテストする
  • JavaScriptのモジュール単位でテストをする

2つの選択肢があると思うのですが、私のプロジェクトでは、前者を採用することにしました。理由は、

  • フロントエンドにロジックを持っているとはいえ、そこまで複雑なロジックではない
  • サーバーサイドのテストと同じフレームワークで書けるので、導入コストがほとんどない

という点から、E2EテストでJavaScriptで書いたロジックのテストをすることにしました。

そこで問題になったのは、モジュールのビルドをどのタイミングで行うかです。

いわゆるRails wayなプロジェクトだと、Sprocketsをつかってモジュール管理をすると思うのですが、Sprocketsはアセットのリクエスト時にビルドをしてくれるので、phantomjsなどのヘッドレスブラウザを使ってテストをすることで、アセットのビルドを気にすることなくE2Eテストが書けました。

しかし、Webpackerを使う場合は、テスト実行前にモジュールをビルドする必要があり、その設定とタイミングで悩みました。

私のプロジェクトでは、test環境のビルド設定をdevelopment環境に合わせ、テストフレームワークのフック等とは別で、あらかじめアセットをビルドする方針にしました。*4

ビルドの設定をdevelopment環境と合わせることで、開発中でもプロジェクトのビルド設定(RAILS_ENV的なもの)を変更することなくテストを実行できます。 また、ビルドのステップをテストとは別にすることで、開発中には不要なビルドが走らず、こまめにテストを実行することができます。

現時点で、rails webpacker:install で生成されるWebpackの設定は、test環境のものは生成されないので、config/webpack/test.jsを追加しました。

module.exports = require('./development.js');

こうすることで、RAILS_ENV=testの場合でも、bin/webpack を実行することでdevelopment環境と同じようにビルドすることができるので、CIで以下のようなスクリプトを書いても、開発中と同じようにテストをすることができます。

export RAILS_ENV=test
bin/webpack
# additional pre process
# test

まとめ

この記事では、RailsアプリケーションでWebpackerを使ったJavaScriptの開発について書きました。 Yarn、Webpackを使ったモダンなJavaScriptの開発環境が、rakeタスクなどが用意されている状態で、シュッと整うのは非常にいいと感じました。

ただ一方で、packs以下に配置するファイルの方針を考えたり、デフォルトのWebpackの設定だとディレクトリ構成に制約があったり、test用の環境はそれぞれでセットアップしなければいけなかったりするのと、今回の記事では触れませんでしたが、現時点ではCSSのビルドに対応できていない等、課題はまだまだありそうだなという感触です。

最後になりましたが、この記事がRailsでのJavaScriptの開発について、少しでもお役に立てれば幸いです。

*1:ビルド成果物をアセットパイプラインにのせるのかどうか、テストやデプロイ、digest等をどうするかを考えなければいけないので、多いなと思いました。

*2:私のプロジェクトでは、Vue.jsを導入しており、ここでview modelと呼んでいるのは、root vue instanceにあたります。

*3:ただし、しばらくWebpackerを使っていくうちに、不都合が出てきたときを想定して、Sprocketsに切りもどせるようにコードを書くよう意識しています。

*4:私のプロジェクトではRSpecを使っており、ここでいうフックとは、before(:suite)等のことを指します。

モバイルアプリ開発における思いと工夫

$
0
0

こんにちは、技術部品質向上グループの茂呂一子(@)です。

3月18日にProductivity Engineering − Forkwell Meetup #4において、「クックパッドにおけるモバイルアプリ開発の工夫」というタイトルで発表しました。その内容を補完しつつ、最近のモバイルアプリ開発の取り組みについて紹介します。 (発表資料はこちら)

開発体制とスケジュール

クックパッドでは、Web/モバイルアプリなどのプラットフォームに依らず、機能ごとにチームを組んで開発を行っています。 例えば、検索機能、投稿機能といったサービス内の機能ごとにチームがあり、その中にデザイナーとエンジニアが所属しています。

モバイルアプリの開発という視点から見たときには、機能ごとのチームを横断し、より基盤的な業務を担うメンバーを含めて、iOS/Androidのモバイルアプリの開発を目的とするコミュニティがあります。(会社の組織編成と対応しない枠のため、ここではコミュニティと呼びます。) このコミュニティには、機能ごとのチームからディレクター、デザイナー、エンジニアが参加します。 ほかにも基盤的な業務を担うメンバーとして、モバイル基盤グループと品質向上グループのメンバーも参加しています。

アプリのリリースは、通常2週間から1ヶ月に1回行っています。リリーススケジュールを調整することはあるものの、スケジュールにあわせて機能を入れるかどうかを決める方法をとっています。 リリースに関わるエンジニアは、Android/iOSのリリースでそれぞれ10人程度です。

サービス開発で大切にしていること

私たちが開発で大切にしていることを、サービスを提供する立場と、ユーザー体験の2つの面から述べます。

まず、サービスを提供する立場からの視点です。

私たちのサービスでは、「毎日の料理を楽しみにする」ことに貢献するかどうかを重視します。 そのために「○○によって、毎日の料理が楽しみになる」といった仮説をたて、その正しさを明らかにする必要があります。 仮説は、サービスの変更や機能追加を含む施策として実装されます。 ユーザーさんに使われる様子をデータなどから検証することで、その施策に効果があったのか/なかったのかを明かにし、ひいては仮説の正しさを明らかにします。

施策の効果を計測するには、それが仮説どおりに実装されている前提で、その効果を計測します。 そもそも仮説どおりに実装されていなければ、効果の計測に意味がありません。 仮説どおりに実装されたときでも、例えば、実装された機能に到達できないことがあれば効果を計測できません。アプリの外側で、APIサーバーに問題があるなどして機能を提供できなければ、これもまた計測ができません。

したがって、施策を仮説どおりに実装していること、その効果を計測できる状態を維持することが大切になります。

次に、ユーザー体験の面から見てみましょう。

クックパッドはコミュニティサービスなので、ユーザーが離れていってしまうことはサービスにとっての損失になります。ユーザーが離れていってしまう体験とはどんなものでしょうか。 ユーザーにとって残念な体験が続くと、離れていってしまうことが考えられます。 例えば、操作性を損ってしまうクラッシュの問題があります。 他にも、施策の意図にないが、これまでできていた(当たり前になっていた)ことができなくなった。画面ごとにいろいろな操作があって、覚えづらく、操作に慣れることができない。といったことにも、私たちは注意しています。これらは、ご意見やレビューといったユーザーの反応をもとに、施策として意図しないネガティブな反応が増えることを「ユーザーにとって残念」と捉えているためです。

このようなことから、私たちは、体験の一貫性というものを大切にしています。

リリースフローにあわせたチェックポイント

私たちのモバイルアプリ開発ではリリースフローを定めて運用しています。 このリリースフローを、必要に応じて変更しながら運用しています。現在は、以前の記事では触れていなかった、チェックポイントとなるイベントをいくつか置いています。

今回は、リリースフローに定めたイベントの中から、2つを紹介します。 先に述べた開発において大切にしていることを損なわないための、キックオフと確認会です。

キックオフ

キックオフは情報共有の場としてはじまりました。

以前は、毎日、始業後に10分程度の時間をとって、その日のタスクを共有していました。 顔をあわせ、タスクを共有し、開発の状況を把握するための会です。これを朝会と呼んでいました。 しかし、朝会参加者が増えてきたことと、全社的なフルフレックスへの移行に伴って時間をあわせるコストが増したことから、チャットツールであるSlack上で行うようになりました。

朝会チャットは、専用のチャンネル(部屋)を用意し、毎日、各自のタスクを書き込む形式をとっています。 チャットというツールの性質上、非同期に行われます。そのため、議論に発展した場合も朝会自体の流れを阻害しないというメリットがあります。 一方で、目的が「今日のタスクを共有する」というところに絞られる傾向があり、大元の施策のはなしや、ちょっと未来のはなしといった関連情報を共有することが減ってきました。また、非同期であるがゆえに、他者の書き込みへの関心が薄れやすい傾向にあるようです。 他にも、顔をあわせることで得ていた、顔色や声色から調子の悪さを知る、ということがむつかしくなりました。

全体的に他チームの施策への関心が薄くなった結果、ひとつの問題が発生しました。

ひとつの画面に、異なるチームから複数の施策が実装されました。その結果、同時に表示されたことにより表示がくずれ、効果の計測ができない状態になってしまったのです。

情報の共有ができていなかった、実装前にこの事例のような競合は把握したい、という反省のもと、キックオフがはじまりました。

キックオフは、そのリリースに関係するディレクター、デザイナー、エンジニアすべて(最近では20人程度)が参加します。(参加は任意なので、毎回全員が参加するわけではありません。)

画面や操作に大きな変更がある施策を共有し、必要であればその調整を行います。すべての変更を共有するのではなく、大きな変更があるものを選んで共有を行っています。

施策を共有し、事前調整をすることで、先のような失敗は起きなくなりました。 また、実装前に他チームの施策を知ることで、それへの懸念を伝える機会となっています。仕様の未定義が見つかることもあり、うまく機能しているといえるでしょう。

確認会

確認会は、そのリリースに関わるディレクター、デザイナー、エンジニアすべてが参加します。 すべての変更をマージしたアプリをインストールし、変更内容を確認しながら操作します。

ここでは、全体の一貫性が保たれているか、意図どおりに実装できているかを確認します。 いわゆるリグレッションテストとは違い、過去の実装範囲への影響よりは、新たに実装したものに注目して確認を行います。

確認回は、ユーザーインターフェース(以下UI)とユーザーエクスペリエンス(以下UX)の一貫性を保ちたい、という要求からはじまりました。これは、各チームが独立して施策を実装するので、アプリ全体をみたときにUI/UXの一貫性を失いやすいためです。

あわせて、確認回では、検証端末を持ち寄り、開発中より多くの種類の端末で操作を行うようにしています。これは、開発中に動作を確認する端末が偏りやすいことから、端末依存の問題の検出を目的にしています。

これらの取り組みにより、UI/UXのばらつき、特定端末での問題といったものが見つけやすくなりました。 また、開発中には気づかなかった違和感を見つけることもあります。 見つかった問題は、重要度に応じて修正を行います。ものによっては、次のリリースで対応する場合もあります。

確認会を設定した目的は達成されつつありますが、それだけで開発がうまくいくわけではありません。

例えば、特定の種類のユーザーでしか起きない問題が、確認会以降で見つかるという問題があります。 実はクックパッドのユーザーは状態の種類が多く、無料ユーザー/課金ユーザーというわかりやすい物の他にもいくつか存在します。これらはサーバー側でユーザーを用意するだけでは、網羅することがむつかしく、状態の変化も含めて確認すべきバリエーションが多いという状態です。 状態の掛け合わせで起きるような問題ではない、特定条件のユーザーを模倣すると比較的簡単に見つけることができる問題なので、もっと早く見つけられるようになりたいと思っています。

まとめ

モバイルアプリ開発をうまく回すための、私たちの工夫の一部として、キックオフと確認会について紹介しました。

クックパッドではこのような取り組みを共に実施し、より良いサービスを提供し続ける為にエンジニアを募集しています。ご興味のある方は、是非とも覗いてみてください。

Consumer-Driven Contract testing in Cookpad

$
0
0

こんにちは、技術部の taiki45 です。3月17日に ICST 2017 の参加者などを招いたミートアップがあり、そこでクックパッドの開発とテストへの取り組みについてお話をしました。この記事では資料とセッション中に行われた質疑応答を元に、発表についての補足をしたいと思います。このミートアップについては、後日開催報告記事が出る予定です。

今回はクックパッドをご存知ない方に向けて、クックパッドでのテスト領域での取り組みについて紹介しました。そのため、前半部分では後半への橋渡しとして、クックパッドがどういうことに取り組んでいるか、またクックパッドでのソフトウェア開発の規模や様子について俯瞰した視点でお話しました。

特に今回の話では、分散型アーキテクチャを選択した際に起きる問題が主題だったので、チーム構成とシステム構成の面を説明しました。具体的には、事業領域に沿ってチームが分割されていること、システムもチームの分割に沿うように分けられていて、システム同士が協調して動作する構成になっています。

プロダクトの規模について紹介をしている場面で、月次利用者数とともに “プロダクトとしての Cookpad” だけでのテストケース数をお話した際には、「そのケース数は自動テストのみを数えているのか?」「自動テストのみです」といったテストエンジニアの集まりらしいやりとりをしました。また、その数のテストケースを実行している仕組みについて質問があったので、分散テスト実行システムである RRRSpecについて説明をはさみました。

「ビルドとリリース」については、コードレビュー後のマージによって自動テストジョブがキックされ、その後の Docker イメージのビルド、開発環境サーバーへのデプロイ、本番環境同等のカナリアサーバーへのデプロイが自動で行われています。カナリアサーバーへのデプロイ後は、カナリアサーバーに対してスモークテストが自動で実行され、さらに必要に応じて開発者による動作確認が行われます。本番環境へのデプロイは細かい粒度で行われています。リリース対象の機能やバグ修正に責任を持つ開発者が明確になっているので、開発者の責任でデプロイ後の動作確認をしています。問題があれば開発者がロールバックを行います。

テストに関する取り組みについては、「実践 Pact:マイクロサービス時代のテストツール」で紹介したことと同じく、Consumer-Driven Contract testing パターンを適用したことについて紹介しました。Pact の仕組みについては特にデータセットアップに関する質問が多く、質疑応答や後の懇親会等で「本番環境は使用せずテスト環境で Provider 側のテストを実行すること」や「"Provider States"という仕組みでデータセットアップ処理に名前をつけて Provider 側でテスト時にデータを用意すること」を補足しました。


テストや開発環境における自動化に関して議論したICST2017 unofficial meetup

$
0
0

技術部品質向上グループの松尾(@Kazu_cocoa)です。

2017年3月13日〜2017年3月17日の間に、東京にてICST2017という国際学会が開かれました。 その学会に基調講演としてGoogleの方などが来日しました。そのさい、非公式ながらミートアップを開いたのでその時の学びを共有したいと思います。

ICST2017とは

ICST2017とは、2017年に開催された第10回 IEEE International Conference on Software Testing, Verification and Validationの略です。Webサイトはこちら。 そこではソフトウェアテストや開発環境、品質に関する研究や事例が発表され、議論されました。 今年は10周年である上に、この会が始まって以来、初めて日本で開催されたようです。

学会と聞くと学術的すぎると思うかもしれませんが、比較的実務に近い話や産業界で実際に活動している人も参加者に多かったです。 例えば、会の中にありましたパーティーでは、様々な国の産業界で働いている多くの方と会話することもできました。

ミートアップ開催のきっかけ

このICST2017のkeynote speakerの1人としてGoogleからJohn Miccoさんが登壇しました。 とあるご縁から彼と直接やりとりを行うきっかけを作ることができたので、ICST2017に絡めてその日程付近で彼や彼の同僚の人とmeetupを開けないか誘ってみました。 好感触の返事をもらえたので、国内のテスト、品質、CI/CD周りに従事している幾人かの知人に声をかけ、今回のmeetupを開きました。

ゲストや知人のほか、一般参加者も募るため、以下の通りmeetup.comにも募集を開き少数ではありますが参加者を募りました。 基本的には英語による議論になるため、募集はmeetup.comに絞りました。

https://www.meetup.com/Tokyo-Software-Test-Quality-Meetup/events/238376025/

開催にあたり

実際に声をかけて発表者を募り、最終的に以下の人たちに話してもらいました。

  • Cookpad
  • DeNA
  • Mercari
  • Rakuten

スライドは一番下に参考資料として添付しています。

また、ICSTのために日本に来日している人もいましたので、日本料理も幾つか楽しんでもらうことができるよう、寿司(板前さん付き)や日本酒も用意しました。フードスポンサーとして以下の企業様方に協賛していただきました。

f:id:kazucocoa:20170404135813p:plain

meetupの前に

スケジュールの都合上、イベント前に時間の余裕があったのでJohnさんにクックパッドオフィスを案内しました。 途中からBaoさん(Johnさんの同僚の方で、ICST2017ではパネルディスカッションのセッションで登壇された)も加わり、3人でオフィスを回りました。

そこの中で、ビルド環境としてのBazelの話や、翻訳タスクのGoogleの現状の話、クックパッドの海外事業部の取り組みの話を主にしました。

また、弊社における日本/海外事業部における現在ある開発環境や取り組みの差などの話もしました。 例えばGoogleが1つのリポジトリで全て管理していることに対して、クックパッドではチーム/事業部など単位はありますが様々なリポジトリが独立して存在していることに触れることもありました。 それに対してJohnさんは複数チーム、事業部などでコードを共有したりしないのか?というような疑問を投げかけてきました。 クックパッドの文脈では、現状は海外事業部と国内を主とする事業部でサービスの長さに差があり、現状はこうなっているというような話、モバイルアプリといったところではライブラリを共通化していきたいと言った話もでている、と言った時代的な背景などを共有しながら、リポジトリ管理に関して意見を交わしたりしました。

meetupにおいて

実際のmeetupでは、先ほど挙げた4社のスライドを約10~15分間隔で発表しながら、聴講者が適宜質問を行ったり議論したりしながら進行しました。 中には、Johnさんが発表者にかわって回答する場面もあり、白熱した議論が展開されました。

幾つかのスライドの中で共通に存在していた課題は、やはりどうスケールするか、でした。 つまり、組織やサービス、関わる人といった様々な要素の規模が大きくなるにつれて問題になる、分断された環境や情報をどう共有し、集約していくかという問題でした。

この場にいた人の多くは実際にソフトウェアエンジニアとしてテスト、開発環境、様々な自動化にとりくんでいる人たちです。 そのため、ある程度の知識をあらかじめ保有している状態で議論ができたことも大きいのかもしれません。

meetupを終え、その先へ

GTAC2017

今回の発表の中には、GTAC2017へ是非とも応募してほしい!というものもありました。 GTACとは、Google Test Automation Conferenceの略で、2006年から毎年行われ続けている歴史のある自動化カンファレンスです。 今年はおそらくロンドンであるそうです。

参加者にも是非とも参加しなよ!とあったので、興味のある人はまずは応募してみてはいかがでしょうか!?(現地に行くことができるかは抽選です)

ICST2018

最後に、忘れてはダメなものとしてICST2018があります。 次回はスウェーデンのようです。 是非とも産業分野、学術分野で応募して採択、もしくは一般参加者として参加して自動化周りの先を見てみませんか?

最後に

今回、幾つかのご縁が重なりこのように濃いmeetupを開くことができました。 ご協力いただいた方々、本当にありがとうございました。

英語を主体としてソフトウェアテストやその周辺のことを対面で議論できる場所は日本ではおそらく少なく、そのためこのような場所が参加者、または参加者の所属する企業などへ広がると幸いです。

資料(発表順)

Cookpad

DeNA

Mercari

Rakuten

ユーザー基盤を作り直しながらRailsでのサービス層に向き合う

$
0
0

こんにちは。パートナーアライアンス部の諸橋 (@moro) です。

突然ですが、わたしはいまクックパッドの「ユーザー基盤」を再構築しようとしています。 一口に「ユーザー基盤の再構築」といっても、そのゴールが何を指すかは(わたし自身にとってもまだ)漠然としており、固定されたゴールは見いだせていません。しかし後述するように、いくつかの問題は明確な形を取っています。言い換えると、それら明確な問題と向き合いながら『柔軟でいい感じのユーザー基盤を目指す』というのがこの再構築プロジェクトの目的です。

その第一歩目として、ユーザー登録部分を現状のクックパッド本体とは別の小さなRailsアプリケーションとして実装を進め、つい先日、一部の限定された利用者の方に向けて公開することができました。 今後も様子を見ながら公開範囲を拡大していく予定です。

再構築の背景

ではその「明確な問題」とはなんでしょうか。

最大のものは、ユーザー登録のあり方が時勢にそぐわなくなってきているのではないかという懸念です。たとえばクックパッドのアカウントはメールアドレスとパスワードを登録していただくのが基本になっています。しかし世の中のトレンドとしてEメール自体の利用シーンが減るに連れ、それを必須とするサービスまで使いづらいものになるのではないかという危機感がありました。そのため、ユーザー基盤に大きく手を入れたいというニーズがありました。

もう一つは、クックパッドのコードベースがかなり巨大化・複雑化していることです(参考)。巨大で複雑なコードベースの上では、上記のユーザー登録のあり方の抜本的に変更することは難しいですし、日常の開発においてもある機能の修正が他の機能のバグのきっかけになったり、自動テストの実行時間も長くなったりするなどの問題がでています。そのため各機能を別のサービスとして実装した上で連携させるマイクロサービス化を進めています。その方向性を踏まえ、今回のユーザー基盤の再構築でも、ユーザー登録やログインといった機能を別サービスとして実装することにしました。

サービス層への興味

また、せっかく新しくRailsアプリケーションを作るのだから「Railsにおけるサービス層の実現」という技術的関心にも向き合うことにしました。

Railsは、DBのテーブルをそのまま読み書きするような単純なアプリケーションを作ることは簡単にできますが、より複雑なアプリケーションではそのぶん慎重な設計が必要になるため、Trailblazerのような複雑なWebアプリケーション構築を支援するライブラリが注目を集めています。

私自身もこのサービス層の実現にはおおいに興味があるいっぽう、今回はまだコード規模が小さいためにTrailblazerそれ自体の導入は先送りし、Rails上でフォームオブジェクトパターンによるサービス層の実現に挑戦することにしました。

この記事では、そういったビジネス上の背景と技術的興味を踏まえてアプリケーションを作る過程の試行/思考を紹介します。

最小限の機能から少しずつ進める

再構築を決意したとは言え、サービスにおいて「ユーザー」というのはとても大事かつ複雑なドメインモデルです。

実際に、現クックパッドのActiveRecordモデルクラス(以下ARモデル)Userはファイル中で定義されるメソッドが328個、includeしているモジュールが14個、has_(one|many)合わせて175個の関連が定義されている3,000行を超えるクラスです。このすべてを一気に新しいユーザー基盤(以下、新基盤)に置き換えようとしても、影響範囲が大きすぎてリリースにたどりつけないのではないかという懸念がありました。

そこで今回は最小限の機能をリリースすることを重視し、従来より負担の少ないユーザー登録フローを実現するアプリケーションを開発しました。

現方式では(1-1)ファーストビューで必要な情報をすべて入力したあとで、(1-2)到達確認のためにメールを送信し、(1-3)メール中のURLにアクセスすると登録が完了するという流れで登録します。 対して新基盤では、(2-1)ファーストビューではメールアドレスのみ入力し、(2-2)すぐに到達確認メールを送信し、(2-3)メール中のURLにアクセスしたあとに残りの情報を入力する、という流れです。ファーストビューでの入力項目が減る分、ユーザー登録する際の負担が減るであろうと見込んでいます。

また、新基盤のデータの持ち方やインフラ構造についても、現行クックパッドからの漸次的な改善を進めていくために、以下のような方針を取りました。

  • 新基盤では、現クックパッドとDBを共有する。
    • 新しいフローでのみ必要なデータは新規のDBに、現クックパッドで使うデータは現DBに書き出す。
    • 現DBに書く場合でも、ビジネスロジックは新たに書き直す。
  • 今後もインクリメンタルな移行を進めるため、現サービス https://cookpad.com/以下の特定のパスをリバースプロキシで分岐して新基盤と統合する。
  • 技術要素は社内推奨かつ新しいものを採用する。
    • 現クックパッドからの移行がしやすく、開発者の熟練度も高いRuby on Railsを採用する。
    • 技術的な進歩にキャッチアップするため、Ruby 2.4 + Rails 5.1.rc1 + Webpack + React などで開発する。
    • ECS上にアプリケーションをデプロイするためのHakoなど、社内の標準的なインフラ構成に乗せる。

このように「最小限の機能で、ちゃんと動くソフトウェア」をリリースすることを優先した結果、ユーザー登録のみを行うアプリケーションとして最初のリリースを迎えられました。

アプリケーションを少しずつ設計する

さて、再構築の方針やファーストリリースのスコープは決めたものの、それだけではアプリケーションはできません。 今後の拡張に対して柔軟に対応でき、かつ複雑だったり無理のある構造にしないため、慎重にアプリケーションを設計しました。その過程も紹介します。

最初の一歩: メールアドレスを登録する

出来上がった新基盤でのユーザー登録フローでは、利用者が目にする最初の画面は下記のようなものになります。

f:id:moro:20170406152442p:plain

これをみてわかるように、入力フォームはメールアドレス一つだけというシンプルなものにしました。この画面からメールアドレスを登録すると、メールアドレス確認用のリンクが記載されるメールが送られます。利用者がそれをクリックすることで、メールアドレスの到達確認がなされたとみなします。

そのため最初は、「メールアドレス登録」を表すARモデルを作り、メールアドレスと確認用URLに付与する予測が困難なトークン文字列(以下、確認URL用トークン)を作りました。カラム定義は次のようにしました。

  • 登録対象のメールアドレス(文字列)
  • 確認URL用トークン(文字列)
  • 確認URL用トークンの有効期限(タイムスタンプ)
  • 登録完了日(タイムスタンプ, 初期値null)
  • Railsタイムスタンプ(created_at, updated_at)

このデータを永続化するとともに、入力されたメールアドレスに対し、確認用メールを送ります。

次の一歩: 確認URLから登録完了する

確認用URLからアクセスする画面はこのようなものになります。

f:id:moro:20170406152434p:plain

ここでパスワードと生年月日を入力してもらい、メールマガジンなどの設定を見直してもらうと登録が完了します。

一見すると単純な画面ではあるのですが、ここから登録すると下記のような、たくさんの処理が走ります。

  • 現DBのユーザーテーブルにデータを作る。
    • 合わせて、現DBのユーザーのデータ一式を作る。それらは複数のテーブルに分割されている。
  • 新基盤のDBでも、そのメールアドレス登録を使用済みとして更新する。
  • 登録完了のメールを送信する。
  • 他システムにユーザー登録完了を知らせるためのAPIを叩く。

そこで、この処理ではARモデルを直接読み書きするのではなく、フォームオブジェクトを導入し、「ユーザー登録完了フォーム」として抽出しました。

これにより、以下の観点で「無理のないアプリケーションコード」にできたと思っています。

  • フォームの入力値をrequestparamsハッシュから取り出したあとの、Plain OldなRubyオブジェクトにできた。そのため、モデルのユニットテストとして一連の処理をテストできた。
  • 複数ARモデルのオブジェクトを一気に保存する場合に、関連先オブジェクトの内部構造と密結合してしまいやすいaccept_nested_attributesを避け、ネストのないActiveModelモデル内にカプセル化できた。

これで主要な入力は出来てきましたが、前のメールアドレスの登録画面や、それを永続化するテーブル構造に気になるところがでてきました。

メールアドレス登録画面のフォームオブジェクト化

前述のように、メールアドレス登録フォームでは、入力されたメールアドレスと確認URL用トークン、有効期限を1つのテーブルに永続化していました。これは初手としては手頃な落とし所ではありましたが、いくつか気になる点がでてきました。

まず、再構築の目的には、近い将来にメールアドレス以外でのユーザー登録できるようにしたいというものもありました。そうすると「利用者がユーザー登録しようとしたこと」そのものと「そこで登録してくれたメールアドレス」は分割すべきように見えます。 また、登録完了した時点で現DBに書き込まれるユーザーも追跡可能にしておきたくなります。

それらを考慮し、テーブル構造を以下のようにしました。

  • ユーザー登録をしようとしたことテーブル

    • 確認URL用トークンの有効期限(タイムスタンプ)
      • メールではない「新規登録」であっても、有効期限は存在するはずという仮定で
    • 完了後現DBに作成されたユーザーのID(数値, 初期値null)
    • 登録完了日(タイムスタンプ, 初期値null)
    • Railsタイムスタンプ(created_at, updated_at)
  • メールアドレス登録テーブル

    • ユーザー登録したことテーブルへの外部キー
    • 登録対象のメールアドレス(文字列)
    • 確認URL用トークン(文字列)
    • Railsタイムスタンプ(created_at, updated_at)

なお「現DBのユーザーID」と「登録完了日」をのみを抽出して「登録完了したこと」テーブルに分けるアイディアもあり、個人的にもそのほうが好みではありました。しかし、そこを分割する利点がまだ少ないこと、将来的にそうしたくなった場合でも無損失に分割できそうなことなどを考慮して、いまの2テーブルの構成にしています。

さて、このようにテーブルを分割するとRailsからも複数のARモデルを扱うことになります。結果として、メールアドレス登録画面にもフォームオブジェクトを導入することでスッキリ書けるようになりました。

まとめ

このように、機能がまだ少ない簡単なWebアプリケーションであっても、アプリケーション設計で考えられることはたくさんありました。今後も継続的に開発を進めながら設計を洗練させていきたいと思っていますし、あるいはTrailblazerの導入なども考えていくつもりです。

またこれ以外にも、Reactを使ったクライアントサイド設計や、その前提となるサーバ側/JS側での責務の切り分け、自動テスト戦略などなど、あらためて言語化してみると様々なレベルで設計判断が必要となりました。また別の機会に、それぞれ紹介できればいいなと思います。 さらに、今回の「最小機能でリリースする」というスコープ決定や、今後もやりたいことリストから取り組む順番づけなど、ソフトウェア開発は大小の判断の連続なのだなあというのをあらためて実感します。

こういった判断は、それぞれの現場によって、やりたいことも前提も既存コードも大きく違うため一概には言えないと思いますが、今回の話が一つの例として参考になれば嬉しく思います。

利用者数の増加に伴う、ユーザー行動の変化と対応について

$
0
0

こんにちは。サービス開発部ディレクターの新里です。

iPhoneアプリ「みんなのお弁当 byクックパッド」を担当しています。本アプリでは、自分の作ったお弁当をクックパッドのレシピ付で記録したり、アプリ内で共有したりすることができます。リリースから約1年半経ち、35万ダウンロードを超えました。今回は、サービスディレクターとして、アプリの利用者数の増加に伴うユーザーの行動の変化について、サービス開発側で実施した対応と合わせて、まとめようと思います。

1.投稿数が増加

アプリのダウンロード数の増加に比例して、お弁当の投稿数*1も増加しました。大半の投稿はガイドラインに沿ったものですが、そうではない投稿も中にはあります。

ガイドラインに沿わない投稿の増加

みんなのお弁当のガイドラインでは『外出先に持っていく食べ物のこと』をお弁当と定義しており、このガイドラインに則っていないものに関しては、投稿ユーザー宛にメールをお送りし、投稿内容について確認をお願いしています。この目視で公開されたものをチェックし、メールをお送りする運用は、リリース当初から変わらずに継続しています。基本的には、メールで連絡させて頂くと、ガイドラインの要旨をご理解頂き、誤った投稿に関しては見直していただけます。

追加で実施した対応

しかし、新しい事案として、日本語以外の言語で投稿されている方がガイドラインに則っていないケースが複数回発生しました。考えられる要因としては以下の2つです。

  • 日本語のガイドラインが読めない
  • 日本の文化に馴染みがなく、お弁当がどういうものかという認識が曖昧

日本国内向けのアプリなので、ガイドラインの英訳を掲載するのは見送り、日本語の読解が出来ていない可能性のあるユーザーに対しては個別に、お弁当の定義や投稿内容のご確認について記載した英文メールをお送りするようにしました。少し地道な方法ではありますが、英文メールをお送りした方々において、ガイドラインに則っていない投稿は収束したので、効果はあったと言えそうです。

2.コミュニケーションが活性化

ブックマーク機能の使われ方が変化

アプリ内で公開されたお弁当には「参考になった」ボタンを押すことができます。これは、レシピや盛り付け方法が参考になったお弁当をブックマークして、後日見返しやすくするための機能として実装したものです。 しかし、利用者数が増えることでこのボタンがブックマークとしてではなく、「いいね!」のようなライトフィードバックの意図で使われることが増えました。1人あたりの「参考になった」タップ数の推移はGoogle Analyticsのイベント数から確認しています。 f:id:Teriyakky:20170410101534j:plainまたmixpanelも併用しており、こちらはユーザーが想定した通りの動きをしているか、想定外の離脱などはしていないかなどを確認するのに使っています。

追加対応は実施せず

ブックマーク機能の使われ方が変化しつつありますが、さらにコミュニケーションを活性化するための機能の追加などは行っていません。あくまで本アプリの主目的は「作ったお弁当をレシピ付きで記録・共有」であり、コミュニケーションを活性化するための機能追加に関しては、本質からずれると判断したためです。 (ただし、ブックマークの側面が弱くなっていることで不便さが出てきたり、改修が必要と判断した場合は、今後対応する可能性があります。)

3.投稿後の反響が大きくなった

トップ画面掲載への反応が変化

アプリのトップ画面には毎日お弁当を1つ運営側でピックアップして掲載しています。これは、アプリを開いた時に更新性を感じさせることと、漠然とお弁当を作ろうと思っている方へのリコメンドの意図がありました。 それが時間の経過と共に、「ピックアップに掲載されるようなお弁当を作りたい」という憧れの対象になり、掲載されることが「日々のお弁当作りのモチベーションになる」という声を多く頂くようになりました。

追加で実施した対応

トップ画面への掲載がお弁当作りのモチベーションになる、嬉しい事としてユーザーが捉えるようになってきたので、ピックアップに選出されたユーザー宛にメールをお送りするようにしました。これによって、ピックアップ掲載という嬉しい出来事をユーザーが見逃さないようにしています。

4.クックパッド本体の活用事例が増加

レシピが紐付いたお弁当の投稿が増加

アプリリリース当初は、レシピが紐付いていないお弁当の投稿(写真だけなど)が大半でしたが、利用者が増え「日々のお弁当を共有する場」として定着してくると、お弁当の写真と共に参照したクックパッドのレシピを投稿する方も増えてきました。

  • 美味しかったレシピを他の人にも教えたい
  • 憧れのお弁当のレシピを作ったから報告したい
  • 自分のオリジナルレシピを見て欲しい

レシピを紐付けたお弁当を投稿する思いは様々あるようですが、こういう状況の中で、クックパッド本体サービスへの関わり方にも少しずつ変化が出てきました。 お弁当を公開しているユーザーのうち、つくれぽを送ったことがある人の割合は、リリース当初の25%前後から40%前後へと増加、レシピ投稿に関しては19%前後から30%前後へと、それぞれ増加しており、投稿することに積極的になってきている状況が伺えます。

追加で実施した対応

レシピやつくれぽを投稿することは、お弁当の記録の一環であり、モチベーションにもつながるため、よりスムーズに投稿できるように、みんなのお弁当アプリから直接つくれぽを送れるようにしました。 f:id:Teriyakky:20170410121613j:plain(右上・自分のお弁当詳細画面に導線を設置)

機能リリース後は、今までつくれぽを送ったことがなかったユーザーがつくれぽを送るようになったりと、順調に利用されています。

まとめ

人が集まると、機能自体は変わっていなくても、その使われ方や位置づけが変わったり、新たな課題が生まれたりします。 いち早く変化を察知するために、まずは自分自身で日々サービスを使い、変化に気づくことが大切です。その後、必要に応じて解析ツールを使って分析を行い、数値の裏付けを取って施策を検討するのですが、全てのニーズに応えようとはせず、コンセプトやペルソナをもとに実施する対応についてはきちんと優先順位付けし、時に対応を見送ることも重要です。

クックパッドでは、このような感じで一緒にサービスを成長させていきたい人を募集中です。ご興味のある方は、是非とも覗いてみてください。

iPhoneアプリ みんなのお弁当 byクックパッド

*1:ユーザーは、お弁当を投稿する際に公開か非公開かを選択することができ、投稿数はその合計です。

iTunes Connect から発行したプロモコードをカメラで読み取る

$
0
0

 こんにちは、iOS エンジニアの中村です。iOS クックパッドアプリの開発では、iTunes Connect から発行したプロモコードを使いリリース前に動作確認をしています。(2013年頃から審査通過後の動作確認がこの方法で出来るようになり、リリース時の緊張感が少し軽減されました)ここまではいいのですが、このコードは文字列で発行されるためキーボードで入力しなければなりません。毎回ランダムな文字列を手入力するのは避けたいです。そこで、このコードを iTunes Card に記載されているコードのようにカメラで読み取れる画像を生成するツール icccigを作りました。この記事では、このツールの製作過程をご紹介します。

  • App Store.app > おすすめ > コードを使うの画面(カメラで読み取るという選択肢がある)

f:id:nkmrh:20170406151004p:plain:w300

フォント

 テキストエディタに Helvetica Nune でコードを書き、その周りに枠線を付けたものが認識できるか試しましたが、枠線は認識してもコードを認識することはできませんでした。そこで、コードに使われているフォントを WhatTheFontで探しましたが、これはというものは見つかりませんでした。探している過程で「光学式文字認識のための字形(英数字)」というものを知り OCR-Bがとてもよく似ていたのですが、これも認識することはできませんでした。

  • OCR-Bで書いたコード

f:id:nkmrh:20170406151015j:plain:w300

  • iTunes Card のコード

f:id:nkmrh:20170323132333j:plain:w300

 フォント探しは諦め、Google の画像検索で iTunes Card のコードが沢山写っている画像を見つけることができたので、この画像からフォントを作るアプローチに変更しました。しかしよく見るとこの画像には足りない文字がありました。例えば 12BZ等々。ここで iTunes Connect からコードを100個発行して出現文字を確認したところ、そこで使われていたのは以下の20文字でした。

iTunes Connect から得られたコードの文字の種類
数字 3, 4, 6, 7, 9
英字 A, E, F, H, J, K, L, M, N, P, R, T, W, X, Y

 お店で売られている iTunes Card に使われているコードは16桁なのに対して、iTunes Connect から得られるのは12桁です。出現文字が少ないのは恐らくこのためですが、今回は iTunes Connect から得られるものに対応できればいいので足りない文字は無視して進めました。Sketch で文字をスライスした画像を並べて試したところ認識できました。これでフォントは完成です。

スクリプト

 スクリプトは2つ用意しました。1つは文字列からカメラで読み取れる画像を生成するスクリプトです。これは上記で作成したフォントでコードを描き、その周りに枠線を付けた画像を生成します。枠線の太さと文字からの距離も重要な点です。枠線をうまく認識できるように微調整してこのスクリプトを完成させました。

  • icccig で生成した画像

f:id:nkmrh:20170406151023j:plain:w300

 2つ目は生成した画像を社内画像共有サーバにアップロードし、そのリンクのリストを生成するスクリプトを用意しました。issue にこのリストをコピペしておけば、開発関係者がコードを手入力せずに済みます。

最後に

 上記のフォントとコード画像を生成するスクリプトは こちらのリポジトリにありますのでよかったら試してみてください。実際の運用で試したところ、確認項目の多いディレクターやテストエンジニアに好評でした。このツールで一旦解決できましたが、一方で Xcode プラグインとして作った方が良かったかもしれませんし、他にもっといいツールや方法があるかもしれません。何かいいアイデアをお持ちの方は是非教えていただきたいです。

ハードな案件のやわらかいプロジェクト管理

$
0
0

研究開発部 兼 クックパッド料理教室の伊尾木です。 暖かくなったり、寒くなったりと気温差が激しいですが、皆さんお体は大丈夫でしょうか。

ところで、最近クックパッド料理教室で、ビジネスモデル変更に伴うリニューアルプロジェクトを実施しました。

f:id:woochanx:20170417111032p:plain:w300
(ビジネスモデル変更に伴う全面リニューアル)

私はPMと開発リーダーを担当したのですが、そこで実施した「やわらかいプロジェクト管理」についてご紹介したいと思います。

炎上しそうな予感がいっぱい!

ビジネスモデル変更に伴うリニューアルって聞いただけで炎上の予感で胸が膨らみますね。 ビジネスモデルの変更だけでも大きな話なのに、システムの全面刷新まで同時に実施したので、プロジェクトとして不確定要素が多く、管理が難しいものになっていました。

20名弱(エンジニアが8名、他には営業チーム、ユーザサポートチームなどがありました)で8ヶ月程度のプロジェクトでした。一般的には非常に大規模というわけではないのですが、システムの全面刷新でしたので開発量が膨大で、クックパッド内の普段の開発規模からすると非常に大きなプロジェクトです。さらにはこの規模の管理を経験したメンバーも少なく、私がJOINした段階ではどのようにプロジェクトを管理するかもあまり明確に決まっていませんでした。

つまりは、大い燃え上がることが当初から想定できていたわけです。

じゃあガチガチのプロジェクト管理をやるのも以下の理由から難しいと考えました。

  • 開発量が多く、不確定要素が多い
  • 期間が短い
  • そんなに管理工数を割けない(PMの私もバリバリに実装していました…)
  • 文化に合わない

多くの場合、不確定要素が多いとガチガチのプロジェクト管理に走ってしまうのですが、管理のための管理が発生しやすく、結果的にプロジェクトの進行が遅くなると思っています。また、社内の普段の開発方法から大きく変わると、文化的な摩擦や、コミュニケーションミスが起きてしまうリスクも高いなと考えました。

そこで変更が多発すること前提とした「やわらかいプロジェクト管理」を実施することにしました。

やわらかいプロジェクト管理

ここでいう「やわらかいプロジェクト管理」とは、

  • 開発対象をやや大きな粒度でタスク化し、各タスク間にあえて「遊び」を持たせることで、調整や変更を行いやすくする
  • エンジニアの生産性を低下する管理を排除する

を実施することです。

厳密な計画を立ててその通りにやることを目標にするのではなく、変更が多発することを前提として、状況に応じて臨機応変に変更できるように管理することを目標としました。 こういうと「あーアジャイル開発ね」っと思われるかもしれませんが、ここで言っているのは開発方法論のような大きな話ではなく、それよりはもう少し細かく、タスクの管理のテクニックなどの話になります。

具体的には以下のことを実施しました。

  • プロジェクト全体は月単位のタイムボックス管理
  • 進捗管理は週単位のタイムボックス
  • 開発メンバーとの進捗確認MTGを実施しない
  • 心理的安全性を保証する

プロジェクト全体は月単位のタイムボックス管理

細かいスケジュールは立てませんでしたが、全体の流れとチームやタスクの依存関係を把握するために月単位のタイムボックス管理を行いました。

プロジェクト全体を1ヶ月程度のタイムボックスに分割して、各タイムボックスに大きな粒度のタスクのみを配置します。 そして、そのタスク間の順序や依存関係を整理して、どのタイムボックス中に何ができるかを把握しました。

各タイムボックスに作業の目安となるような目的を決めていたので(e.g. 「ビジネスモデルの詳細をつめる」「管理画面の主要な機能を開発する」等)いわゆるフェーズ管理とほぼ同じものになっていたと思います。ただ、フェーズ管理のように「今が何フェーズか」というのはあまり重要ではなく、各タイムボックスに積み残しがあっても次のタイムボックスに進むようにしました。

ちなみにここでは粒度の細かいタスクを配置しないように注意しました。このレベルで細かいタスク管理などをやってしまうと、管理工数が跳ね上がってしまいますし、柔軟な変更が難しくなってしまうからです。

進捗管理は週単位のタイムボックス

タスクの具体的な進捗管理は週単位のタイムボックスで管理を行いました。 月単位のタイムボックスに配置されたタスクを少しだけ分割して、週のタイムボックスに配置し、それらの進捗管理を週次で行うようにしました。

ちなみに、日次での進捗は追わないようにしました。よくある「タスクAは、X月Y日に終了します」というのを避けて、「今週中にAとBが終わればいい」という感じです。

1週間の中でどのように時間配分するかや、どのような開発順序にするかは、各エンジニアに任せるようにしました。 どのように働くのが効率がいいのか、いつが集中できるのかなどはエンジニアによって大きく異なるので、なるべくその人が良いと思うやり方を実施しました。

また、タスク自体もあまり細かい分割は避けました。タスクを細かくして管理してしまうと、管理のための管理が発生しやすいと考えたからです。 といっても粒度が大きすぎても管理できないので、大体1週間のタイムボックスに収まるように分割しました。

開発メンバーとの進捗確認MTGを実施しない

私はPMと開発チームのリーダーを兼務していましたが、開発メンバーとは進捗MTGを実施しませんでした(リーダー間の進捗MTGは週次で実施していました)。 進捗確認MTGは、どうしても余計な作業が発生してしまうので(進捗遅れの言い訳作りや、進捗を示すためのデータ集めなど)、そんなムダなことはしたくなかったですし、 進捗を報告させるのは一定のストレスを与えてしまうので、そういう余計なストレスも与えたくなかったためです。

そもそも開発の進捗は、GithubのPullRequestやIssueを見れば大体は把握できますし、細かい部分は現場でのメンバーの様子を見ていれば把握できるので、廃止しました。

心理的安全性を保証する

チームの心理的安全性が低いとどうしてもストレスから生産性が落ちてしまいます。また、タスクの柔軟な変更や、調整を行うには心理的安全性が高くないと、防衛的になってしまって、上手く調整できなくなってしまいます。そこで、心理的安全性を保証するように色々と配慮しました。

例えば、進捗の遅れを追求しないようにしていました。 進捗が遅れている人も、サボって遅れているわけではなくその人なりに最大限努力しているので、無理に急かしたところでメリットはありません。

何か問題があって遅れているのか、見積もりが間違っていたかだけなので、問題を取り除くか、スコープを調整する必要があります。いずれにしろ問題はメンバーではないということを意識して伝えました。

また、どんなに進捗が遅れていても、メンバーのプライベートを優先することを徹底しました。

どんなに忙しくてもライブやサッカーの観戦などの邪魔は絶対にしないようにしましたし、何時に来て何時に帰ろうが自由だという空気を作るようにしました。そもそもフルフレックスなので、当然の権利なわけですが、例えばお昼から出社する場合、みんなどうしても「すいません、お昼から出社します」というように謝ってしまいます。このような発言がある場合「謝る必要ないですし、そもそも報告も不要ですよ」と伝えるようにしました。

やわらかいプロジェクト管理のデメリット

上で述べたように、やわらかいプロジェクト管理では変更を吸収しながらプロジェクトを進めることがやりやすくなります。とはいえ、プロジェクト管理に銀の弾丸はなく、当然デメリットも存在します。

まず、リーダー(あるいはPM)の負荷が非常に高いです。言ってしまえば、メンバーに対してタスク管理をあえて甘くしているため、具体的な進捗や、リスクなどの把握をリーダーが一挙に把握し切る必要があります。 つまりは、リーダーが全体を常にオーガナイズして、各メンバーの生産性を高める手法だとも言えます。

今回の場合でいうと、私が開発の全てをコントロールしていました。いわゆる、トラックナンバー1の状態で、非常に危うい状態だっと思います。

また、心理的安全性を高めるための様々な配慮も必要になります。プロジェクトが佳境に入っても、リーダーは常にポジティブな空気を作る必要があり、一般的には簡単なことではないと思います。

このため、プロジェクトの規模がある程度大きくなってしまうと、この手法は適用しづらいと思われます。

さらには、ある程度メンバーを信用して管理を行うため、自走できるメンバーがいない場合も上手く回らない可能性があります。

終わりに

クックパッド料理教室のリニューアルプロジェクトで用いた「やわらかいプロジェクト管理」についてご紹介しました。何かの参考になれば幸いです。

MySQL with InnoDB のインデックスの基礎知識とありがちな間違い

$
0
0

こんにちは、サービス開発部の荒引 (@a_bicky) です。

突然ですが、RDBMS の既存のテーブルを見てみたら「何でこんなにインデックスだらけなの?」みたいな経験はありませんか?不要なインデックスは容量を圧迫したり、挿入が遅くなったりと良いことがありません。

そんなわけで、今回はレコードを検索するために必要なインデックスの基礎知識と、よく見かける不適切なインデックスについて解説します。クックパッドでは Rails のデータベースとして主に MySQL 5.6、MySQL のストレージエンジンとして主に InnoDB を使っているので、MySQL 5.6 の InnoDB について解説します。

InnoDB のインデックスに関する基礎知識

インデックスの構造 (B+ 木)

InnoDB では B+ 木が使われています。B+ 木は次のような特徴を持った木構造です。

  • 次数を b とすると、各内部ノード(葉ノード以外のノード)は最大 b - 1 個のキーと最大 b 個の子ノードを持つ*1
  • 内部ノードは値を持たない
  • 葉ノードの各キーは値(または値へのポインタ)を持つ
  • 葉ノードは次の葉ノードへのポインタを持つ
  • O(\log_bn)で検索できる

次の図は次数 3 の例です。

f:id:a_bicky:20170417093420p:plain:w552

例えば、key が 4 の value を取り出すには次のように木を辿れば良いです。

f:id:a_bicky:20170417093427p:plain:w440

key が 2 〜 9 の value を全て取り出すには次のように木を辿ることができます。葉ノード間が繋がっていることによって範囲検索を効率的に行うことができます。

f:id:a_bicky:20170417093430p:plain:w440

InnoDB の B+ 木では、インデックスに使われているカラムの値と主キーの値が value に格納されています。

ここで (c1, c2)のような複合インデックスを考えてみます。イメージとしては次のような構造になります。例えば、key の上位 4 bytes を c1に割り当て、下位 4 bytes を c2に割り当てるイメージです。

f:id:a_bicky:20170418083840p:plain:w440

図からわかるように、c2 = 2に対応するインデックスレコードを探そうとしても、c1, c2の順番で並んでいるので、木を辿ることで探すことはできません。c1 = 4 AND c2 = 2のように c1の条件が指定されることで木を辿ることができるようになります。

また、c1 >= 2 AND c2 <= 4のような条件の場合、c1 >= 2の条件を満たすインデックスレコードは木を辿ることで探せますが、それらのインデックスレコードを更に c2 <= 4の条件で絞り込むために木を利用することはできません。具体的には、図の例だと [7,5]が条件を満たさないインデックスレコードですが、その隣の [9,3]が条件を満たすかどうかはわからないですよね。結局のところ、c1 >= 2のインデックスレコード全てを走査しないといけません。

よって、インデックスレコードの走査の数を減らす観点では c1 = 2 AND c2 <= 4のように c1の条件が等価比較になる場合に限って、複合インデックスとしての意味を持つことになります。*2

このことについては MySQL のドキュメントの 8.2.1.2 Range Optimizationにも次のように言及されています。

The optimizer attempts to use additional key parts to determine the interval as long as the comparison operator is =, <=>, or IS NULL. If the operator is >, <, >=, <=, !=, <>, BETWEEN, or LIKE, the optimizer uses it but considers no more key parts.

InnoDB における B+ 木の実装について知りたい方は次の記事と言及されている関連記事を読むとかなり理解が深まると思います。

B+Tree index structures in InnoDB – Jeremy Cole

クラスタ化インデックスとセカンダリインデックス

InnoDB の主キーはクラスタ化インデックス (clustered indexes) になっており、B+ 木の葉ノードにレコードの全データが格納されています。 特定のカラムに張るインデックスはセカンダリインデックスと呼ばれ、セカンダリインデックスの葉ノードには主キーが必ず含まれています。

セカンダリインデックスを使った検索は、まずセカンダリインデックスの B+ 木を辿って主キーを取得し、次にクラスタ化インデックスの B+ 木を辿ってレコードを取得することになります。

f:id:a_bicky:20170417093437p:plain:w440

セカンダリインデックスに必要な情報が全て格納されていれば、クラスタ化インデックスを辿る手順をスキップすることができるので高速です。このようにレコードを取得する際にセカンダリインデックスで完結する場合のことをカバリングインデックスと呼びます。

MySQL がレコードを取得する大まかな手順

MySQL がレコードを取得する際の主要な登場人物として、executor*3と storage engine (e.g. InnoDB) がいます。

storage engine が InnoDB の場合は次のようにレコードを取得します。

  1. executor が storage engine にレコードを要求する
  2. storage engine (InnoDB) はセカンダリインデックスの木を辿ることで、取得すべきレコードのインデックスに含まれているカラム値と主キーの値を得る
    • インデックスが使えない場合はクラスタ化インデックスに含まれている全レコード情報を executor に返す
  3. storage engine は 2 で得たデータのうち、インデックスに含まれているカラム値の情報を使って取得すべきレコードをフィルタリングする (Using index condition)
    • インデックスに含まれている情報が使えない場合はスキップ
  4. storage engine は 3 で得たデータの主キー情報を使ってクラスタ化インデックスからレコードを取得する
    • SELECT で指定されているカラムや WHERE で指定されているカラムの情報が全てインデックスに含まれている場合はスキップ (Using index)
  5. storage engine は取得したレコード情報を executor に返す
  6. executor は storage engine がフィルタリングできなかったレコードをフィルタリングする (Using where)
    • storage engine 側で全てフィルタリングされている場合はスキップ

「雑なMySQLパフォーマンスチューニング」はこの辺の内容をわかりやすく説明しているので、ピンとこなければ読むことをお勧めします。

以上の説明で、Explain で extra に Using index, Using index condition, Using where が出る場合にどのような処理が行われているかイメージが付くと思います。 Using index condition が出る場合は ICP (Index Condition Pushdown) 最適化と呼ばれ、MySQL 5.6 から導入されました。これによって、クラスタ化インデックスから取得するレコードが減るのでその分高速になります。c1 >= 2 AND c2 <= 4のような条件のために (c1, c2)の複合インデックスを張っても意味がないと前述しましたが、ICP の恩恵を受けてパフォーマンスが改善する場合もあります。

インデックスを張る上でのポイント

インデックスを張る上では次のような内容がポイントになると思います。

  • インデックスで絞り込めるレコード数が大きいか?(選択性が高いか?)
    • WHERE c1 = 1 AND c2 = 2 AND c3 = 3 AND c4 = 4みたいな条件があるからといって、(c1, c2, c3, c4)の複合インデックスが必要とは限らない
    • c1で十分絞りこめるのに他のカラムもインデックスに含めるとインデックスが肥大化する
  • 複合インデックスを張る場合
    • 絞り込めるレコード数の大きいカラムを先にしているか?
    • 等価比較するカラムが先になっているか?
  • カバリングインデックスの恩恵を十分に受けられるか?
  • ICP の恩恵を十分に受けられるか?
  • ソートに使うか?*4

不適切なインデックスの例

これまでに説明した内容がわかっていると、以下に挙げる内容が不適切なインデックスだとわかるはずです。

  • インデックスの最初のカラムに範囲指定をする複合インデックス
  • 選択性の悪いインデックス
  • 他のインデックスで代替できるインデックス

具体例を挙げるために、次のようなテーブルを扱うことにします。商品情報を管理するテーブルで、現在以降に掲載される商品の情報が日々追加されていく想定です。

CREATETABLE `products` (
  `id` int(10) unsigned NOTNULL AUTO_INCREMENT,
  `shop_id` int(10) unsigned NOTNULL,  -- 商品を掲載している店舗の ID
  `name` varchar(255) NOTNULL,         -- 商品名
  `price` int(10) unsigned NOTNULL,    -- 商品の価格
  `starts_at` datetime NOTNULL,        -- 商品の掲載開始日時
  `ends_at` datetime NOTNULL,          -- 商品の掲載終了日時
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

それでは具体例を見ていきます。

インデックスの最初のカラムに範囲指定をする複合インデックス

次のインデックスが定義されているとします。

ALTERTABLE products ADDINDEX ix_ends_at_starts_at (ends_at, starts_at);

これは、次のようなクエリに対処するために定義されたインデックスですが、ends_at単一のインデックスを張るのとあまり変わりません。*5

-- 現在掲載されている商品を抽出するSELECT * FROM products WHERE starts_at <= NOW() AND ends_at >= NOW();

この理由は次のような B+ 木をイメージするとわかりやすいです。この木は (c1, c2)に対して構築された B+ 木ですが、c1 >= 2 AND c2 <= 5のような条件でレコードを引こうと思った場合にインデックスレコードを走査する数が減りません。例えば、[2,8]のインデックスレコードが c2の条件を満たしませんが、その隣の [3,1]c2の条件を満たすかどうかは木の構造から判断できず、c1 >= 2のインデックスレコードを全て走査することになります。

f:id:a_bicky:20170418084900p:plain:w440

MySQL 5.6 では前述した ICP 最適化という仕組みがあるので、starts_at >= NOW()での絞り込みで劇的にレコードが減るようであれば、ICP の恩恵を受けられるかもしれません。

ICP の効果は session status の Handler_read_next の値がどれだけ変わるかを見てみるのが良いでしょう。この値が少ないほどインデックスレコードの走査が少ないことを意味します。

-- ICP 有効
FLUSH STATUS;
SET @@optimizer_switch = "index_condition_pushdown=on";
SELECT * FROM products WHERE starts_at <= NOW() AND ends_at >= NOW();
SHOW SESSION STATUS LIKE'Handler%';

-- ICP 無効
FLUSH STATUS;
SET @@optimizer_switch = "index_condition_pushdown=off";
SELECT * FROM products WHERE starts_at <= NOW() AND ends_at >= NOW();
SHOW SESSION STATUS LIKE'Handler%';

ICP の効果が低いと判断したら、ix_ends_at_starts_atは削除して単一のインデックスを張りましょう。

ALTERTABLE products DROPINDEX ix_ends_at_starts_at,
  ADDINDEX ix_ends_at (ends_at);

似たような例として、次のようなインデックスを考えます。

ALTERTABLE products ADDINDEX ix_ends_at_shop_id (ends_at, shop_id);

これは、次のようなクエリに対処するために定義されたインデックスですが、同様に ends_at単一のインデックスを張るのとあまり変わりません。

-- shop_id = 1234 の店舗の現在掲載される商品を抽出するSELECT * FROM products WHERE shop_id = 1234AND starts_at <= NOW() AND ends_at >= NOW();

shop_idは等価比較で使う前提なので、(shop_id, ends_at)の順番でインデックスを張ることで複合インデックスとしての恩恵が得られます。

ALTERTABLE products DROPINDEX ix_ends_at_shop_id,
  ADDINDEX ix_shop_id_ends_at (shop_id, ends_at);

選択性の悪いインデックス

次のインデックスが定義されているとします。

ALTERTABLE products ADDINDEX ix_shop_id_starts_at (shop_id, starts_at);

これは、次のようなクエリに対処するために定義されたインデックスですが、選択性の観点で良くありません。

-- shop_id = 1234 の店舗の現在掲載される商品を抽出するSELECT * FROM products WHERE shop_id = 1234AND starts_at <= NOW() AND ends_at >= NOW();

ix_shop_id_starts_atの選択性が良いかどうかはテーブルとクエリの特性次第ですが、productsテーブルが現在以降に掲載される商品しか追加されず、過去に掲載されていた商品が残り続けるのであれば、starts_at <= NOW()という条件は該当レコードが日々増えていきます。一方で、ends_at >= NOW()は商品の増え方が一定であれば該当レコードの量は一定とみなせます。

よって、(shop_id, ends_at)にインデックスを張るべきです。

ALTERTABLE products DROPINDEX ix_shop_id_starts_at,
  ADDINDEX ix_shop_id_ends_at (shop_id, ends_at);

他のインデックスで代替できるインデックス

次のインデックスが定義されているとします。

ALTERTABLE products ADDINDEX ix_shop_id (shop_id),
  ADDINDEX ix_shop_id_ends_at (shop_id, ends_at);

これは次のような 2 種類のクエリに対処するために定義されたインデックスですが、ix_shop_idは冗長です。

-- shop_id = 1234 の店舗に掲載されたことがある、または掲載される予定の商品を全て抽出するSELECT * FROM products WHERE shop_id = 1234;
-- shop_id = 1234 の店舗の現在掲載される商品を抽出するSELECT * FROM products WHERE shop_id = 1234AND starts_at <= NOW() AND ends_at >= NOW();

shop_idでの絞り込みに特化したインデックスとして ix_shop_idを導入したと思われますが、次の 2 つの木を見れば ix_shop_id_ends_atix_shop_idの役割を包含していることは一目瞭然です。

f:id:a_bicky:20170417093440p:plain:w440

よって、ix_shop_idは削除すべきです。

ALTERTABLE products DROPINDEX ix_shop_id;

最後に

以上、MySQL (InnoDB) のインデックスについて簡単に解説しました。InnoDB について完璧に理解しようと思うと膨大な知識が必要ですが、よくある単純な用途でインデックスを張るだけであれば、必要とされる知識はほんの少しであることがわかると思います。 本エントリーによって、世の中から不適切なインデックスだらけのテーブルが少しでもなくなれば幸いです。

*1:次数の解釈は文献によって異なるので、2017 年 4 月 17 日時点の Wikipediaに合わせています

*2:後述する ICP が効果を発揮する場合はその限りではありません

*3:executor を mysql server と表現している記事を見かけることがありますが、sql_executor.cc に実装されているので executor という表現の方が適切だと思います

*4:本エントリではソートには触れないので、興味のある方は「漢(オトコ)のコンピュータ道: Using filesort」を参照すると良いと思います

*5:Explain の key_len 的には starts_at も使われるように見えるので、ソースコードを読んでその理由を調べようと前々から思ってますが、未だに調査できてません…

Web サービスの完全 HTTPS 化

$
0
0

インフラストラクチャー部長の星 (@kani_b) です。

2017年1月5日をもって、クックパッドにおける全ページで HTTPS が使われるようになりました。 完全 HTTPS 化をするにあたり、その理由や具体的な進め方について紹介します。 以前 SRE Tech Talks #2にて一部発表した内容も含みますので、ご興味のある方はあわせてスライドもご覧ください。

完全 HTTPS 化に踏み切った理由

以前のクックパッドは、ログインや登録情報の参照など、いわゆる個人情報や認証情報を扱う箇所のみに HTTPS が使われていました。 このように「必要な箇所にのみ HTTPS を使う」構成は、ある程度歴史のある Web サービスにおいてよく使われている構成です。 この状態から、完全 HTTPS 化に踏み切った理由を説明します。

サービスをよりセキュアにするため

HTTPS の利用を考えるにあたりまず思い浮かぶ利点は、「通信を暗号化できる」そして「通信先を認証できる」ことでしょう。前述の通り、これまでは機密性の高い情報を扱う箇所のみで HTTPS を利用してきました。これだけでも、守りたい情報が簡単に窃取できてしまう状態は避けることができます。しかし、現代は公衆無線 LAN などオープンなネットワークがかなり普及しており、また国家レベルでの盗聴なども明らかになってきています。

「レシピサイトくらいで大げさな」と思われるかもしれませんが、食事は人間の生活と密接に紐付いており、思わぬ情報を得られる場合があります。例えばクックパッドはレシピの検索機能を提供しています。この機能にはこれまで HTTP が使われてきました。しかし、実際の検索動向を見てみると、例えば「ダイエット」であったり、特定の病気 (糖尿病など) に適したレシピなど、プライバシに大きく関わるキーワードが含まれることがあります。

こう見ると、「では検索は HTTPS にしたほうが良い」という気持ちが働きます。しかし、検索だけではなく、他の機能についても同様のことが考えられます。「その機能が実際どのように使われるのか」を完全に想定することは困難です。よって、全ての通信が暗号化されている状態をまず前提とすることにしました。もちろん、HTTPS の上でやり取りされる情報がどのように扱われるか、はまた別の話であり、アプリケーションレイヤにおいてどのように情報を扱うかは今後も考慮していく必要があります。

プラットフォームの進化

iOS の App Transport Security (ATS) 対応必須化などをはじめ、プラットフォーム側で通信を HTTPS にする流れも進んでいます。現在完全 HTTPS 化が進められている主な要因はここにあるのではないかと思います。Chrome においても近年は HTTPS 絡みの変更が盛んです。Chrome 56 でリリースされた、 HTTP ページにログインフォームが表示されている場合に “安全でないページ” という警告を出す機能は、多くの HTTP ページでログイン用のモーダルを表示していたクックパッドにも影響を及ぼす変更でした。

また、検索エンジン側の変更も要因の一つです。Google は検索ランキングにおいて HTTPS の利用有無をランキングアルゴリズムに利用することを発表しています。また多くの検索エンジンが検索画面そのものを HTTPS 化しており、 HTTPS 化なしに自社サービスへの流入などを正確に計測することは難しくなります。こうした外部のプラットフォームが HTTPS 化へと舵を切ってきたことも、移行理由の一つになりました。

開発のやりにくさ

完全 HTTPS 化をしていないサービスの多くで、ログインフォームや登録情報の参照など一部の画面のみが HTTPS 化されています。クックパッドでも長い間同様に HTTPS が使われている画面を使い分けていましたが、開発者が「この画面には HTTPS が必要かどうか」を判断して使い分けていたため、本来 HTTPS であるべき画面がそうでない、などの事故が起きうる状況でした。また、HTTPS 画面で提供されているエンドポイントにアクセスするために CORS に対応する必要があるなど、普段の開発にも影響が及んでいました。

新技術への対応

HTTPS は、現在出てきている新しい技術の必須要件とされることも多くなりました。例えば HTTP/2 はその代表格 (正確には HTTP/2 自体が HTTPS を要求しているわけではないが、インターネットサービスにデプロイするためにはほぼ必須) でしょう。他にも、ServiceWorker や Web Push, iOS の Shared Web Credentials などは HTTPS が利用されていることが要件となっています。 こうした新しい技術を活かしていくためにも、完全 HTTPS 化は必要でした。

以上が、クックパッドを完全 HTTPS 化するに至った理由です。Web サービスにおいて、もはや完全 HTTPS 化をしないポジティブな理由はないと考えます。

完全 HTTPS 化までの道のり

完全 HTTPS 化する理由がまとまったところで、次に実際どのように HTTPS 化したか、具体的な進め方について説明します。完全 HTTPS 化は、概ね以下のように進めました。

  1. HTTPS テスト環境を作成する
  2. mixed content をなくす
  3. 段階的リリース
  4. 全体リリース

完全 HTTPS 化は社内のアプリケーション、検索エンジンはもちろんのこと、提携他社からのアクセス、メディア媒体掲載時に利用される URL などあらゆる範囲に影響するため、社内での宣言は早めに行いました。結果として、多くの部署の協力を得ながら完全 HTTPS 化を進めることができました。

HTTPS テスト環境の作成

まずは HTTPS になった際のアプリケーションをテストできる環境をつくる必要があります。 クックパッドでは、Rails の HTTPS 必須化スイッチである force_ssl を利用し、特別な Cookie をリクエストに差し込んだ場合に force_ssl が有効化された専用のアプリケーションサーバにルーティングされるようにしていました。こちらは、段階的リリースのタイミングでは内部アクセスや Cookie の扱いに不都合があったため、リバースプロキシでリダイレクトする形に変更しています。

mixed content をなくす

mixed content とは、HTTPS の中に HTTP のリソースが含まれることです。多くのブラウザは、mixed content になっているリソースはロードしない、あるいは動作させないような制約を持っているため、完全 HTTPS 化にあたり mixed content をなくすことは必須です。最も大変なのがこの対応だと思います。

アプリケーションや CSS の中に埋まっている HTTP URL を探しだし、HTTPS に修正します。クックパッドにおいても、いくつか HTTP URL が記載されてしまっているケースがありました。 こういった URL は、 protocol-relative URL (//:ではじまる URL) に置き換えたり、アプリケーション側でリクエストプロトコルを見て URL を生成するように修正します。

大きなコードベースにおいて、この作業は根気の要るものです。また、この作業をしている間もサービスは開発され続けているため、終わりがありません。また、mixed content はコードそのもの以外 (ユーザの持つデータなど) に起因することも多いため、実際に本番に出してテストしていくことが大きな助けになります。そのため今回は、いくつかの主要機能を定めておき、それらに mixed content が発生しない段階で次のステップに進むことにしました。

ネットワーク広告の HTTPS 対応

ネットワーク広告は、その仕組み上実際に配信される広告クリエイティブが HTTPS を利用しているかが非常に重要です。クックパッドが完全 HTTPS 化に着手した2015年8月頃は、まだ多くの事業者が HTTPS に対応していない、あるいは対応を保証できない状態でした。つまり、ネットワーク広告を配信すると、mixed content が起きてしまう可能性が高かったのです。

しかし、ATS がリリースされたことによるものか、現在では多くの事業者が HTTPS 対応を進めています。クックパッドでも一部ネットワーク広告が利用されているため、2015年の着手時にはこれが原因となり一度ペンディングとなったものの、流れが変わったことで再び進めることができるようになりました。このあたりの事情は @suzu_v さんのスライドに非常にわかりやすくまとまっています。

完全 HTTPS 化完了後、ネットワーク広告の売上について事業部と確認も行いましたが、特に影響はありませんでした。

段階的リリース

完全 HTTPS 化による影響を確認するため、段階的なリリースを行いました。 リバースプロキシのレイヤで、特定の Cookie を用いて全ユーザのうち数%が完全 HTTPS 化されたアプリケーションサーバにアクセスするようにします。 アプリケーション側のエラーやパフォーマンスをトラックしつつ、ユーザからのご意見やお問い合わせをユーザサポート部門と連携しながら確認します。 結果として、いくつかのリダイレクトミスと不具合を見つけ、修正しました。

CSP Report の活用

今回の移行では利用できなかったのですが、Content Security Policy (CSP) の機能を使うことで、より効率的に mixed content の情報を集めることができます。CSP のディレクティブとして block-all-mixed-contentディレクティブがあり、これを指定するとブラウザは mixed content を一切読み込みません。また、CSP の機能として、ポリシ違反が発生した際に指定した URL にレポートを送出する機能 (report-uri *1 ) があります。 これらを活用することで、ユーザのブラウザで起きた mixed content 情報を収集することが可能です。

そのままユーザにリリースしてしまうと mixed content が存在した場合本当にリソースが読み込まれなくなってしまうため、Content-Security-Policyヘッダでなく Content-Security-Policy-Report-Onlyヘッダを利用します。

Content-Security-Policy-Report-Only: block-all-mixed-content; report-uri https://example.com/csp-report

上記のようなヘッダを送出すると、もし mixed content が発生した場合指定した URL にそのブラウザから JSON レポートが POST されます。内容は以下のようなものです。

{"csp-report": {"blocked-uri": "http://example.com/some_picture.png", 
        "disposition": "report", 
        "document-uri": "https://example.com/mixed_content.html", 
        "effective-directive": "block-all-mixed-content", 
        "line-number": 6, 
        "original-policy": "block-all-mixed-content; report-uri https://example.com/csp-report", 
        "referrer": "", 
        "source-file": "https://example.com/mixed_content.html", 
        "status-code": 0, 
        "violated-directive": "block-all-mixed-content"
    }}

JSON が送られてくるだけですので、受け取るサーバの実装も簡素なことに加え、 Elasticsearch などに投入することで簡単に分析することが可能です。 クックパッドでは、一部のユーザに CSP を送出しています。また、レポートの受信には Amazon API Gateway を使い、受け取った JSON を Amazon Kinesis Firehose に送信して Amazon Elasticsearch Service で分析を行えるようにしています。実装が必要な箇所は API Gateway が JSON を受け取る箇所のみですので、非常に楽です。

全体リリース

段階的リリースにおいて徐々に公開範囲を広げていき、問題がなければ全体リリースへと進みます。 アプリケーションが HTTPS 接続を受け入れられる状態にした上で、リバースプロキシでリダイレクトを行いました。問題が判明した際すぐに切り戻せるよう、以下の点に気をつけていました。

  • HTTP 301 ではなく 307 (Temporary Redirect) の利用
  • Cookie 属性や HSTS など切り戻しのしにくい変更を行わない
  • ユーザサポート部門との協力

リリース後に監視を行いましたが、大きな問題は見当たりませんでした。

切り戻しのしにくい変更

完全 HTTPS 化における “後戻りのしにくい” 変更として、Cookie への secure 属性の付与や HTTP Strict Transport Security (HSTS) があります。

Cookie の secure 属性は、Cookie を HTTPS 環境下でのみ送出する属性であり、設定ミスや攻撃による意図しない HTTP アクセスでの Cookie 漏出を防ぐことができます。 完全 HTTPS 化が完了すればこの属性を付与することは何ら問題ないのですが、万一 HTTP への切り戻しを行った際に、例えば既存のユーザセッションが全て無効 (セッション Cookie が送出されない) になるといった事態を招きます。 そのため、完全 HTTPS 化を行った上で、サービスに影響がないことを確認できたタイミングで付与する必要があります。

HTTP Strict Transport Security (HSTS) は、Web サービス側からブラウザに対し「次回以降このドメインには HTTPS でアクセスしてほしい」旨を伝え記憶してもらう仕組みです。 完全 HTTPS 化により、 HTTP ページへのアクセスを HTTPS ページにリダイレクトします。つまり、リダイレクトされるまでの初回アクセスは HTTP になってしまいます。 HSTS を付与することで、例えばユーザがブラウザにドメインのみを入力してアクセスする場合でも、そのドメインにアクセスしたことががあれば HTTPS を利用するようになります。

この仕組みも、ブラウザの挙動を変更するため導入には注意が必要です。HSTS が設定されたドメインで、切り戻しのために HTTPS から HTTP へのリダイレクトを単に行うとリダイレクトループが発生します。 HSTS は max-age を 0 にした HSTS ヘッダを送出することで無効にすることができますので、secure 属性よりは切り戻しやすいといえます。しかし、HSTS は常に HTTPS で送出される必要がある (無効にしたい場合でも) という点には注意が必要です。 例えばロードバランサの負荷が心配なケースで、一度 HSTS を全体で有効にしてしまうと、切り戻したあとも継続して HTTPS アクセスを (リダイレクトが行われるまで) 受け続ける必要があります。

上記のことを踏まえ、クックパッドではまず HSTS を有効にして意図せず HTTP でアクセスされているページや動かない機能がないことを社内のエンジニアや事業部門に確認してから secure 属性の付与を行いました。 アプリケーション側でヘッダ送出を行うことも可能ですが、設定の見通しをよくするためリバースプロキシを利用しています。

全体リリース後

画像やあまり利用されていない機能などでトラブルが起きることもあるため、ご意見やお問い合わせを見ながら個別に対応を行っています。 また、URL が変更されるため、集計バッチなどの動作にも気を配る必要がありました。

完了後の反響など

上記のような進め方で、無事にクックパッドを完全 HTTPS 化することができました。 移行にあたり、パフォーマンスなども懸念として上がっていましたが、現在特に問題にはなっていません。HTTPS のオーバーヘッドは当然存在しますが、近年では端末のリッチ化やネットワークの高速化、安定化により大きな問題にはなりにくいと考えています。また、HTTP/2 や TLS 1.3 などプロトコルの進化により、よりオーバーヘッドは減らせると考えています。 また、上述の通りネットワーク広告の売上や、検索エンジンの順位などにも影響はみられませんでした。

おわりに

クックパッドを完全 HTTPS 化した背景や具体的な進め方についてご紹介しました。現在は、公開している全てのサービスを HTTPS で提供しています。 完全 HTTPS 化は、エンジニアに限らず様々な人を巻き込む必要があり、場合によっては少し根気も要る作業になりますが、段階的リリースや CSP などを使うことでよりよく進めていくことも可能です。

個人的には、完全 HTTPS 化に限らず、ユーザが安全にインターネットを利用できるようにすることは Web サービス事業者の一つの責務と考えています。完全 HTTPS 化は、その中でも実行のための障壁がなくなりつつある改善の一つです。

ユーザの安全や新技術など、Web が次の段階に進んでいくためにも HTTPS 対応は今後より必須になることでしょう。この記事が、まだ HTTPS 移行が済んでいない方のお役に立てば幸いです。

*1:report-uri は CSP Level 3 において廃止されているため、今後は report-toを使っていく必要があります


最近のサービス間のデータとイベントの連携について

$
0
0

こんにちは。牧本 (@makimoto) です。最近はバックエンドシステムの設計をやったりしています。

今回は複数のサービスが存在するとき、その間でどのようにデータ連携を実現するかついて述べていきます。

背景と問題定義

cookpad.com は世界有数の規模の Ruby on Rails で作られたウェブアプリケーションです。

巨大な Rails アプリケーションは単純に起動やデプロイ、テストが遅いという問題以上に、自分の変更が与える影響範囲を予測するのが困難という大きな問題があります。cookpad のメインレポジトリ (cookpad_all と呼ばれる) には1つの mountable engine を共有する Rails アプリケーションは7つがあり、困難さに拍車をかけています。cookpad_all を触る開発者は新しい機能を追加する、既存の機能に手を入れる、不用な機能を消すなど様々な場面でこの困難さと対峙することになります。

そのため、われわれは既存の機能を切り出したり、新機能を新しいアプリケーションとして実装するという取り組みを行なっています。その際問題となることの一つに、複数のサービス間から必要とされるデータをどのように共有したり、またそれらデータの変更などをどのように伝播させるかというものがあります。

1つのデータストレージがモデルロジックの異なる複数のサービスで共有されるのはデータの不備や不整合の温床となるので原則として行ないません。*1そのため、一方のサービスのデータを他方で使いたい場合は、直接データストレージにアクセスするのではなく、データを管理するサービスを通して取得するというのが基本となります。

そのような複数のサービスの間でデータを連携したいという状況では、一方のサービスで発生した変更を受けて他方のサービスで処理をする、といった場面が増えてきます。たとえば、「あるユーザーが退会したことにより、他のサービスに存在するそのユーザーに関連するアイテムを削除してアクセスできなくする」といった場合です。

これらの課題を現実的なコストで解決することが必要となってきます。

アプローチ

Garage と GarageClient の導入による RESTful Hypermedia API の社内標準化

まず、データ連携の第一歩としてウェブ API によるデータの作成・閲覧・更新・削除についてです。

クックパッドではウェブ API の開発・利用には Garageとそのクライアントである GarageClientを用いています。 Garage は RESTful Hypermedia API を Ruby on Rails 上で開発するためのライブラリであり、クックパッドで4年近くの開発・利用されています。Garage 自体については過去の紹介記事を参照いただくことにして詳細は割愛します。

共通の API 実装のためのライブラリを用いることで、各サービス間のインターフェースの差異をなくし、NantokaClient を乱立させることなくスムーズにサービスが管理しているデータにアクセスできるようにしています。また、Garage は認証認可の機能も提供しているので、共通基盤である認証システムと連携させることで適切なアクセス制御も実現できるようになっています。

イベントにもとづくデータの連携

前節では各サービスの管理しているデータにアクセスする方法を説明しました。 次は前述した「あるユーザーが退会したことにより、他のサービスに存在するそのユーザーに関連するアイテムを削除してアクセスできなくする」というケースについて考えていきます。

シンプルな方法

複数のサービスをまたいだデータの連携を実現するためのもっとも単純な方法は、データの変更があったタイミングで、そのデータに依存しているサービスに対し処理を促すリクエストを行なう方法です。この方法は、2つのサービス間でしか連携がないことがわかっている場合はうまく動きます。しかし、サービスが増えてくると複雑さが増していきます。また、この方法では、データの提供元と提供先の両方の実装に手を入れる必要があるため開発自体も煩雑になります。

Amazon SNS を用いた pub-sub メッセージング

そこでクックパッドでは、 Amazon Web Services の提供する Simple Notification Service (SNS) を用いた pub-sub メッセージングを採用しています。

これは以下の流れで実現します:

  1. あるイベント (たとえば、ユーザーの削除など) が発生するとそれに対応する SNS トピックに通知を行なう *2
  2. SNS はトピックを購読している HTTPS エンドポイントに対しイベントが発生した旨を通知する
  3. 通知を受けた HTTPS エンドポイントのサービスはその通知内容に応じて処理を行なう

なお、 SNS のメッセージには 256 KB のサイズ制限があるので、SNS のメッセージとしてはデータの ID のみを渡して、データ本体は Garage 経由で取得するという方式がよく取られます。

これらは Ping という名前の社内ライブラリによって実現されており、各サービスで簡単に導入できるようになっています。

この方法の問題点

しかしながら、この方法はいくつかの問題があります:

  • 本来内部通信しかないサービスであったとしても HTTPS エンドポイントを SNS の通知を受け取るためにインターネットに公開する必要がある
  • 通知を HTTP リクエストとして受けるので、リクエスト数が増えたらアプリケーションサーバ (Unicorn であるケースが多い) が詰まるリスクがある

Barbeque と Amazon SQS を用いたファンアウト

これを解決するため、通知の広報先を HTTPS エンドポイントから AWS の Simple Queue Service (SQS) のキューに変更し、そのキューをポーリングしてジョブを実行するという方法に転換しようとしています。 (いわゆるファンアウト)

この仕組みは、クックパッドのジョブキューシステムである Barbequeの機能として実装しています。 Barbeque は SQS をキューとして使っているため、実装的には、キューとジョブと SNS トピックを関連付けてキューでトピックを購読する機能を追加するだけでこの仕組みは実現可能でした。 (https://github.com/cookpad/barbeque/pull/20)

この方法での流れは以下の通りです:

  1. あるサービスでイベント (たとえば、ユーザーが退会する、など) が発生すると、サービスは SNS トピックに通知を行なう
  2. SNS はトピックを購読している SQS のキューに対しイベントが発生した旨を通知する
  3. Barbeque のワーカーが対象のキューをポーリングし、通知を受けとる
  4. Barbeque のワーカーが通知をもとに関連付けられたジョブを実行する

Barbeque は Docker の利用を前提としたジョブキューシステムなので、ジョブの実行は Docker コンテナ内で行なわれます。すでに稼動しているサービスとは別のコンテナでジョブが実行されるため、ジョブが増えたことによりリクエストが増えて Unicorn のワーカーが詰まる問題を避け、よりスケーラブルなジョブ実行環境となります。クックパッドでは、現状ほとんどすべての新規サービスが Docker コンテナ上で動いているため、この仕組みの恩恵を十分に受けることができます。

まとめ

本稿では、クックパッドでどのように複数のサービス間でのデータ連携を行なっているかについて述べました。 Docker、AWS のメッセージング系のサービス、内製のソフトウェアを組み合わせて複数のサービスで協調してデータを扱うことを実現しています。

サービスを分割するときの心配事の1つとして、既存サービスのデータとうまく連動させるのが難しいというのがありますが、今回紹介した方法である程度は解決できると考えています。

*1:ただし、既存サービスから機能を徐々に切り出す場合などのケースで例外はあります。

*2:実際の運用では SNS にリクエストを送るために fluentd を経由させています。

クックパッド サマーインターンシップ 2017 を開催します!

$
0
0

f:id:takai_naoto:20170512093753j:plain

いつもお世話になっております。エンジニア統括マネージャーの高井です。

クックパッドでは、毎年恒例になっているサマーインターンシップを今年も開催いたします! 今年のインターンシップは、昨年よりもパワーアップして、エンジニアやデザイナー志望のみなさんに向けた三つのコースと総合職を志望する方に向けた一つのコースを用意しています。

14day海外技術インターンシップ

f:id:takai_naoto:20170512093807j:plain

今年からの新しいチャレンジがこちらです。クックパッドのグローバル展開の中心であるイギリスのオフィスで働くことを通じて「クックパッドに入社して海外で活躍するってどんな働き方になるんだろう」というのを体験していただけるインターンシップです。

14day海外技術インターンシップでは、日本でクックパッドのサービス開発の考え方を学び、その後にイギリスへ渡航して実践的な開発に取り組んでいただきます。グローバルを本格化したクックパッドのミッションとバリューズを理解し、体験し、実践することができるインターンシップになっています。

ぜひとも、技術力と英語力に自信のある方の参加をお待ちしております!

17day技術インターンシップ

f:id:takai_naoto:20170512093811j:plain

クックパッドの技術を知るために最適なインターンシップがこちらです。17day技術インターンシップは、大きく前半と後半とに分かれています。

前半では、講義形式でクックパッドで使われている技術を学びます。今年は、「サービス開発」「Rails・TDD・Git」「モバイルアプリケーション(iOS/Android選択式)」「インフラストラクチャー」「SQL」「機械学習」「Ruby」と七つの講義を準備しています。去年と比べると新しく「インフラストラクチャー」「SQL」「Ruby」は新しく追加された講義です。毎年、ソフトウェアエンジニア界隈からも「学生向けでなければ自分が参加したい」と声があがるほどの豪華な講義です。今年はRubyのコア開発者でもある笹田による「Ruby」の講義もあります。

後半は、メンターとなるエンジニアと一緒にクックパッドの実環境で開発をしていただきます。社内のエンジニアと一緒になって、クックパッドの開発がどうやって進められているのか、どのような開発基盤が整備されているのかなど、クックパッドでの働き方を知るための、またとない機会となっています。

昨年の資料は下記になりますので、ぜひとも参考になさってください。

5dayサービス開発インターンシップ

f:id:takai_naoto:20170512093800j:plain

クックパッドのサービス開発の真髄に触れることができるのが、こちらのインターンシップです。

このインターンシップでは、エンジニアとデザイナーとでチームを組んで、サービス開発に取り組んでいただきます。ユーザーの課題についてひたすら考え抜き、悩み、それをどうやって解決するのか、そのためにはどんなサービスを作ればいいのかを考えぬくインターンシップです。

インターン中は、クックパッドの一線で活躍するエンジニアやデザイナーから常にフィードバックがされますので、それを通じて大きく成長できるチャンスです。将来は自分でサービスを立ち上げたいぞ、というエンジニアやデザイナーの方におすすめです。

14day 海外事業開発インターンシップ

f:id:takai_naoto:20170512105036j:plain

海外での事業開発にチャレンジしたいという方に向けたインターンシップがこちらです。総合職に向けたプログラムになっています。クックパッドの海外拠点へ赴き現地にて企画の実行まで体験してもらうという内容です。

海外ビジネスに携わり世界を舞台に活躍したい方や、世の中に大きなインパクトを与える事業を創り上げたい方、自分で考え、行動し、達成することに価値をおく環境で働きたい方の応募をお待ちしております。

どうぞ、お友達に紹介してくださいね。


毎年恒例となりつつありますが、毎回参加してくださる学生のみなさんのために会社を挙げて全力で取り組んでいます。 学生時代にしか経験できない貴重な機会になるとおもいます。学生のみなさんの参加をお待ちしております!

Sisimaiを使ったバウンスメールの管理

$
0
0

最近、Ninja650に乗り換えたSREチームの菅原です。今までマルチばかり乗ってきたんですが、ツインもなかなか面白いですね。シフトペダルをガチャコンいわせながら方々に出かける毎日です。

この記事では、サービスが配信しようとして何らかの理由で差し戻されたメール—バウンスメールの管理をどのように行っているかという話しを書きます。

バウンスメール

サービスがユーザに向けてメールを配信しようとすると、多かれ少なかれバウンスメールは発生します。メールアドレスが間違っている・携帯電話の設定で受信を拒否している・メールボックスが一杯にになっている・IPアドレスがブラックリストに載ってしまったためサーバにメールの受信を拒否されている…etc。完全になくすことは難しいですが、バウンスメールを放置するとメールの到達率を下げたり、送信先から一時的にメールの受信を拒否されたりすることがあるため、差し戻されてしまった宛先はリストに登録して、再送を抑止することが望ましいです。

SendGridのようなサービスを利用している場合、差し戻されたメールは自動的にリストに登録されて再度メールを送っても配送されないようになっていたりするのですが、クックパッドの場合は内部システムのPostfixサーバからメールを配信していたため、バウンスメールの管理をある程度、自前で作り込む必要がありました。

既存のシステム

以前はbounceHammerというOSSとMySQLのバウンスメール管理用のテーブルを使って、バウンスメールの管理が行われていました。

f:id:winebarrel:20170502181740p:plain

  1. アプリケーションはメール送信サーバのPostfixを経由して、メールを配信します
  2. 差し戻されたメールは、バウンスメール管理サーバのPostfixが受け取ります
  3. Postfixが受け取ったメールをbounceHammerが解析して自身のデータベースに入れます
  4. 定期実行されるスクリプトがbounceHammerのデータベースからアプリ用のデータベースにバウンスメール情報をマイグレーションします
  5. アプリケーションはユーザテーブルとバウンスリストを結合して、メールが差し戻された宛先にはメールを送らないようにします
  6. バウンスリストの情報は、管理用アプリケーションから削除することができます

既存システムの問題点

このバウンスメール管理システムには、いくつか問題点がありました。

  • bounceHammerの導入が後付けであったため、バウンスメール情報がbounceHammerとアプリ用データベースで二重管理になっていた
  • 同様に後付けが原因で、管理アプリケーションからアプリ用データベースの情報は削除できるが、bounceHammerの情報を削除(ホワイトリスト登録)できないため、手作業で同期を取る必要があった
  • ユーザテーブルとバウンスリストをSQLで結合して配信対象をフィルタリングする方式であったため、スケーラビリティに問題があった
  • バウンスリストがテーブルという単位で管理されているため、アプリケーションの各機能が個別に配信対象のフィルタリングを実装する必要があった
  • SQLの結合という形でフィルタリングを行うと、メールが配信されなかったユーザがそもそも配信の対象にならなかったのか、バウンスリストに登録されていたため配信されなかったのか、区別を行うことが難しかった
  • バウンスメールの情報がきちんと可視化されていなかったため、配信状況の把握に難があった
  • bounceHammerがEOLになった

問題点を抱えた状態での運用がつらく、またbounceHammerがEOLになったこと、さらにシステムのリニューアル作業が進行中だったこともあり、バウンスメール管理システムを新しく作り直すことにしました。そして、そのコアの部分として利用することになったのがSisimaiです。

Sisimai

SisimaiはbounceHammerの後継として開発されているバウンスメール解析ライブラリです。bounceHammerが管理用Webアプリや集計用のスクリプトも含めた複合的なシステムであったのに対して、シンプルなPerl・Rubyのライブラリです。ライブラリの依存関係も少なく、またわかりやすいAPIで、しかも自分が慣れたRubyのライブラリであったため、とても簡単に新しいシステムに組み込むことができました。

以下はSisimaiを使ってバウンスメールの解析を行うサンプルコードです(※公式ドキュメントより引用)

#! /usr/bin/env rubyrequire'sisimai'
v = Sisimai.make('/path/to/mbox') # or Path to Maildirif v.is_a? Array
  v.each do |e|
    puts e.addresser.address    # shironeko@example.org # From
    puts e.recipient.address    # kijitora@example.jp   # To
    puts e.recipient.host       # example.jp
    puts e.deliverystatus       # 5.1.1
    puts e.replycode            # 550
    puts e.reason               # userunknownendelse# There is no bounce message in the mailbox# or Sisimai could not parseend

Sisito

Sisimaiはすばらしいライブラリなのですが、bounceHammerにあったような管理用のWebアプリケーションはなくなってしまいました。エンジニア以外のスタッフが「問い合わせのあったメールアドレスをホワイトリストに登録する」等の作業が発生するため、管理用のWebアプリケーションは必須です。そこで以前の運用経験を踏まえ、バウンスメール情報保存用のデータベースとそれを管理するウェブアプリケーションを作成しました。それがSisitoです。

以下はSisitoの管理画面のスクリーンショットです。

f:id:winebarrel:20170508161014p:plainf:id:winebarrel:20170508161019p:plain

また、バウンスメール管理システム以外からバウンスメールの情報を取得するため、sisito-apiというAPIサーバも作成しました。

新バウンスメール管理システム

SisimaiとSisitoを使った新しいバウンスメール管理システムの構成が以下のようになります。

f:id:winebarrel:20170508153346p:plain

  1. アプリケーションはメール配信サーバのPostfixを経由してメールを配信します。このときPostfixの機能でblacklistに登録されているメールアドレスには配信しないようにします
  2. 差し戻されたメールはメール配信サーバに保存されます
  3. 定期実行されるSisimaiスクリプトがSisitoデータベースにバウンスメール情報をを保存します
  4. 定期実行されるblacklist更新スクリプトが一定の条件に従って(ハードバウンスのみ ・特定の理由のみ、など)blacklistを更新します
  5. Sisitoを使って統計情報の閲覧やホワイトリストへの登録を行います
  6. アプリケーションはsisito-apiを経由してバウンスメールの情報を取得します
  7. メールの送信ステータス・Subject・blacklistによるリジェクト状況などのログはElasticsearchに送信してKibanaで閲覧できるようにします

まとめ

新バウンスメール管理システムの導入により、アプリケーションの各機能で配信制御を行う必要がなくなり、Postfixでフィルタリングを行うことでスケーラビリティの問題も解決することができました。また、Sisitoによる可視化により問題が発生しても(たとえば、特定の理由のバウンスメールが増えているなど)状況をすぐに把握して迅速に対応することができるようになりました。さらにホワイトリストの登録処理がエンジニアを介さずにできるようになったため、業務フローのコストも下げることができました。

差し戻されるメールのエラー内容はサービスによって様々なパターンがあり、人間が解析することはかなりの労力を伴います。SisimaiのようなライブラリがOSSとして公開されていることは大変ありがたいことです。積極的に活用して、フィードバックなどで開発に貢献していきたいと考えています。

バウンスメールの解析で苦労しているかたは、一度Sisimaiを試してみてはどうでしょうか?

Hackarade: MRI Internal Challenge

$
0
0

f:id:koichi-sasada:20170510112338j:plain今年1月に入社した技術部の笹田です。Ruby インタプリタの開発をしています。

少し旧聞になりますが、今年3月の終わりに Hackarade: MRI Internal Challenge という、Ruby インタプリタ(MRI, Matz Ruby Interpreter)をハックするという社内ハッカソン企画を行いましたので、その様子をご紹介します。ハッカソンでは、弊社エンジニアが原則全員参加で Ruby インタプリタをいじりました。今回のハッカソンでは、特別ゲストとして 世界ナンバーワンの Ruby コミッタ(コミット数が世界一)である 中田さん(nobu)に参加していただき、様々な助言をいただきました。

Hackarade って?

f:id:koichi-sasada:20170510112942j:plain

クックパッド社内で、技術力の底上げを目的に、エンジニア全員が1日参加するハッカソンを年に3回程度行っていくことになりました。このハッカソンの名前を、Hack + Parade からとって、 Hackarade と名付けました。

MRI Internal Challenge

f:id:koichi-sasada:20170510112328j:plain

最初の Hackarade の企画は、MRI Internal Challenge と題して、Ruby の中身を触るハッカソンになりました。MRI とは、Matz Ruby Interpreter の略で、いわゆる rubyコマンドです。クックパッドでは Ruby を用いて開発しているので、自分たちが使っているものへの理解を深める狙いもあります。

この企画に決まった直接の理由は、MRIの開発を主務とする私が入社したためです。そして、クックパッドには、私以外にも青木さんや福森さん(sora_h)といった MRI のコミッタがいるので、彼らのサポートを得ることができるというのも理由の一つでした。

開催にあたり、次のような目標をたてました。

  • 参加者全員
    • MRI は普通の人間が開発している、ということを思い出すことができる
    • MRI のビルドを各自行うことができるようになる
    • MRI のチケットの読み方に関する文化を知る
    • MRI のチケットを起こすことができる
    • MRI のソースコードのディレクトリ・ソースコードの構造を知る
  • 希望者
    • MRI の改善チケットを実際に起票する
    • MRI の改善チケットを解決するパッチを作成し提案する
    • MRI の中身に興味をもち、後日 開催する MRI Internal workshop に参加する

最初の「MRI は普通の人間が開発している、ということを思い出すことができる」というのは、実際に MRI をいじることで、Rubyは所与のものであるわけではなく、自分たちで変更可能である、ということを思い出して欲しいという気持ちから最初の目標としました。 MRI はオープンソースソフトウェアですから、改善したければ好きなだけ改善できます。知らないとちょっとハードルが高い気もしますが、わかってしまえば意外と簡単です。このハッカソンでは、そのハードルを下げるお手伝いをしたいと思って臨みました。

そして、ハッカソンで開発した成果は、可能なら MRI 開発コミュニティに提案し、貢献することを目標としました。

一日の流れ

プログラムは次のような流れでした。なお、時間はあまりオンタイムには進みませんでした。

  • 10:00-10:10 オープニング
  • 10:10-10:40 (1) MRI 文化の紹介
  • 10:40-11:20 (2) Ruby のソースコード構造の紹介
  • 11:20-11:30 休憩
  • 11:30-12:30 (3) The Edge of MRI internal
  • 12:30-13:30 お昼休憩
  • 13:30-14:30 (4) ハックのアイデアの紹介
  • 14:30-19:00 (5) 各自チャレンジ
  • 19:00- パーティー & 成果の発表

(1)~(4) までが座学で、(5) が実際に自分の好きなテーマで開発するハッカソンでした。

最後は、打ち上げパーティーであり、弊社キッチンで美味しいご飯を食べながら、参加者それぞれの成果を称え合いました。

座学の紹介

f:id:koichi-sasada:20170510112331j:plain

座学の内容を掻い摘んでご紹介します。座学といいますが、学校の教室のように説明を聞いてもらうよりは、資料を片手に手元のコンピューターをいじりながら、自分のペースで進めてもらうというものでした。弊社には日本語ネイティブでないエンジニアも多数在籍しているため、資料は英語で作成しました(その場での説明は日本語でした)。

時間も短いので、ハッカソンに必要になるビルドの方法や、他の資料の紹介が主になりました。

(1) MRI 文化の紹介

ここでは、開発者がどのような手順で開発をしているのか、意思決定はどのように行われているか、Ruby で用いている Redmineのチケットはどのように書くとよいか、それから、MRIの情報はどのように取得すればよいか、などを紹介しました。

チケットの書き方は、Ruby バグレポートガイドラインの内容を紹介しました。また、開発方法も、開発者の手引きを主に紹介しました。

情報の取得については、青木さんの『Rubyソースコード完全解説』サポートページRubyのしくみ Ruby Under a Microscope の紹介を行いました。その他、まつもとゆきひろさんの Twitter アカウントの紹介や、Ruby 関連イベントやワークショップなどオフラインコミュニケーションを紹介しました。また、社内に 3 人も MRI コミッタがいるので、気兼ねなく聞いてください、という話もしました(例えば、弊社 Slack には #ruby チャンネルがあります)。

細かいルール的な話が多くなりがちでしたが、ここで強調したのは、「ハッキングが全てであり、良い成果を出せば多少の形式は不問とされる」ということでした。たいてい、説得力のある成果があればなんとかなります(回りがサポートします)。

(2) Ruby のソースコード構造の紹介

実際にソースコードをいじるために必要になる、Ruby のソースツリーの大まかな概要をお伝えました。例えば、ここはインタプリタのコア(VMやGCのなど)を記述してあり、ここは ArrayStringといった組み込みクラスの定義である、ここはテストが収まっている、などです。MRI は、自身のソースコードを生成しながらビルドを進めていくので、そのあたりの概略もお伝えしました。

ソースコードを説明するにあたり、ソースコードをチェックアウトしてもらって、実際に makeコマンドを用いてビルドを行ってもらいました。また、C 言語で新しいメソッドを定義するといった、実際に MRI の中身を改造するような演習を行いました。

(3) The Edge of MRI internal

この時間は、私が行ってきた MRI のハックを紹介し、何が楽しくてこのようなことをやっているのか、ということを紹介しました。また、MRI にどのような課題があるか、という紹介をしました。

自分の行った改善が、世界中の利用者の方に影響をあたえるというのはやり甲斐を感じますよね。

(4) ハックのアイデアの紹介

何もない状態で MRI をハックしてください、というのは大変だと思うので、いつかやりたい、と思っていた(比較的軽い)アイデアを提案したり、チケットを紹介しました。

私はあまり残りチケットをまとめていなかったのですが(資料として、未アサインのチケット = 放置チケットへのリンクを提示するのみだった)、青木さんがその場でやりやすそうなチケットをピックアップしてくれました。

ハッカソンの紹介

f:id:koichi-sasada:20170510112744j:plain

ハッカソンは、最終的に40個ほどのアイデアに挑戦し、10個ほどのパッチの投稿という貢献を行いました。いくつかのハックはまだ継続して行われています。

ハックはグループで行っても良いですし、単独で行ってもよい、ということにしました。結果としては、ほとんどの人が単独で挑戦していました。

ここでは、実際にどのようなハックが行われたかをいくつかご紹介します。ここにある以外にも、挙動やテストの修正や、ドキュメントの追加などが行われました。

More flexible GC parameters

環境変数で GC の挙動を変更するためのパラメータを与えられるのですが、実行時に変更することはできません。しかし、原理的には変更可能であるため、変更できるようにする、という話になります。この話の難しいところは、「本当に変更しても大丈夫なの?」というところで、それを確認するのが面倒で手をつけていなかったのですが、今回のハッカソンで提案してもらったところ、興味を持った方に挑戦してもうことができました。

実装して、チケットとして登録するまでやり遂げることができました。 https://bugs.ruby-lang.org/issues/13388

弊社アプリケーションのような、実際のアプリケーションに応用して成果が出るかどうか、確認して取り込みたいと思っています(取り組んでくれた方々に依頼中です)。

再代入不可なローカル変数をつくってみる

Rubyのローカル変数はいつでも代入可能ですが、新しい文法を導入し、代入不可能なローカル変数(?)を作ろうというものです。 最近流行りのテーマですね。foo := exprとすると、fooへの再代入を禁止する、という意欲的な挑戦でした。

とりあえず、foo := exprを受理するパーサを作成し、限定的な場面で再代入を禁止する機能を作成することはできたようですが、すべてのローカル変数への代入を網羅するところまでは至らなかったので、パッチはお蔵入りにしたとのこと。取り組んだ方々は、「パーサが理解できてよかった」という感想をもったそうです。

sprintfの精度のバグを直してみる

[Bug #8916 rb_sprintf への精度指定が正しく機能していない]という報告に対して、数人の方々が取り組みました。C のソースコードをかき分け、問題を追っていって、ある程度目星がついたところで、最終的には nobu がさっと直していった、という結末になりました。

感想を伺ったところ、「仕様の理解に時間がかかりすぎてつらい気持ちになったのに、しゅっと直っていて知識不足を痛感しました」とのことでした。半日しかない中ではしょうがないところだと思います。

Net::FTP で明示的に PORT コマンドを送れるようにする

Net::FTPが明示的に PORT コマンドを送れない関係で、NAT を挟んで active mode で通信できないケースがある、という問題に対するパッチの投稿を行いました https://bugs.ruby-lang.org/issues/13382。社内では渋いテーマだと評判でした。

チケットでは、「そもそも要るのか?」というコメントから議論がはじまりましたが、必要性を示すことで理解を得て、現在は最終的な実装を行っているところです。

Process.uid= validation and casting

[Feature #12410] Process.uid= validation and castingで報告されたバグ報告に対する修正を試みました。

バリデーションを入れたパッチを提案しましたが、チケット上での議論で現在の挙動が適当である、という結論が出たため、リジェクトされることになりました。

Module#source_location

Module#source_locationという、モジュールが定義された場所を返すメソッドを実装し、提案しましたhttps://bugs.ruby-lang.org/issues/13383。すでに、Method#source_locationというメソッドがありますが、その Module版ということになります。

チケットでは、モジュール定義の追加が行われたとき(つまり、リオープンされたとき)、どうするか、といった問題や、実装に関する意見が出ています。便利な機能だと思うので、ぜひ Ruby 2.5 で導入されてほしいですね。

おわりに

f:id:koichi-sasada:20170510112632j:plain

このような形で、Hackarade: MRI Internal Challenge は無事に終了することができました。MRI のコア部分は C 言語で記述されており、最初はどうなることかと思っていたのですが、予想以上に皆さん楽しそうにハックを進めてくれており、一安心でした。半日のハックなので、最初は大きな成果が出なくて当然なのですが、これを機に興味を持ってくれる人が増えると嬉しいと思っています。

最後に、開催にあたっての反省点です。ハッカソン形式なので、課題をどのように準備するかがキモだと思います。今回は「未着手のチケットの中から自由に課題を選んでもらおう」と思っていたのですが、膨大なチケットの中から着手可能な難易度の課題を見つけることができずに、ハックの時間を十分にとれない参加者がそこそこ居ました。初めて見るチケット群では、目星もつきづらいですよね。事前にもう少し精査してチャレンジしやすいものを用意しておけばよかったと思います。

次回の Hackarade は、エンジニアみんなで機械学習を使おう、という企画をしているとのことで、今から楽しみです。

というわけで、本記事ではクックパッド社内で行われた、エンジニア全員参加のハッカソンイベント Hackarade の、1回目、MRI Internal Challenge をご紹介しました。

クックパッドの開発基盤、インフラ環境での開発で心がけているラストワンマイル

$
0
0

f:id:mirakui:20151007180203j:plain

 初めましてインフラや基盤周りの技術が好きなエンジニアの渡辺です。 今回は私がサービス開発を行う上で心がけていることをお話させて頂きます。 (画像は私の好きな言葉で、ここの過去ブログで使われていた物を再掲させて頂いています)

前提

 クックパッドのサービスはクックパッドで整備、運用されている全社共通の開発基盤、インフラ環境上に構築されています。 別に強要されているわけではないのですが、そのレールに乗ることで様々な恩恵を受けることが出来ます。 サービス開発では価値を届ける、検証することにフォーカスしたいのでサービス毎に環境を 1 から構築していては手間が勿体無いです。 そして、セキュリティやバグ等の対応も全社的になるべく共通の環境にすることで環境依存で発生する問題のリスクを分散することが出来ます。

 近年は Microservices 化ということで、新しいサービスを立ち上げる環境整備が進んでいます。 Microservices 化の動向については以下のブログが参考になるかもしれませんので興味がある方は是非読んでみてください。

 大きな方針としてはサービス開発エンジニアの裁量を増やし、今まで依頼ベースで進めていたことを開発基盤、インフラメンバの力を借りずに作業を進められるようにしています。 ただ権限を渡して「はい、どうぞ」ではなく、使いやすくしたり、ミスが起こらないようにツールや環境を整備してくれています。 依頼ベースでの作業が少なくなれば、サービス開発のスピードも上がります。 開発基盤やインフラメンバがボトルネックにならないように、組織としてスケールする為にも個人的にはとてもいい事だと感じています。

 ただ、そこには課題もあると感じています。

課題

 モノリシックな Rails アプリケーションで開発していた時代は作業の分業化が進み、サービス開発エンジニアが開発基盤やインフラ環境をきちんと把握出来ているメンバが少なくなっていました。 これはサービス開発にフォーカス出来るようになったことで発生したジレンマであると思います。

 また、クックパッドはそれなりの規模のサービスということもあり、開発基盤やインフラ環境は色々複雑化、高度化しているのもキャッチアップが難しくなった理由の一つだと思います。 そしてその環境も日々進化、変化していて前に一度触ったことがあっても、後日また利用しようとした時に既に変わっていて、違う使い方を学ばないといけない場合もあります。

 細かくは今回は書きませんが、例としてはどこまでがどう Infrastructor as code で管理されていたかとか、秘匿値の管理が Vaultに変わったり、Hakoの Cluster の使い分けが増えたり・・・。*1

 もう一つは開発基盤はあくまで全社的な共通部分を受け持ってくれるだけで、個別のカスタマイズは自分たちでやる必要があります。 勿論相談すれば助けてくれますし、場合によっては開発をしてくれることもあります。 ただ、上記にも書いた通りスケールする組織になる為には「自分たちで」出来ることが重要だと考えています。

心がけていること

 クックパッドは非常に優秀なエンジニアが居るので日々開発されている開発基盤や整備されているインフラ環境は素晴らしいです。 ただ、どんなに素晴らしい物でも価値を提供、発揮出来なければ絵に描いた餅になってしまいます。 そこで重要なのがラストワンマイルだと個人的には考えています。

ラストワンマイルとは

 FTTH(Fiber To The Home) とかで使われた言葉で今回の話を表現するのにマッチした言葉だと思っています。 一つの部署(または対象のサービス)であれば知っている人が集中してフォローすればいいですが、課題でも書いた通り日々進化、変化していくので継続的な活動が必要になります。 そして部署が増える度にラストワンマイルを埋める為に必要なコストは増えていきます。 しかし、開発基盤やインフラ環境の価値を正しく余すこと無く開発者(サービス)に伝えるにはこのラストワンマイルを無視することは出来ません。 自分としては以前は開発基盤やインフラの仕事もしていたこともあり、今回の「ラストワンマイルを埋める活動でも貢献出来るのでは」と日々取り組んでいたりします。*2

開発基盤、インフラとして目指している方向性の理解

 ラストワンマイルを埋める活動として、単純に日々開発、整備されているツールや環境を把握するだけなく、会社としての技術の方針や技術部やインフラ部の目標、個人の目標を把握することが重要だと考えています。 それは大きな流れとしてどういう方向に進んでいるかを把握していないと、今後廃止されるツールや環境を選んでしまうことがあるからです。 他にも一つひとつの技術やツールだけを見ていると、どういう意図や目的でそれを採用されたのか、使い方としてどういうものが想定されているのかを理解できなくなるからです。

 そして我々は組織で働いています。 サービス開発エンジニアだけでなく、開発基盤、インフラメンバも成果を上げて行くことが勿論期待されています。 ある側面では直接的な価値を提供していない縁の下の力持ちの人たちが、どういう方向性で自分のキャリアパスを描こうとしているのか、またどういうことが成果として認められるのかを把握しておくことで、彼らのパフォーマンスをより引き出したり、どういう作業をお願いすると彼らの成果に繋がることが想像できるようになります。そうなることでお互い Win/Win になる方法を考えることが出来るようになると思います。

適切なフィードバックをする

 これは自分の経験からなのですが、開発基盤に関して言うと自分達が開発したツールや環境の直接的な利用者で無いことがあります。 そういう状況では、改善点や問題点を把握する為には、利用者からの積極的なフィードバックが非常に重要だと感じています。 *3例えば、「せっかく作ったツールがサービス開発エンジニアに使われない」という状況の時に、それは使いづらいからなのか?そういうニーズが無いのか?ということを把握出来ないと、どうして良いかが想像の域を出ないからです。 エラーが出ていても「たまにだから良いか」とならず、きちんとフィードバックをすることで改善活動の指針になったり、放っておくと大問題になりかねない事を未然に防げたりもします。 このフィードバックも目指している方向性の理解している上でしないと、「そういう使い方は想定(または推奨)していない」「それは今後こちらの置き換わる予定」ということにもなりかねません。 こういうコミュニケーションが発生する事自体は問題ないのですが、お互いを理解しようとする意識は持っておく方が個人的には良いと思います。

 余談ですが、そういう仕組等を理解していると Hakoで動いているアプリケーションなら pull request 毎に環境を作って動かせるのではと思い、提案して開発して貰いました。

部としての方針を理解した上で、必要な技術を活用する

 ここで言う「部」とは私が属している部のことです。 今期は目標として新しいサービスをスピード感を持って開発して行くことが多くなる部署だったりします。 出来る限りチームメンバにはサービス開発にフォーカスして欲しいと考えたので、整備してくれている最新の環境を利用して開発していくのが最適と判断しました。 そして徐々にサービス開発エンジニアに裁量が与えられる領域が増えていく部分に関しては、積極的に権限移譲を受けられるように活動しています。 依頼ベースでは作業者が不慣れな事による作業ミスのリスクは減らせますが、今期に必要としているスピード感が落ちると考えているからです。

 個人としてもなるべく早く、信頼して裁量を任せてもらえるように自らの作業範囲を決めること無く、働く様に心がけています。

使うだけでなく自分でも改善出来るようにコードの理解

 運用に乗せたりした後に、改善したいポイントやエラーの原因を把握したくなることがあります。 その時に必要になって調べていては、すぐに修正、対応が必要になった場合に対処が出来なくなってしまいます。 *4なので個人的にはツールを使う場合に出来る限りコードを読むようにしています。 それはどういう仕組で動いているのかを把握する事と、その特徴を掴む意味でもあります。 また、用意されている拡張ポイント等も知ることが出来るので、自分たち用のカスタマイズする時にも役立ちます。

 インフラ環境についても Groupad という社内ツール(blog + wiki)に情報があったりするのでその辺を読んだり、実際の環境や Infrastructure as code で管理されているリポジトリを見たりしています。

実際やってきたこと

 サービスレベルや可用性を上げる為や、日々改善されている環境のメリットを享受する為に社内に古くから存在するアプリケーションを Hakoアプリケーションとして動くように改修しました。 また、 Codenize.toolsを使うにあたって、仕組みや実装を把握する為に私個人でも拙作ですが Applbというのを開発しました。 まだ作ったばかりで荒削りですが、AWS の ELB v2 を Codenize するツールです。 秘匿値の扱いについても権限移譲を受ける為に現在の部で開発しているアプリケーションをまとめて変更しました。

 自分たちである程度サービスを運用出来るようになるためには、ログの見方や監視方法の確立、割れ窓を放置しない、自分たちのアプリケーションが利用しているリソース状況の把握等の活動を自ら率先して行っています。

 また、自分がラストワンマイルを埋める時に得た知識や考えは、部内で行っている定期勉強会で発表して伝えるようにしています。チームビルディングに関しても色々思うところはあるのですが、また機会があれば書きたいと思います。

最後に

 例えるならサービス開発エンジニアは料理人で開発基盤は調理道具を作る人、インフラは水道やガス等を安定供給する人だと思います。 サービス開発エンジニアであれば、求められているまたは目指している料理(=価値)を届ける為に調理道具やインフラの特性を理解し、ケースバイケースで必要とされている最短、最善または最高な開発が出来るエンジニアになりたいものですね。

*1:個人的には色々刺激になって楽しいですが

*2:個人的な技術の興味の方向がそちら向きというのもあります

*3:技術部では色々なメトリクスを収集することで可視化する活動もしてくれていたりします

*4:何度も書いていますが、ありがたいことに開発基盤、インフラメンバは Slack で mention や電話等をかければ緊急時に対応してくれる体制は取ってくれています

Viewing all 726 articles
Browse latest View live