ISMM 2019 で発表してきました

技術部の笹田です。遠藤さんと同じく Ruby のフルタイムコミッタとして、Ruby インタプリタの開発だけをしています。

先日、アメリカのフェニックスで開催された ISMM 2019 という会議で発表してきたのと、同時開催の PLDI 2019 という会議についでに参加してきたので、簡単にご報告します。

f:id:koichi-sasada:20190717032426j:plain
カンファレンス会場

ISMM 2019

ISMM は、International Symposium on Memory Management の略で、メモリ管理を専門にした、世界最高の学術会議です。というと凄いカッコイイんですが、メモリ管理専門って凄くニッチすぎて、他にないってだけですね。多分。ACM(アメリカのコンピュータ関係の学会。すごい大きい)SIGPLAN(プログラミングに関する分科会。Special Interest Group)のシンポジウムになります。

発表するためには、他の多くの学術会議と同じく、論文投稿をして、査読をうけ、発表に値すると判断される必要があります。基本的に、ガーベージコレクション(GC)のテクニックの提案や、新しい malloc ライブラリの提案とか、NVMどう使うかとか、そういう話を共有する場です。

ISMM 2019 は、6/23 (日) にアメリカのアリゾナ州フェニックスで1日で開催されました。外はムッチャ暑い(40度近い)ですが、室内は空調でムッチャ寒い、というつらい環境でした。外は暑すぎて歩けなかった。

会議は、キーノート2件に通常発表が11件でした。投稿数が24件だったそうで、採択率は50%弱だったようです。日本国内の会議より難しい(私の知っている範囲では、50%はあまり切らない)けど、トップカンファレンスに比べると通りやすい、というレベルだと思います。

今回、ISMM 2019 に投稿した論文が採択されたので、はじめて行ってきました。GC に関する仕事をしているので、ISMM は一度行ってみたい会議だったので、今回参加できてとても嬉しい。Ruby の GC に関する論文の発表だったので、出張としていってきました。感謝。おかげで、最新研究の雰囲気を感じることができました。

正直、内容と英語が難しくて、あんまり聞き取れなかったんですが、分かる範囲でいくつか発表などをご紹介します。

基調講演

2件の発表がありました。

Relaxed memory ordering needs a better specification

1件目はGoogleのHans-J. Boehmさんよる「Relaxed memory ordering needs a better specification」という発表でした。Boehmさんといえば、私にとってはBoehm GCというよく知られた実装の開発者の方ということで、お会いできて大変光栄でした。最近はC++言語仕様の策定などでお名前をよく聞きますが、今回はその話でした。なお、ここ最近は GC の実装にはほとんど関わってないと伺いました。

f:id:koichi-sasada:20190717032523j:plain
Boehmさんのキーノート

マルチスレッドプログラミングにおいて、メモリを読み書きする順序(メモリオーダリングといいます)が問題になることがあります。書いたと思った変数の値を読み込んでみたら、書き込む前の値だった、ってことがあったら驚きますよね。実は、マルチスレッドだとそういうことが起こってしまうんです。性能を良くするために、いくつかのCPUでは、共有メモリに対する他のスレッドからの書き込みが、逐次実行で見える書き込みの順序と違う可能性があるのです。

何を言っているかよくわからないと思うんですが、正直私もよくわかりません。例えば、0初期化された共有メモリ上にある変数 a, b があったとき、a = 1; b = 2; というプログラムがあったら、(a, b) の組は (0, 0)、(1, 0)、(1, 2) の3通りしかないように思うんですが(逐次(シングルスレッド)プログラムだと、実際そうです)、他のスレッドから観測すると、(0, 2) という組が見えたりします(他の最適化が絡むと、もっと変なことが起る可能性があるらしいです)。わけわからないですよね? わからないんですよ。人間にはこんなの管理するのは無理だと思う。共有メモリなんて使うもんじゃない(個人の感想です)。

さて、どんなふうにメモリーオーダリングをするか、という指定をするための言語機能が C++ などにあります(std::memory_order - cppreference.com)。例えば memory_order_seq_cst というのが一番厳しい指定で、他のスレッドからも同じように見える(つまり、上記例だと (0, 2) という組は見えない)ようになり、プログラミングするにはこれが多分一番便利です。ただ、性能のために都合の良いように CPU が順序を変えている(可能性がある)のに、その順序を厳しく制御する、ということになるので、オーバヘッドがかかります。で、どの程度厳しくするか、みたいなので、いくつか種類があるわけです。CPU によって、どの程度デフォルトが厳しいか決まるんですが、幸い、x86(x86_64)は比較的強いメモリオーダリングを行うので、あんまり難しくない、らしいのです。ARM とかだと弱いらしいとか、さっきググったらありました。やばいっすね。

今回の基調講演では memory_order_relaxed という、多分一番ゆるい(何が起こるかわからない)メモリオーダリング指定を、どうやって仕様化すればいいか難しい、という話を、実際にすごく不思議な挙動があるんだよねぇ、という豊富な実例をあげて紹介されていました。従来の仕様では、例ベースでしか仕様に書けなかったんだけど、なんとか書きたいなぁ、でも難しいなあ、というお話でした。結論がよくわかってなかったんだけど、結局うまいこと書けたんだろうか。

なんでメモリ管理の会議 ISMM でメモリオーダリングの話が問題になるかというと、並行GCっていう研究分野があって、GC するスレッドとプログラムを実行するスレッドを並行・並列に実行していくってのがあるんですね。で、それを実現するためにはメモリオーダリングをすごく気にしないといけないわけです。これもきっと人間には無理だと思うんですが、実際にいくつかの処理系でやってるのが凄いですよねえ。いやぁ凄い。

Why do big data and cloud systems stop (slow down)?

2件目のキーノートは、シカゴ大学のShan Lu氏による「Why do big data and cloud systems stop (slow down)?」という発表でした。

実際のウェブアプリケーションや分散処理基盤(Azure。共同研究されてるんでしょうなあ)でどんな問題があるか、主に性能の観点から分析してみたよ、という話でした。ウェブサイト(Shan Lu, CS@U-Chicago)を拝見すると、輝かんばかりの業績ですね(研究者は良い学会に論文を通すことが良い業績と言われています。で、見てみると本当に凄い学会に沢山論文が採択されていて凄い)。

面白かったのが、ウェブアプリケーションの性能分析で Rails が題材になっていたことです。「あ、見たことあるコードだ」みたいな。

ウェブアプリケーションに関する分析の話は、View-Centric Performance Optimization for Database-Backed Web Applications (ICSE'19) のものだったように思います。主に ORM でのアンチパターンをいろいろ分析して(講演では、そのパターンを色々紹介されていました)、それを静的解析してアプリからそのアンチパターンを見つけて良い方法を提案するツールを作ったよ、と。Panorama というツールを作っていて公開されています。なんと IDE (Rubymine)との統合までやっているようです。凄い。論文中に、いくつかリファクタリング例があるので、気になる方は参考にしてみてください。しかし、Rails アプリの静的解析って、えらく難しそうだけど、どれくらい決め打ちでやってるんですかねぇ。

Azure のほうは、設定間違いがほらこんなに、とか、そんなご紹介をされてたような気がします。具体的には What bugs cause production cloud incidents? (HotOS'19) の話かなぁ。論文中 Table 1 がわかりやすいので引用します。

    What are the causes of incidents?
↓ Few hardware problems
↓ Few memory bugs
↓ Few generic semantic bugs
↑ Many fault-detection/handling bugs
↑ Many data-format bugs
↑ More persistent-data races

    How are incidents resolved?
↑ More than half through mitigation w/o patches

Table 1: How are cloud incidents different from failures in single-machine systems?
(↑ and ↓ indicate cloud incidents follow certain pattern more or less than single-machine systems.)

いやぁ、こういう網羅的な調査を行うって凄いですよね。

一般発表

一般発表は、次の4つのセッションに分かれていました(Program - ISMM 2019)。

  • Scaling Up
  • Exotica
  • Mechanics
  • Mechanics / Message Passing

かなり大ざっぱな区切りですよね。Exotica とか凄い名前。

そういえば、"Scaling Up" セッションは、東工大とIBM東京基礎研の方々による3件の発表となっており「東京セッション」と座長に紹介されてました。また、私が発表しているので、東京の組織の発表が11件中4件あったことになるんですね。日本人はメモリ管理好きなんでしょうか。まぁ、私は好きですけど。

いくつか紹介します。

malloc の改良

  • Timescale functions for parallel memory allocation by Pengcheng Li (Google) et.al.
  • A Lock-Free Coalescing-Capable Mechanism for Memory Management by Ricardo Leite (University of Porto) et.al.
  • snmalloc: A Message Passing Allocator by Paul Lietar (Drexel University) et.al.

これら3件の発表は、malloc の実装を改良、もしくは新規に作りました、という話でした。なんというか、malloc() は、まだまだ進化するんだなぁ、やることあるんだなぁ、という感想。どれも、並列計算機(マルチスレッド環境)での弱点をどう克服するか、という研究でした。

とくに最後の snmalloc は面白くて、確保 malloc()、解放 free() のペアって、たいていは同じスレッドで行われると仮定してライブラリを作るので、別スレッドで free() しちゃうと余計なオーバヘッドがかかっちゃう、ことが多いようです(実際、私も作るならそう作りそう)。ただ、いくつかの種類のプログラム、例えば複数スレッドで仕事をパイプライン的に流していくとき、確保と解放は必然的に別スレッドになって、そこがボトルネックになるので、メッセージパッシング機構をうまいこと作ることで、free()の時にしか同期が不用で速いアロケータを作ったよ、というものでした。

Google の中川さんが論文の説記事を書いていたので、ご参照ください(論文「snmalloc: A Message Passing Allocator」(ISMM 2019))。

GC の改良

  • Scaling Up Parallel GC Work-Stealing in Many-Core Environments by Michihiro Horie (IBM Research, Japan) et.al.
  • Learning When to Garbage Collect with Random Forests by Nicholas Jacek (UMass Amherst) et.al.
  • Concurrent Marking of Shape-Changing Objects by Ulan Degenbaev (Google) et.al.
  • Design and Analysis of Field-Logging Write Barriers by Steve Blackburn (Australian National University)

GCの改善の話も結構ありました。

最初の話は、IBM東京基礎研の堀江さんらによる、並列GCの work-stealing を効率化した、という話でした。GCスレッドを複数立てて、GC処理を速く終わらせるには、仕事を分散させるためのテクニックである work-stealing が必要になります。それに関するテクニックの話でした。対象が POWER なのが IBM っぽくて凄いですね。

二つ目は、GCのいろいろなチューニングをランダムフォレストでやってみよう、という話でした。GC の制御も AI 導入、みたいな文脈なんでしょうか?

三つ目は、Google V8 での並行マーキングにおいて、メモリの形(というのは、メモリレイアウトとかサイズとか)を変更しちゃう最適が、並行GCと食い合わせが悪いので、それをうまいこと性能ペナルティなくやるって話で、実際に Chrome に成果が入っているそうです。みんなが使うソフトウェアに、こういうアグレッシブな最適化を入れるの、ほんと凄いですね。話は正直よくわからんかった。

最後は、Field-Logging Write Barriersというのは、フィールド単位(Ruby でいうとインスタンス変数)ごとにライトバリアを効率良く入れる提案でした。Ruby 2.6(MRI)だと、オブジェクト単位でライトバリアを作っているんですが、さらに細かく、バリア、というか、バリアによって覚えておくものを効率良く記録する方法、みたいな話をされていました。むっちゃ既存研究ある中(発表中でも、既存研究こんなにあるよ、と紹介していた)で、さらに提案するのは凄い。

Gradual Write-Barrier Insertion into a Ruby Interpreter

私(笹田)の発表は、Ruby にライトバリア入れて世代別GCとか作ったよ、という Ruby 2.1 から開発を続けている話を紹介しました(Gradual write-barrier insertion into a Ruby interpreterスライド資料)。2013年に思いついたアイディアなので、こういう学会で発表するのはどうかと思ったんですが、ちゃんとこういう場で発表しておいたほうが、他の人が同じような悩みをしなくても済むかも、と思って発表しました。RubyKaigi などでしゃべっていた内容をまとめたものですね。

簡単にご紹介すると、Ruby 2.1 には世代別GC(マーキング)、2.2 にはインクリメンタルGC(マーキング)が導入されました。これを実現するために、"Write-barrier unprotectred object" という概念を導入して、ライトバリアが不完全でもちゃんと動く仕組みを作った、という話です(次回の Web+DB の連載「Ruby のウラガワ」でも解説しますよ。宣伝でした)。GC は遅い、という Ruby の欠点は、この工夫でかなり払拭できたんじゃないかと思います。まだ GC が遅い、というアプリケーションをお持ちの方は、ぜひベンチマークを添えて笹田までご連絡ください。

「Gradual WB insertion」というタイトルは、ライトバリアをちょっとずつ入れて良い、って話で、実際 Ruby 2.1 から Ruby 2.6 までに、徐々にライトバリアを入れていったという記録を添えて、ちゃんと「Gradual に開発できたよ」ということを実証しました、という話になります。

結構面白い話だと思うんだけど、アイディア自体が簡単だったからか、質問とかほとんどなくて残念でした。まぁ、あまり研究の本流ではないので、しょうがないのかなぁ(本流は、ライトバリアなど当然のようにある環境でのGCを考えます)。

PLDI 2019

PLDI は、Programming Language Design and Implementation の略で、プログラミング言語の設計と実装について議論する、世界で最高の学術会議の一つです。以前は、実装の話が多かったんですが、PLDI 2019 から引用しますが、

PLDI is the premier forum in the field of programming languages and programming systems research, covering the areas of design, implementation, theory, applications, and performance.

とあって、設計と実装だけじゃなく、理論やアプリケーション、性能の分析など、プログラミング言語に関する多岐にわたる話題について議論する場です。言語処理系に関する仕事をしているので、一度は行ってみたかった会議です。ISMM出張のついでに出席させて貰いました。参加費だけでも6万円くらいするんですよね。

PLDI 2019 は、6/24-26 の3日間で行われました。ISMM 2019 は、この PLDI 2019 に併設されています。PLDI は言語処理系によるメモリ管理もスコープに入っているので、実は ISMM で発表するよりも PLDI で発表するほうが、他の人から「凄い」と言われます。どの程度凄いことかというと、283論文が投稿され、その中で76本が採択されたそうです(27%の採択率)。これでも、例年より高かったそうです。死ぬまでに一度は通してみたい気もしますね。まぁ、難しいかなぁ(例えば、日本人で PLDI に論文を通した人は、あんまり居ません)。

発表

三日間で最大3セッションパラレルに発表がされるため、あまりちゃんと追えていないのですが、印象に残った発表についてちょっとご紹介します。

ちなみに、以前は結構、がっつり実装の話が多かったんですが、今回の発表は、

  • 理論的な分析の話
  • 特定分野(例えば機械学習)の DSL の話

が多いなぁという印象であり、あんまり(私が)楽しい実装の話は少なかったように思います。

セッションは次の通り(これだけ見てもムッチャ多い)

  • Concurrency 1, 2
  • Language Design 1, 2
  • Probabilistic Programming
  • Synthesis
  • Memory Management
  • Parsing
  • Bug Finding & Testing 1, 2
  • Parallelism and Super Computing 1, 2
  • Type Systems 1, 2, 3
  • Learning Specifications
  • Reasoning and Optimizing ML Models
  • Static Analysis
  • Dynamics: Analysis and Compilation
  • Performance
  • Systems 1, 2
  • Verification 1, 2

いくつかご紹介します。

Renaissance: Benchmarking Suite for Parallel Applications on the JVM

発表は聞いてないんですが、JVM の並列実行ベンチマークについての発表だったそうです。よく DaCapo とかが使われていましたが、また新しく加わるのかな。

DSL

繰り返しになりますが、ある分野に対する DSL の話が沢山ありました。ちょっと例を挙げてみます。

  • LoCal: A Language for Programs Operating on Serialized Data は、シリアライズされた状態のままデータを操作する DSL
  • Compiling KB-Sized Machine Learning Models to Tiny IoT Devices は、IoT 環境みたいなリソースセンシティブな閑居杖、良い感じに整数で浮動小数点っぽい計算をする DSL
  • CHET: An Optimizing Compiler for Fully-Homomorphic Neural-Network Inferencing は、暗号化したまま計算する仕組みのための DSL/Compiler(多分。自信ない)。
  • FaCT: A DSL for Timing-Sensitive Computation は、タイミングアタック(計算時間によって秘密情報を取ろうというサイドチャンネルアタック)を防ぐために、計算時間を結果にかかわらず一定にするコードを生成するための DSL(多分)。

なんかがありました。もっとあると思います。適用領域が変われば言語も変わる。正しいプログラミング言語の用い方だと思いました。

メモリ管理

メモリ管理はわかりやすい話が多くて楽しかったです。

  • AutoPersist: An Easy-To-Use Java NVM Framework Based on Reachability は、Java (JVM) に、良い感じに NVM (Non-volatile-memory) を導入する仕組みを提案。
  • Mesh: Compacting Memory Management for C/C++ Applications C/C++ で無理矢理コンパクションを実現しちゃう共学のメモリアロケータの実装。
  • Panthera: Holistic Memory Management for Big Data Processing over Hybrid Memories は、NVM をでかいメモリが必要な計算でうまいこと使うためのシステムの紹介。

Mesh については、これまた Google 中川さんの論文紹介が参考になります(論文「MESH: Compacting Memory Management for C/C++ Applications」(PLDI 2019) )。むっちゃ面白い。Stripe にも務めている(多分、論文自体は大学の研究)ためか、評価プログラムに Ruby があって面白かった。ちょっと聞いたら(発表後の質疑応答行列に30分待ちました。凄い人気だった)、Ruby のこの辺がうまくマッチしなくて云々、みたいな話をされてました。

Reusable Inline Caching for JavaScript Performance

V8のインラインキャッシュを、再利用可能にして、次のブート時間を短縮しよう、という研究でした。私でも概要がわかる内容で良かった。インラインキャッシュの情報って、基本的には毎回変わっちゃうんで、難しいのではないかと思って聞いてたんですが、巧妙に変わらない内容と変わる内容をわけて、変わらないものだけうまいことキャッシュして、うまくボトルネック(ハッシュ表の検索など)を避ける、という話でした。V8って膨大なソースコードがありそうなので、Google の人に聞いたのですか、と聞いてみたら、全部独学だそうで、すごい苦労して読んだと言ってました。凄い。

Type-Level Computations for Ruby Libraries

RDL なんかを作っている Foster 先生のグループの発表で、Ruby では動的な定義によって、実行時に型が作られるので、じゃあ実行時に型を作ってしまおうという提案です。Ruby でも PLDI に通るんだなあ、と心強く感じます。Ruby 3 の型はどうなるんでしょうね。

A Complete Formal Semantics of x86-64 User-Level Instruction Set Architecture

x86-64 の全命令(3000命令弱といってた)に形式定義を K というツールのフォーマットで記述した、という発表で、ただただ物量が凄い。おかげで、マニュアルなどにバグを見つけたとのことです。成果は Github で公開されてます(kframework/X86-64-semantics: Semantics of x86-64 in K)。

おわりに

ISMMはPLDIに併設されたシンポジウムですが、PLDIもFCRC という、学会が集まった大きな会議の一部として開催されました。懇親会はボーリング場などが併設された会場で行われ、いろいろ規模が凄かったです。

f:id:koichi-sasada:20190717032623j:plain
懇親会の様子

こういう学会に出席すると、最新の研究成果に触れることができます。正直、しっかりと理解できないものが多いのですが、雰囲気というか、今、どういうことが問題とされ、どういうところまで解けているんだ、ということがわかります(まだ、malloc ライブラリの研究ってこんなにやることあるんだ...とか)。このあたりの知見は、回り回って Ruby の開発にも役に立つと信じています。立つと良いなぁ。

今回の論文執筆と参加をサポートしてくれたクックパッドに感謝します。

冪等なデータ処理ジョブを書く

こんにちは、マーケティングサポート事業部データインテリジェンスグループの井上寛之(@inohiro)です。普段はマーケティングに使われるプライベートDMP(データマネジメントプラットフォーム)の開発を行っています。本稿では、その過程で得られた冪等なデータ処理ジョブの書き方に関する工夫を紹介したいと思います。今回は、RDBMS上で SQL によるデータ処理を前提に紹介しますが、この考え方は他の言語や環境におけるデータ処理についても応用できるはずです。

まずクックパッドのDMPと、冪等なジョブについて簡単に説明し、ジョブを冪等にするポイントを挙げます。また、SQL バッチジョブフレームワークである bricolage を使った、冪等なジョブの実装例を示します。

クックパッドのDMPと冪等なジョブ

クックパッドのプライベートDMPは、データウェアハウス(社内の巨大な分析用データベースで、クックパッドでは Amazon Redshift を使っている。以下 DWH) 上で構築されており、主に cookpad.com 上のターゲット広告や、社内のデータ分析に活用されています。材料となるデータは、広告のインプレッションログや、クックパッド上での検索・レシピ閲覧ログです。また他社から得たデータを DWH に取り込んで、活用したりしています。

これらのデータを活用したバッチジョブ群は、社内でも比較的大きめのサイズになっており、途中でジョブが止まってしまうことも考慮して、基本的にそれぞれのジョブが冪等な結果を生成するように開発されています。

冪等についての詳しい説明は省略しますが、簡単に言うと「あるジョブを何度実行しても、同じ結果が得られる」ということです。特にデータ処理の文脈においては、「途中で集計ジョブが失敗してしまったがために、ある日のデータが重複・欠損して生成されていた」ということはあってはなりません。ジョブが冪等になるように開発されていれば、失敗した場合のリトライも比較的簡単になります。また、ジョブが失敗しなかったとしても、(オペミス等で)たまたま複数回実行されるかもしれませんし、毎回同じ結果が生成されるべきです。

さらに、ジョブを冪等になるように開発すると、開発時に手元で試しに実行してみるときも検証が簡単なため、おすすめです。

冪等なジョブにするポイント

プライベート DMP を開発して得られた、ジョブを冪等にするためのポイントはズバリ「トランザクションを使え」です。

トランザクションを使ってロールバック

大量のデータを、長時間(N時間)かけて書き込むようなバッチジョブを考えるとき、途中で止まってしまったり、そこから復旧(リトライ)するという状況は予め考慮されているべきです。このとき、書き込む先がトランザクションをサポートするようなデータベース(一般的なRDBMSなど)ならば、トランザクションを利用しましょう。一つのトランザクションとしてまとめた一連の処理は、「すべて成功した状態」か、「すべて失敗した状態(ロールバック)」のどちらかになることが保証され、中途半端な状態にはなりません。途中で失敗しても、最初からぜんぶ書き直すことになりますが、冪等性は保たれています。

クックパッドの DMP は並列分散 RDB である Amazon Redshift 上に構築されているので、トランザクションをフルに活用しています。

自前でロールバック

一度実行された集計ジョブを再度実行した場面を考えてみます。再度実行される理由はいろいろ考えられますが、「意図せず間違って実行されてしまった」というのも同じような状況と考えられます。前回実行したときと同じ結果が得られれば問題ありませんが、集計した結果が重複してしまうと、後続のジョブが失敗するか、最悪の場合正しくない分析結果を用いて、何らかの意思決定が行われてしまうかもしれません。

つまり、現在実行中のジョブが書き込むテーブルに、今から書き込もうとしている条件で、既にデータが書き込まれているかもしれないのです。そこで、新たな結果をを書き込む前に、既存の行を削除(自前でロールバック)することで重複の発生を避けます。さらに、「削除」と「新しい結果の書き込み」を一つのトランザクションにまとめることで、このジョブは冪等になります。

冪等なデータ構造を利用する

一方で、トランザクションをサポートしないような NoSQL データベースを使っているとき、ジョブを冪等にするのは比較的簡単ではありません。このような状況で考えられる一つの解決策として、何度書き込まれても結果が変わらないデータ構造の利用が挙げられます。集合(Set)やハッシュテーブルです。これらのデータ構造は、データの順序は保証されないものの、既に存在する値(もしくはキー)を書き込んでも、要素が重複しません。

クックパッドの DMP で作成したターゲット広告用のデータは、最終的に Amazon DynamoDB *1 に書き込まれ、広告配信サーバーがそのデータを使っています。ターゲット広告用のデータは、一度に数千万要素をバッチジョブが並列で書き込みますが、このジョブが稀に失敗することがあったり、過去に書き込まれている要素が時を経て再度書き込まれることがあるため、SS(文字列のセット)型を使っています。過去には Redis のセット型を使っていることもありました。

bricolage による冪等なジョブの実装例

クックパッドの DMP だけでなく、社内で SQL バッチジョブを書くときのデファクトスタンダードになっている bricolage には、頻出パターンのジョブを書く際に便利な「ジョブ・クラス」がいくつかあり、これを使うことで冪等なジョブを簡単に実装することができます。この節では bricolage を使った「トランザクションでロールバック」パターンと、「自前でロールバック」パターンの実装例を示します。

bricolage については、ここでは詳しく説明しませんが、詳細については過去の記事「巨大なバッチを分割して構成する 〜SQLバッチフレームワークBricolage〜」や、RubyKaigi 2019 でのLT「Write ETL or ELT data processing jobs with bricolage.」をご参照ください。また inohiro/rubykaigi2019_bricolage_demo にデモプロジェクトを置いてあります。

「トランザクションでロールバック」パターン

rebuild-drop もしくは rebuild-rename ジョブ・クラスを使うと、「現行のテーブルを削除し、新規のテーブルに集計結果を書き込む」または「新規にテーブルを作り、集計結果を書き込み、現行のテーブルとすり替える」という操作を、一つのトランザクションで行うジョブを簡単に実装することができます。rebuild-drop は対象のテーブルを作り直す前に drop table し、rebuild-rename はすり替えられた古いテーブルを、別名で残しておきます。

以下は、毎日作り変えられるようなサマリーテーブルを rebuild-drop ジョブ・クラスで実装した例です。

/*
class: rebuild-drop -- ジョブ・クラスの指定
dest-table: $public_schema.articles_summary
table-def: articles_summary.ct
src-tables:
  pv_log: $public_schema.pv_log
analyze: false
*/

insert into $dest_table
select
    date_trunc('day', logtime)::date as day
    , id_param::integer as article_id
    , count(*) as pv
from
    $pv_log
where
    controller = 'articles' and action = 'show'
    and logtime < '$today'::date
group by
    1, 2
;

このジョブは、以下の SQL に変換されて実行されます。

\timing on

begin transaction; -- トランザクション開始

drop table if exists public.articles_summary cascade; -- 既存テーブルの削除

/* /Users/hiroyuki-inoue/devel/github/rubykaigi2019_bricolage_demo/demo/articles_summary.ct */
create table public.articles_summary
( day date
, article_id integer
, pv bigint
)
;

/* demo/articles_summary-rebuild.sql.job */
insert into public.articles_summary
select
    date_trunc('day', logtime)::date as day
    , id_param::integer as article_id
    , count(*) as pv
from
    public.pv_log
where
    controller = 'articles' and action = 'show'
    and logtime < '2019-07-13'::date
group by
    1, 2
;

commit; -- トランザクション終了

ジョブ全体が begin transaction;commit; で囲われているので、仮に集計クエリに問題があり失敗した場合は、元のテーブルは削除されずに残ります。

「自前でロールバック」パターン

insert-delta ジョブ・クラスは既存のテーブルに差分を書き込むために利用され、差分を書き込む直前に指定した条件でdelete を実行します。また、一連の SQL は一つのトランザクションの中で行われるので、delete 直後の差分を集計するクエリが失敗しても安心です。

以下は、日毎に広告インプレッションを蓄積しているテーブルimpressions_summary に、前日($data_date*2の集計結果を書き込むジョブの例です。delete-cond: に削除条件を指定します。今回の例では、集約条件の一つである日付を指定しています。

/*
class: insert-delta -- ジョブ・クラスの指定
dest-table: $public_schema.impressions_summary
table-def: impressions_summary.ct
src-tables:
    impressions: $ad_schema.impressions
delete-cond: "data_date = '$data_date'::date" -- 削除条件の指定
analyze: false
*/

insert into $dest_table
select
    '$data_date'::date as data_date
    , platform_id
    , device_type
    , count(*) as impressions
from
    $impressions
group by
    1, 2, 3
;

このジョブは以下のような SQL に変換され、実行されます。

\timing on

begin transaction; -- トランザクション開始

delete from impressions_summary where data_date = '2019-07-12'::date; -- 既存行を指定した条件で削除

/* demo/impressions_summary-add.sql.job */
insert into impressions_summary
select
    '2019-07-12'::date as data_date
    , platform_id
    , device_type
    , count(*) as impressions
from
    ad.impressions
group by
    1, 2, 3
;

commit; -- トランザクション終了

テーブルに書き込む前に指定した条件(delete-cond: "data_date = '$data_date'::date")で delete クエリが実行され、"掃除"してから書き込むクエリが実行されるのが確認できると思います。対象の行がなければ何も削除されませんし、対象の行が存在すれば、新たな結果を書き込む前に削除されます。

まとめ

本稿では、クックパッドの DMP 開発において「冪等なデータ処理ジョブ」を書くために行われているいくつかの工夫について紹介しました。また、bricolage を使ってこれらのジョブを実装する例を示しました。

このように、トランザクションのあるデータベースを利用する場合は、なるべくその恩恵に乗っかるのがお手軽です。また、一つのジョブに色々なことを詰め込まず、ジョブを小さく保つことで、ロールバックの対象も小さくなり、失敗した場合のリトライなどもシンプルに行えると思います。bricolage のジョブ・クラスを上手に使うことで、トランザクションを利用した冪等なデータ処理ジョブを簡単に実装することができます。ぜひお試しください。

*1:この記事を書いていて思い出しましたが、Amazon DynamoDB はトランザクションをサポートしたのでした https://aws.amazon.com/jp/blogs/news/new-amazon-dynamodb-transactions/

*2:変数には前日の日付が入るように仮定しているが、ジョブのオプションで上書きが可能

サービス開発でぶつかってきた壁と、そのとき助けてくれた本

こんにちは、開発ディレクターの五味です。クックパッドにレシピを投稿してくれるユーザーのための機能やサービスを開発する「投稿開発部」に在籍しております。

投稿開発部は、2018年1月に前身となる部からメンバーを一新して発足した部署です。自分たちで1から戦略を作るため、強い実感を持ってユーザーを理解することを信条に、資料を読んだり前任者に聞いたりするだけではなく、実際にユーザーとたくさん話し、たくさんレシピを投稿し、ユーザーのことをたくさん考えてきました。

この記事では、その中でぶつかった課題を解決するために取り入れた書籍や、それをうまく業務に取り入れるために行っている工夫を紹介します。

サービス開発にはさまざまな壁が現れる

ユーザーと事業目標に真摯に向き合うほど、サービス開発にはたくさんの壁が現れます。私たちも例外ではなく、部の発足以降、以下のような壁に激突してきました。

  1. 「ユーザー課題の見極め難しい!」の壁
  2. 「戦略づくり難しい!」の壁
  3. 「良いソリューションアイデアが出ない!」の壁

f:id:natsuki53:20190711170248j:plain
開発の歩みとぶつかった壁の所在

壁にぶつかったら学習チャンス

投稿開発部には、日常的に書籍を読んで、仕事に取り入れようとする文化があります。特に壁にぶつかった時は、ブレイクスルーを図るため、チームで意識的に本を読んだりします。

この1年半で、激突した壁ごとにお世話になった本をご紹介します。

1. 「ユーザー課題の見極め難しい!」の壁

部の発足後、早々に苦悩したのが「レシピ投稿ユーザーの本質的な課題は何か」という問いでした。

クックパッドには、レシピを検索するユーザーと投稿するユーザーがいますが、それぞれ数や志向が大きく異なります。投稿ユーザーのための開発をするなら、彼らのことを誰よりもわかっているべきです。「投稿ユーザーはなぜレシピを投稿してくれるのか?」という命題に、自分たちが心底信じられる答えを得たい、でもどうすれば良いのだろう...というのが最初の壁でした。

そしてその時は、以下の本からとっかかりを得ました。

「ジョブ理論」

https://www.amazon.co.jp/dp/B0746JCN8B/

人が何かプロダクトを使う時、必ずその人は何らかの解決したい「ジョブ」を持っており、その解決のためにプロダクトを「雇用」している、という見地に立って、顧客の「ジョブの解決」に寄り添うプロダクト開発を論じた本。「ジョブ」はいわゆる「課題」や「インサイト」と似た意味だが、顧客の置かれている「状況」により注目し、特定状況下で発生する実用的な欲求に目を向けている。

どう活用したか?

  • ユーザーにレシピ投稿を雇用させている「ジョブ」は何なのか、読み解くことにしました
  • 具体的には、さまざまなレシピ投稿者を呼んで根掘り葉掘り話を聞き出し、あとから彼らの「ジョブ」を推察するインタビューを実施しました

やってみてどうだったか?

ユーザーの抱える「ジョブ」の解決をサービス発想の起点にすることは本質的だと感じます。これだ!という「ジョブ」を発見できると強いコンセプトが作れる、という実感が、取り組むうちに芽生えてきました。

ただし「ジョブ」は、ユーザーの発言内容だけでなく、発話時の表情やその人の価値観、普段の生活の様子など、さまざまな情報を複合的に組み合わせて考えないと推測できないので、慣れるまでは考えるのが難しいです。私たちは、インタビュー後に観察した情報を参加者全員でぶちまけ、それを見ながら「このユーザーのジョブは何だったか」を議論して定義する手法で乗り切りました。インタビューの内容は、本に出てくる、ユーザーの発話からキーワードを捉えて深堀りしていく様子を参考にし構成しています。

また、「ジョブ」の定義を何度か重ねていくと、「ジョブ」の粒度をどのくらいに設定するかが難しいことに気づくと思います。その際は本で紹介される事例を引き合いに出しながら、”定義した「ジョブ」を解決している競合サービスを思いつくか”という基準で調整するのがおすすめです。

なお今では施策設計の際、誰のどんな「ジョブ」をターゲットにするのか定義することが必須になるほど、「ジョブ理論」は深く活用されています。最近はインタビューの結果を人物ごとに「ジョブ」起点で記事のようにまとめた「ユーザー白書」を作成・蓄積するなど、実践手法も進化させながら続けています。

f:id:natsuki53:20190711174549p:plain
インタビュー後、「ジョブ」を見つけようとしている議論の様子

2. 「戦略づくり難しい!」の壁

向き合うユーザーのことがわかってきたところで、次は、事業目標をどう達成するか、戦略立案の壁にぶつかりました。投稿開発部の事業戦略は、部長が部の発足前に作った草案を、適時メンバーを巻き込みながら見直し、アップデートしています。目標がかなり動かし難い数字であることに加え、自分たちの学びも日々進化していく状況であるためです。

ただ当時は、事業戦略を考えたことのないメンバーが大半だったので、議論に入っても、意見すらうまく出ない状態でした。そこで、部長主導で以下の書籍を取り入れました。

「ストーリーとしての競争戦略」

https://www.amazon.co.jp/dp/4492532706/

競争戦略(事業戦略)は、静止画でなく動画、ストーリーであり、良い戦略は人に思わず話したくなる面白いストーリーになるはずだ、という見地に立って、他社が追従しようと思えないほど優れた戦略を作るための考え方を説いた本。講義を受けているような文調で、文量も多いが、事業戦略立案の本質を捉えて論じている良著。実在企業の事例も多く紹介されて参考しやすく、チームでの議論の際の引き合いに出しやすい(ただしITの事例は少ない)。

どう活用したか?

  • レシピ投稿サービスのコンセプト=「本当のところ我々は誰に何を売っているのか?」を定義し直すことから始め、自分たちの戦略ストーリー図を作りました
  • その上で、本で紹介されている他社の戦略図と見比べながら、自分たちの戦略図を磨いていきました

やってみてどうだったか?

自分たちのサービスが「本当のところ誰に何を売っているのか?」を定義するのは、想像以上に難しいです!これまでのインタビューで得たユーザーのエピソードをかき集めたり、競合サービスのコンセプトを推測して自社と比較したり、腹落ちする定義に至るまでに少し時間がかかりました。

しかし、そこで定義した提供価値を軸に、ゴール達成を引き起こすまでの中長期のストーリーを描いて作った戦略図は、シンプルで筋が通っていて、戦術や計画を考えるのに使いやすいです。現場でも、単発のアイデアをやみくもに実行することがなくなり、複数の要素を因果関係を持たせながら実現していく計画を考えられるようになりました。

ただし、実際に戦略図を書き起こすのは至難です。筋の良い戦略ストーリー図は、そらで描けるほどシンプルで、且つ、目標達成のために必要な変化を含む...とのことですが、ここは本に紹介されている実在企業の戦略図を横目に見ながら頭を捻りまくるしかありません。戦略図をチームで議論して描くのは難しすぎるので、今はコンセプトと解決策をチームで議論し、ある程度骨子が見えたところで部長が戦略図の草案を書くことが多いです。チームでは、その草案を元に、ストーリーを確実に実現する方法や、より強い「非合理」を入れてストーリーを面白くする方法を考えることにしています。

f:id:natsuki53:20190711171051p:plain
何度もお手本にしている、スターバックスの戦略ストーリー図

3. 「良いソリューションアイデアが出ない!」の壁

事業戦略ができ、具体的な施策づくりに入って行けるようになると、今度は自分たちの出すソリューションアイデアが今ひとつに思えて悩むようになってきました。戦略と仮説には自信があるのに、思いつく解決策に新しさや捻りがない、そもそも出てくるアイデアの数が少ない...。この壁は二重構造になっていて、以下2つの問題で成り立っていました。

  • ①チームでのアイデア出しの進め方がわからない
  • ②出すアイデアの量・質に自信が持てない

それぞれを助けてくれた本は以下です。

① 「SPRINT 最速仕事術―あらゆる仕事がうまくいく最も合理的な方法」

https://www.amazon.co.jp/dp/B06Y5NW5PQ/

問題①に効いた本。Googleで開発されたという、5日間で新しいアイデアを形にして検証・評価まで完了させる「スプリント」というフレームワークを紹介し、その実践手順を詳しく解説している。

どう活用したか?

  • 施策検証の段階にある施策で何度かそのまま取り入れて、スプリントを実施しました
  • その後、一部の手法を部分的に切り出して、普段の会議に取り入れるようにしました

やってみてどうだった?

スプリント自体は一長一短あると感じます。良いところは、テーマに対して参加者全員で大量のインプットを得て、めちゃくちゃ集中して考えることです。メンバーの脳内同期やステークホルダーの巻き込みにも効いたりします。また、とにかく時間が制限されるので、煮え切らないアイデアを捨てる判断ができる点も良いです。デメリットは、参加者全員の5日間の時間拘束が辛いことと、得られる成果の質が参加者の能力に左右されること。また、「レシピ投稿」はもともと施策の効きに時間がかかる傾向があるのですが、それを1日のインタビューで評価して良いのか?という疑念が拭いきれないことも、私たちにとっては大きな気がかりです。

ただ、「有能な人の仕事の流れをフレームワーク化した」と言うだけあって、スプリントのフレームワークには、目的に対する情報インプット、アイデア出し、検証、評価を効率的に行う工夫が詰まっていると感じます。それらを切り出して普段の業務に取り入れても、アイデアを考えたり意思決定するのがラクになり、チームのアイデア出しのパフォーマンスが高まります。専門家インタビュー、光速デモ、クレイジー8、ストーリーボード、ヒートマップ、サイレント投票などの手法は、普段の会議でも単発で取り入れやすいのでおすすめです。

f:id:natsuki53:20190711171137p:plain
スプリントの手法を活用したアイデア出しの様子

② 「直感と論理をつなぐ思考法 VISION DRIVEN」

https://www.amazon.co.jp/dp/B07NMN1B5Z/

問題②に効いた本。世の中を動かしてきたのは、ロジカルに組み上げられたアイデアではなく、「自分駆動の妄想」を起点にしたビジョンだ、という前提の元、まず根拠のない妄想(ビジョン)があって、それを実現する筋道を作るために論理を組み立てる「ビジョン思考」を提唱している。「ビジョン思考」の強化方法や、それに則ったアイデア作りの方法も詳しく解説されている。

どう活用したか?

  • チームで、既存路線上にない新しいアイデアを出さなければいけない時に、本で紹介されている「組替」の手法を取り入れました
    • 変化を起こしたい事象の「当たり前」を洗い出し、違和感のある「当たり前」をひっくり返してから、それを元に新しいアイデアを生み出していく手法です
  • 併せて、チームメンバーと「プロトタイピング志向」を徹底する同盟を組んでいます
    • 生煮えの考えもissue化したり、粗いプロトタイプを作って早期に人に見せることでアイデアに客観的な視点を加え、そこから練り上げていくところに時間をかけます

やってみてどうだったか?

「組替」の手法は取り入れ始めて日が浅いですが、以前より新しさを含んだアイデアがたくさん出るようになったと感じます。フレームワークに則って頭と手を動かせば、「ひらめき」=”既存要素の組み替え”を意図的に起こせるよう考えられており、チームで一緒に実践しやすいのも良い点です。

「プロトタイピング志向」は、普段のディレクター業務で実践するのにはまだ慣れないですが、うまくできた時は、1人で考え込むことに時間を使ってしまった時よりも成果物の質は高まると感じます。ちなみにこのブログ記事も、構想段階から色んな人に相談し、レビューしてもらって仕上げました!

また個人的にはこの本によって、「仕事のアイデアは論理で組み上げなければならない」という思い込みを打破できたことは大きかったです。まず直感に目を向け、それを人に説明できるよう後から論理づけして磨いていくという思考を意識すると、出せるアイデアの質が変わってくるのを感じます。

f:id:natsuki53:20190711171221p:plain
「組替」の手法を使ったアイデア出しの様子

本の知識をチームに取り入れるために

せっかく読んだ本から知識を”モノにする”には、読了後すみやかに得た知識を業務で実践することが1番重要だと感じます。また、チームで仕事を進める中では、”共通言語を作る”という意味でも、書籍で得た知識の共有は有効です。特にチームが悩んでいる時や、抽象度の高い議論を進めなければならない時、うまい共通言語を得ると、停滞していた話が進み始めることが多々あります。

新しい知見を業務で実践するところまで漕ぎつけたり、自分の読んでいる本の知識をうまくチームに共有できるかは、読んだ人の裁量によりがちです。そのため、本の知識をチームや業務に取り入れやすくするために、以下のような工夫をしています。

1.読んでいる本を共有しやすくする工夫

読書感想文共有スレ

GHEの部署のリポジトリ配下に「サービス開発系の読んだ本の感想を書くスレ」というissueを常設。簡素な1行コメントから超長文レポートまで、メンバーが読書感想を自由にpostしています。熱量の高い感想文には自然と注目が集まるし、共有したい知識はそこにまとめておけば参照してもらえるので、その後の議論でも話題に出しやすくなるのが利点です。最近このissueはコンテンツ力が増してきて、他部署からもファンや投稿者を創出する人気スレ(?)になりつつあります。

定例や1on1で読んでいる本の話をする

定例ミーティングや部長との1on1で、いま読んでいる本をよく共有しあいます。同じ本でも人によって読み取り方が異なるので、話すことで新しい観点が得られたり、チームの誰かと「それいいね!」「あの施策で試せるじゃん!」という会話をしておけると、そのあとの情報発信や業務での実践提案がしやすくなるので、意図的に活用しています。

その他、読んで良かった本は、物理本を買ってデスクに置いておくのも一手です。興味を持ってくれた人にサッと貸し出して、味方を増やします。

f:id:natsuki53:20190711171325j:plain

2.本の知識を業務で実践しやすくする工夫

わかりやすい事例や概念を切り出しておく

目的の書籍を読んでいない人を巻き込みたい時に使います。何も知らない相手に、自分が本から得た知識を口頭で説明してわかってもらう(その上で同じレベルで議論に参加してもらう)ことは至難の技ですが、本の中からわかりやすい事例や、概念を端的に表した図解などを切り出しておいて「うちもこんな風にやってみませんか」と提案すると、やりたいことをわかってもらいやすくなります。

読書後、自分の業務で実践するtodoを出す

「本を読んでも自分の仕事にどう活かせるか、パッとわからない...」という人(=私)におすすめの手法です。本を1冊読んだら、そこから自分の仕事で試してみたいtodoを1〜3個だけ考え出します。それだけで実践に運べる確率が上がります。「前のtodoが終わるまで次の本を読めない」というルールをつけると、より強制力が働くのでおすすめです。あとは実際にやってみて、継続するか・やめるかを振り返る機会を作れば完璧です。

f:id:natsuki53:20190711171436p:plain

おわりに

今回は特に大きかった壁と、ヘビロテしている選りすぐりの本を紹介しましたが、チームでお世話になってる本は他にもまだまだたくさんあります。仕事に行き詰まった時は、視野を広げて新しい知識を取り入れるチャンスと捉え、これからも積極的に本を読んでいきたいところです。

なお現在は、よりインプットの幅を広げたく、書籍に加えて、似た課題に直面している方々との情報交換も積極的に行っていきたい所存です!もしご興味を持ってくださる方がいらっしゃいましたら、お気軽にご連絡ください。
fb: https://www.facebook.com/natsuki.gomi.7

そしてそして、こんな私たちと一緒に壁に激突して、一緒に成長してくれる仲間も募集中です!募集中の職種は採用サイトからご確認ください!
https://info.cookpad.com/careers/