Hackarade: MRI Internal Challenge

今年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 をご紹介しました。

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

最近、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 ruby
require 'sisimai'
v = Sisimai.make('/path/to/mbox') # or Path to Maildir

if 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               # userunknown
  end
else
  # There is no bounce message in the mailbox
  # or Sisimai could not parse
end

Sisito

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

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

f:id:winebarrel:20170508161014p:plain f: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を試してみてはどうでしょうか?

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

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

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

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

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


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