もさの2017年ふりかえりと雑感
サンタさんがswitchをくれたので年末振り返るタイミングを失いました。
2016年は意識高めに振り返ってたので、今年もがんばって意識高めて振り返ろうと思います。
総括
思い返せば、やれる幅や視野が広がった1年でした。
プロモやってみた
自社運用がんばったらCPIおさえられるんじゃね?みたいなことを(超絶素人なのに)言ってたら担当することになりました。2ヶ月くらいかな。
デザイナーさんとがっつり組んで、泥臭く広告クリエイティブを量産したりしていました。毎日にらめっこして入札単価や予算等を調整したり。けっこうな額を任せてもらえて非常に勉強になりました。
たとえばfacebook広告はプロダクトとして非常に良くできていて。機械学習で広告が適切なユーザーさんに出るようにしてくれたり。任意のアプリイベント(チュートリアル突破とか)に最適化して入札できたり。ターゲティングを細かくユーザー属性から指定することもできるし、既存のアクティブユーザーから推定してターゲティングすることもできたり。
管理画面も非常によくてきていて、よくできたSPAで入稿や管理がしやすいし、運用自動化機能もあったり、その場でカルーセルはもちろんスライドショー動画のクリエイティブが作れちゃったりするとか。そもそも広告管理画面自体がABされてるんですよ!
なかなかユーザー側だとわからないですが、広告事業の収益性の高さってこういうところで差がつくんだな〜と感じました。
あとは、毎日のプロモ側のデータを分析用DBにいれて、redashで可視化や分析ができるようにしていました。ユーザーログによる継続率とあわせることで、経路ごとの真のコスパがみえるのはうれしいですね。
広告モデルのプロダクトの収益性はCPI/RR/Sales(LTV)に分解されますが、 CPI/Sales の両面において、良い勉強をさせてもらいました。
仮想通貨/ブロックチェーン
会社で仮想通貨熱が高まっていたので、はじめてビットコインを買ってみたのが2017年の真ん中くらいですかね。わいわいノリで投機していました。
そこから興味をもってサトシ・ナカモトの論文 (和訳)を読んで感動したり(たった9ページなのに力強くて美しい!)、mastering bitcoinを読んで理解を深めたり。
blockchain.tokyo を主催して自分を追い詰めて勉強して、発表したりしていました。
blockchain.tokyo #1 で、lightning networkの前提となる payment channelについて発表したり、
www.slideshare.net
マイニングについてあらためてまとめてみたり。
マイニングのために初めて自作PCつくったりもしてました。(ネトゲ用はBTOでいつも買ってました。。)
チームで毎朝軽く勉強会をする文化をつくれたのも良かったなーと思ってます。毎日問題を出し合って、わからないところは宿題にしたり。例えば、「自分のもっているビットコインの量はどうやってブロックチェーン上保持されている?」「EthereumのEVMはチューリング完全なので無限ループとか書けちゃうわけだけど、どうやって防がれている?」とか。さくっと答えられる人はさすがですね。
本当にめまぐるしい速度で情勢が変わる業界で、調べていてとても楽しいです。
PO やってみた
小さなチームですが、POとPMとLEをやってました。(プロダクトオーナー/プロジェクトマネージャー/リードエンジニア の略です) いままでLEの経験しかなかったので、なかなかつらたんでしたが良い経験になりました。
毎朝数字とむきあって改善点をみつけて。その日のうちにエンジニアとして改善してリリースするくらいの高速サイクルをまわせたのがとても楽しかったです。
ただ反省点もあって。事業計画に追われて、短期で良い結果が出るかも(要は地続きでやりやすい)施策ばかりになっていたのかなあと感じます。ぐっと細かいことを我慢して、本質的なでかくてやりづらい施策に取り組んだ方がよかったのかなと。結果論なとこもあるのですが。
そして複数の役割を兼ねていたことで、経済学でいう比較優位についてずっと考えていました。
たとえば1時間あたりAさんがエンジニア 10 or PM 5の成果を、Bさんは エンジニア 3 or PM 3 の成果を出せるとしたら。Aさんが最強だからエンジニアとPMの両方をやってBさんが手伝うより、比較優位としてAさんエンジニア全張り、BさんはPM全張りの方が実は全体の生産性はよくなります。
(厳密には、「今はエンジニア10よりPM5の成果が大事だ!」とか、「1人が複数を兼ねることで相乗効果があるんや!(デザイナーとフロントエンジニアとか)」とかもあるかもしれないので、そう単純でもないです)
どこもそうだと思いますが、特にベンチャーでは一部の人に仕事が集中しがちです。比較優位のもとに、仕事を剥がして一番得意なことに集中させたほうが全体最適なときもあるよねと。
また、(あくまで僕の考えですが) 人が大きく伸びるタイミングって裁量をもって任されたときしか無いと思うので、そういう意味でもどんどん剥がしてまるっと任せた方がいいなと。
このへんの、「任す」ってこともうまくできず悩んでいたかんじでした。
POとPMって結構相反しがちな役割というか、追うべき指標の方向が違うから兼任すべきじゃないよねーとかも思っておりました。すげー雑なことをいうと、プロダクトや事業のことをひたすら考えて我儘をいうべきPOと、「せやかて工藤」とチームのことを考えて折り合いをつけて、なんならメンバーのキャリアとかも考えちゃうPM。1人でやるとどっちつかずになっちゃう気がします。POとしては「任す」はそこそこに短期を求めてさっさと作りたいし、PMとしては長期を見据えて任したいみたいな。
まぁそんな潤沢に人がいないのがベンチャーなので、どうやったら分身の術をつかえるんだろうとNARUTOを読みふけっていました。
リスクについて
高騰してないころの仮想通貨にたくさん投資して(おそらく)億単位で儲けている友人たちをみて、リスクとらないとリターンってないよねっていう当たり前のことを感じました。
2年前のイーサリアムを投資はおろかマイニングする人とか、半端ないなと。
(正社員かつこの界隈限定かもなのですが)サラリーマンって本当にリスクが少ないなーと思います。解雇規制でなかなかクビにされないし、そこそこのエンジニアならどこも人手不足なので他にうつれるし。極端な話、自分が会社で成果をだしたぞ!!って思っても、実際は会社が自分に投資してそのタスクや事業を任せているので、リスクをとっているのは会社なんですよね。(もちろん個人は時間を投資してますが、それは会社も同じかなあと。)
周囲では当たり前のように起業や独立する人がふえていて。リスクをとって成功した友人をみていると、その後 複利で能力も資産も伸びているのを感じさせられます。ポジションが能力を伸ばし、人も情報もどんどん集まってきて、お金持ちしか投資できない話もたくさん。リスクをとっていない人と比べ、格差はどんどん広がっていくんだなあと。(いや、大前提としてその人が凄いんですけどねもちろん!!!)
結論
ただwebページの更新を通知するだけのツールをつくりました
すごく久しぶりに書きますね。しょぼいですがタイトル通りです。
仮想通貨系で更新が気になるページがあってさくっと作りました。
こんなかんじで、指定したURLたちを定期的に取得して、差分があったらslack通知してくれます。 変更がなかったときは何も言わないよとか、エラったときにも通知するとか、HTMLのこの部分の変化をみたいんやとか、 通知する人のslackアイコンを変えるとか、もろもろ設定できます。
インストール方法とか詳しくは↓をみてね。いまはgoがないと入れられないのでごめんね。変なとこあったらおしえてください。 github.com
取得間隔は、デフォルトで10分ごとにしています。設定変更は一応可能ですが、攻撃にならないよう注意してください。
単なるちょっとしたツールですが、気になる企業のIRをチェックして株をどうこうするとか、サービスのお手軽外形監視とか、web漫画の更新チェックとか、色々つかえるかもですね。
もさの2016年ふりかえり
風邪ひいてうんうんしてたら2017年がきてしまいました。 だらだらして風邪ひいたら1月も半ばになってました。 いまさらだけど、意識高く2016年を振り返ろうと思います
総括
とてもとても躍進できた年でした。できることが増えました。
いまの会社に入社して1年たった
えいやで転職しましたが、とてもいい会社でした。「うちはKPIみてます(DAUしかみてません)」みたいな会社はごまんとありますが、真の意味で数字みて活用している会社です。ひとつひとつの細かい施策ごとに振りかえって数字の要素分解を行い、新しい施策をたてたり、撤退判断をする高速なPDCAがまわっています。どれだけ時間や思いをこめた開発でも、RR等が伸びなかったら即座に元に戻すのです。数字が共通言語なので、「俺はこうおもうんや!!」みたいな主観のぶつかり合いがなく、「過去似た施策やったけど数字ダメだったよね」みたいな含蓄のもとに冷静な議論になるのが凄い風土だな〜と思いました。合う合わないはあると思います。
僕はかなり自由に好き勝手やらせてもらっています。アプリ開発しまくったり、施策に口出しまくったり、分析基盤整えたり、ライブラリ軽くつくってみたり。一時期は(本当は良くないことだけど)勤怠めっちゃくちゃでニュースパスのサーバーサイド開発に没頭していたりしました。
やったこと
- ニュースパスのサーバーサイドけっこう書いた
- 新規事業(アプリ)を2人で全部つくった
- 全部(iOS, サーバーサイド, インフラ, Push基盤, 分析基盤)やった。
できるようになったこと
これまでは、オンプレ環境でPerlでサーバーサイドをゴリゴリ書いてた人だとご理解ください。
Python
主にceleryでジョブキューするために使っています。
あと、管理画面向けに、CRUDなRestful APIをさくっと立てるために使っています(django restful framework)。model定義かくとCRUD APIたつってすごいなぁ(こなみかん)
Go
もう言語設計が頭良すぎてつらいです。必要最小限の美しさ。というか何も考えなくても2行でHTTP Serverたてるだけで死ぬほどスケールするってどういうことなの??
APIはとりあえずgolangでええやんって思ってます。2017年の話になりますが、ジョブキューもgolangで書いてみました。
AWS
意味がわからん、すごすぎる。インフラの人と喧嘩しなくて良いよ!!!全部ひとりで構築できるよ!!!本当最高!!!!!マネージドで運用しなくていいものが増えたよ!!!開発速くなるしインフラエンジニア雇うより安いよ!!!
社内向けにOpsworksでのChef運用が整備されているので、ボタンひとつポチポチでデプロイやサーバー追加などができるのが衝撃でした。rsyncのラッパースクリプト叩くとかじゃないのか…!
Swift
うまれてはじめてネイティブアプリ開発しました。RxSwiftつかってみました。
アプリ、というか最近のフロントエンドは、状態が死ぬほど多くてマジで難しいですね。。。巨大なシングルトンを開発しているかんじ。これに比べてAPIはリクエストごとにライフサイクルが終わるので状態が少なくて本当に楽。リアクティブで少しはマシになるとはいえ、「状態があるからよくないんだ!!状態をなくそう!!!!」とずっと叫びながら開発していました。
そして初心者的にはロジックまわりよりビューまわりがとてもつらかった。デバッグしんどい、ググり方わからん、ストーリーボードいじり方わからん、みたいな…。隣にできる人がいたおかげです。わいまつさんに感謝。
発表したスライド
ほとんど好き勝手発表させてくれるのもよい会社だな〜と。
www.slideshare.net ニュースパスのマイクロサービス開発について。 少し燃えましたので、この場を借りて少し補足すると、「すでにGunosyによってニュースアプリのドメイン知識があったから、思い切ってマイクロサービスしてみたんだよ、だから境界はそこまで失敗してないよ」と言っておきます。
www.slideshare.net いいかんじな設計のクローラーつくったぜ!というお話。
www.slideshare.net キャッシュ戦略と、golangでmemcachedがんばってつかうお話。
拙作ライブラリ
github.com golang memcachedの便利メソッド生やしたクライアント
github.com golang memcachedを用いたmutex
その他いくつかのライブラリにPRしたりしました。
その他
炎上した
某エントリです。ちょっとタイトルで釣ってしまい、口調も激しくしすぎました。すいません。。
禁煙できた
現在禁煙3ヶ月です。自分の中で革命的な変化。惰性のニコチン中毒から脱出することができました。
可処分時間がすごい増えた。作業に没頭できるようになった。くさい臭いから開放された。喫煙所をさがすイライラから開放された。
もう諦めてたんだけど、禁煙外来にノリでいってみたら一瞬で禁煙できました。薬すごい。ちょっと眠気がすごい出ちゃったので1週間で服用をやめた(本当は3ヶ月飲む薬だった)けど余裕で禁煙できました。保険適用内なのでクソ安く禁煙できます。禁煙を考えてる人は行ってみると良いと思います。
2017年のほうふ
- とにかく今やってる事業を成功させたいです
- 体調が不安定なのと、あいかわらず朝弱いので、筋肉で解決したいです。
テストなんか書かなくて良い 僕の考えるサービス開発の肝
世の中は一周まわってエンジニアリングの手法に溢れている。 テストを書け、ドキュメントを書いて冗長化しろ、コミットはわかりやすく、コーディング規約が、安定性が─── でも、それって本質なんだろうか?
新規サービスを作る際に肝だと思っていることをまとめてみた。
おことわり
- 以下は少人数で"普通"のアプリやWebサービスを自社で新規開発するときのことを想定しています。大人数で重厚なソシャゲを作るとか、ガチガチの金融系サービスを作るとか、コンシューマーゲーム開発とか、個人で好きなものを作るとか、受託とかは全く想定していません。
- 基本的に一通り現場をこなした中級以上のエンジニア向けに書いています。
- アンチテーゼとして、ややキツめに断定する箇所が多いです、こういう意見もあるんだな程度に受け止めてください。
- 所属する団体の意見とかは一切関係ありません。
目次
ユーザーのことだけ考える
まず第一に、サービスの主役はユーザーであり、その他の話は大体些細だ。
「事業計画がどうこう」「偉い人が」「新しい技術を試したい」「マネタイズが」「綺麗なコードを」その大体が些細だ。
ターゲットするユーザーは誰なのか、そのユーザーのどんな課題が解決できるのか。そのサービスを使うと何が嬉しいのか。なんでそのサービスを使うのか、他のサービスじゃダメなのか。いつ起動するのか、なんで起動するのか。どうして離脱するのか。
ユーザーとサービスのことだけ考えたら、後のことは大体ついてくる。
企画が最も大事
サービス開発におけるプレイヤーの役割を「企画」「デザイナー」「エンジニア」などとざっくり分けるなら、企画(要件決め)が最も大事だ。企画がクソなものをどんなに上手く作ってもクソだからだ。
だから「俺はエンジニアだから言われたものを作る」とかは態度としてありえない。(スペシャリストなら別だけど、スペシャリストほどなぜかこういう態度はとらないものだ。)
サービスの成功を考えるなら立場によらず最も肝である企画フェーズからどんどん意見すべきで、開発工数的にコスパが合わないと思ったらどんどん代替案を提示すべきだ。
(話は逸れるけれど、設計にはドメイン知識と未来予知のスキルが必要で、「ここはきっとこう要件が変わる可能性があるからこうしとくか」だとかの温度感が掴めないとロクなものができない)
スピード
開発スピードより優先するものはほとんどない。
開発スピードと"品質"は両立できる。さっさと作って、触ってみてイケてないところを直して、リリースして数字や反応をみて直して─── サービス開発はその繰り返しだ。
リリースに関しても、今の時代"普通の"新規サービス開発に1年かかるとしたら遅すぎる。作っている間に競合が出るか市場が変化しているし、万一3年とか言い出すと市場が別世界になっている。そもそも当たってるサービスを数えた方が早いのだから、何年かけて作ったところで当たる保証なんて全く無い。
さらに言えば数年越しの事業計画なんて偉い人向けの説明以上の何者でもない。
※Appleの「ベスト新着App」とかもあるので最初はクソクオリティでも出すべきだとは言わない
開発スピードを妨げるものはとにかく排除すべきだ。
オナニーをやめよう
僕らが作っているのはサービスだ。どんなにコードが綺麗で、カバレッジが100%だろうと、使われないサービスだったらそれは残念ながら意味のないコードだ。
僕らがコミットするべきはサービスの"質"とユーザーへの価値であって、コードの綺麗さではない。全くない。 サービスはユーザーに価値を提供するものであって、エンジニアがオナニーする場所ではない。
オナニーチェックリスト
よく陥りがちなオナニーを項目化した。上から順にオナニー度が高い。
- カバレッジ100%を求める
- ドキュメントを完全に書こうとする
- テストコードの綺麗さや品質にやけにこだわる
- gitコミットの分割とコミットメッセージにこだわる
- 「やってみたいから」で言語やフレームワークを選ぶ
- サービスの安定性に過剰にこだわる
- サービスのバグのなさに過剰にこだわる
- コードの(見た目の)綺麗さにやけにこだわる
- コードレビューフローが整備されすぎている
ここからはやっておいた方が良いかなと思うもの。もちろん程度による。
- 命名にこだわる
- 効率化のためにテストを書く
カバレッジ100%を求める
テストというのはサービスの質を継続的に提供するために書くものだ。金を生んでいるサービスでないと基本的にコスパが悪い。多くのユーザーに使われているサービスならさておき、新しいプロダクトにおいてテストを書くモチベーションはかなり低い。
僕らは1日で新機能のプロトを作って、良ければリリースダメなら廃棄、そういう時間軸で生きている。リリースしても数字がダメなら機能ごと削除するなんて日常茶飯事だ。
「理由があればテストを書かなくてもいい」ではない、「理由がなければテストを書いてはいけない」くらいだ。過度なテストは書く時間の無駄なだけでなく、リファクタや機能追加の邪魔になるからだ(いちいちメソッド名変更などに対応しなければならないため)
もちろん開発効率化のためや、複雑な部分の必要なテストは書いてもいい。大事なのはその見極めだ。
そしてUI周りやリコメンドロジックなど、テストを書いても本質的なところは担保できない(または難しい)ところは沢山ある。人手による確認の質と効率を舐めてはいけない。
ドキュメントを完全に書こうとする
そもそも使われるかわからない(3ヶ月後あるかわからない)機能のドキュメントを完璧に書く必要なんてあるんだろうか? 大人数で開発をしているならわかるが、新規プロダクト開発を大人数でやること自体が悪手だ。
システム体系の伝達は残念ながら一子相伝だ。口頭で伝えて、コードを読んで、実際に書いてみて血肉にしていくものだ。少人数で開発するなら、実は口頭やホワイトボードでの伝達が、同期的という欠点があっても一番効率的だったりする。
そして少人数開発においては、冗長化よりも本来の目的であるスピードを重視すべきだ。
僕らにはドキュメントを完璧に書く時間も、読む時間も、メンテする時間も全く無い。(そしてこのメンテというのが本当に厄介なのだ)
ただし、APIインターフェース/データベーススキーマ/認証フロー 等は最低限書いておくのを勧める。ここで言ってるのは、内部の処理フローを完全に書くとかそういうレベルの話だ。ドキュメントは100点を求めようとすると途端に運用上のコスパが悪くなる。最低限書くべきことを決めて、50点でもいいから運用可能なレベルで妥協すること。
テストコードの綺麗さや品質にやけにこだわる
「テストの独立性が」「振る舞いで書く」とか色々だ。やってもいいけど時間はかけないでくれ。
gitコミットの分割とコミットメッセージにこだわる
とにかくコスパが悪い。そんなに古く遡られることも、コミットメッセージがないとわからないことも稀。コード読んでわからないような話ならソースにコメントとか残しておけば良い。書いた人に聞けば事足りることも多いし。
「やってみたいから」で言語やフレームワークを選ぶ
気持ちはわかるが家でやろう。要件を満たせて、チームが最適に能力を発揮できる言語を選ぶべきだ。勿論採用戦略なら別だが。個人の好みで技術選択をしてはいけない。例えばサーバーサイドの言語選択なら、運用面だけ考えてもこの記事くらい考えることは多いのだ。
ユーザーは何の言語で動いているかなんて興味が無い。
サービスの安定性に過剰にこだわる
これはあまり無い話だとは思うんだけど、神経質にダウンタイムや応答性等にこだわってしまう状態。技術のある大きめの企業だとある話だと思う。
勿論サービスの安定性は死ぬほど大事だし、サーバーエンジニアの腕の見せどころではある。けれどユーザーにとってサービスが凄い安定していることと、それを犠牲にしてでも機能改善のスピードが速いこと。どっちが今のフェーズにとって大事かは考えたほうが良いと思う。スピードと安定性はトレードオフなんだから。
(余談だけど僕はソシャゲは不安定で侘び石がもらえる方が嬉しい)
初めからスケールを気にしすぎてリリースが遅くなるのも少し考えものだ。そもそも2016年現在でも、スケールできるサーバーをきちんと組むのはまだ難しい。それを時間をかけずにできるエンジニアがいるのは稀有なケースだ。極論を言えば、完璧にスケールできるシステムなんて最初から考えるだけ皮算用で、サービスがある程度大きくなってきたら良いエンジニアが採用できるので、そこからシステムをリプレイスしてしまえば良い話だったりもする。だから初めの段階で過剰にこだわるよりも、そこそこにしてさっさと価値を届けることの方が事業としては大事かもしれない。どうせ新規事業なんてほとんど当たらないんだから。
サービスのバグのなさに過剰にこだわる
これも前項と似ている。
ユーザーにとってバグがほとんどないことと、少しバギーだけど機能改善のスピードが速いこと。どっちが今のフェーズにとって大事か。スピードとバグのなさはトレードオフだ。
成長したサービスにおいてバグは命取りだ。それだけで一瞬で何千ものユーザーが離脱したり、数億円レベルの損失が出たりする。けれどひよっこサービスがバグをそんなに恐れるべきだろうか。
バグのなさを品質と呼ぶことも嫌いだ。サービスの"品質"はあくまでユーザーに届ける価値で決まる。
(余談だけど僕はソシャゲはバグがあって侘び石がもらえる方が嬉しい)
コードの(見た目の)綺麗さにやけにこだわる
ここまで読んだ方ならだいたい意図は伝わると思う。勿論綺麗に、一貫して書かれているに越したことはないけど、それに時間を使いすぎるのは違う。そのコードをまるまる破棄することなんて日常茶飯事だからだ。
コードレビューフローが整備されすぎている
これも大体わかると思うので省略。
おわりに
念のため補足するけれど、僕が言いたいのは「テストもドキュメントも全く書かない、焼け野原みたいな現場で戦おう」では無い。そうじゃなくて、「どんな手法もメリット・デメリットがあるんだから、現場のフェーズやプロダクトに合わせて捨てられるものは捨てて、自己満足もやめて、コトに向かおう」ということだ。
だいたいのケースにおいて、技術は目的じゃなくて手段だ。オーバーエンジニアリングをやめて、楽しくユーザーに向き合おう。プロダクト開発はとても面白いのだから。
lua-nginx-module を導入した話 (1) JSON-RPC 2.0 batch request編
nginxにluaを組み込む openresty/lua-nginx-module · GitHub というのが非常に便利で、単なるreverse proxyだったnginxに、あらゆる役割を持たせられるようになります。
nginxの設定を動的にしたり、nginxからDBやmecached, redisなどへのアクセスも可能になります。HTTP requestを発行することさえできます。
これにより、例えばnginxの設定をDB化したり(NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした)、ちょっとした認証機能を入れたり、アクセスをmemcachedやredisなどに記録して攻撃を動的に判定してブロックしたりとか、nginxのレイヤーで色んなことができるようになります。(バックエンドのアプリケーションでも勿論同様のことは可能ですが、前段でブロックできるのが嬉しいですね)
あまりにも色んなことができるので、極論をいえばluaだけでサービスの構築も可能で、バックエンドにアプリケーションを用意する必要さえなくなります。聞く話によると、性能・速度が求められるアドテク企業にてお金がないところだと、本当にnginx+luaだけでサービスを構築しているところもあるそうです。(やりすぎだとは思いますが…)
この記事では応用例の一つとして、JSON-RPC 2.0 batch requestというものをnginx+luaで実装・ライブラリ化した話をします。
目次
JSON-RPC
JSON-RPC はとってもシンプルなRPCプロトコルで、リクエストもレスポンスも全部指定のJSON形式でやろうぜ!というものです。多数の言語でサーバー・クライアント共に実装されフレームワーク化やライブラリ化されています。
JSON-RPCですが、個人的には外部に公開するAPIでもない限り、URI設計や冪等性とか色々気にしないといけないRestFulよりも扱いやすいなーと思っています。大抵のネイティブアプリとサーバーAPI間の通信などはこれで十分ではないでしょうか。
以下がサンプルとなります。
sample
request
{"jsonrpc": "2.0", "method": "subtract", "params": [5, 2], "id": 1}
response
{"jsonrpc": "2.0", "result": 3, "id": 1}
"subtract"という名の引き算するmethodに2つパラメーターを渡しています。とってもシンプルですね。 key "id" の意味は後述します。
request
{"jsonrpc": "2.0", "method": "getUser", "params": { "user_id": 1 }, "id": 1}
response
{"jsonrpc": "2.0", "result": {"name": "mosa", "email": "mosa@hogefuga.com"}, "id": 1}
ユーザー情報を取得するAPIの例です。
どちらも特定のmethodにパラメータつきで1つの単純なリクエストを送って、1つのレスポンスをもらっています。
(後述のbatch requestと区別して、これを便宜上「single request」と呼ぶことにします)
JSON-RPC 2.0 batch request
JSON-RPC 2.0 にはbatch requestという仕様があります。簡単にいうと「複数のリクエストを一気に送って、一気にレスポンスもらおうぜ」というものです。以下がサンプルです。
request
[ {"jsonrpc": "2.0", "method": "subtract", "params": [5, 2], "id": 1}, {"jsonrpc": "2.0", "method": "getUser", "params": { "user_id": 1 }, "id": 2} ]
response
[ {"jsonrpc": "2.0", "result": 3, "id": 1}, {"jsonrpc": "2.0", "result": {"name": "mosa", "email": "mosa@hogefuga.com"}, "id": 2} ]
JSON Arrayで送ってJSON Arrayで返る、とてもシンプルなプロトコルです。 ただし仕様上、送ったArrayの順番通りにレスポンスが並ぶことは保証されません。なので識別子として "id"のkeyをclientは見ないといけません。
batch requestのメリット
batch requestはとても便利です。 複数のAPIのレスポンスをもとにクライアントの画面が描画されることは良くあり、その際batch requestでまとめてしまうことでリクエスト数を減らすことができます。(まるでCSS spriteのようですね) また、クライアントでの「2つのAPIの結果を待たないと描画してはいけない」「どれかのAPIがこけた場合は全体もエラーにしないといけない」などの、非同期では面倒だったハンドリングもシンプルになるケースがあります。
batch request導入前は、リクエスト数を減らすために「1画面に必要な情報はだいたい1つのAPIで返す」ような設計にしていたこともありました。しかしAPIのインターフェースがクライアントのUI変更に引きずられて変更を余儀なくされたりと、設計的に非常に気持ち悪いことになっていました。batch request導入後は「クライアントでどう描画されるか」はあまり気にすることなく、リソース単位でAPIのインターフェースを決めることができるようになりました。
batch requestのデメリット
重い処理に引きずられてしまいます。つまり、処理に10秒かかるリクエストAと、1秒かかるリクエストBの2つをbatch requestにした場合、A, Bのレスポンスをもらうために最低10秒かかることになります。(※サーバーの実装次第では11秒です)
ですので、重いmethodを叩く場合は注意する必要があります。
batch requestのサーバー実装
これだけ便利なbatch requestをどう実装するのが良いでしょうか。 以下、JSON-RPC APIをサーバーに立て、nginxを通してHTTP通信で利用することを考えます。
実装1. フレームワーク側で行う
既存のJSON-RPC 2.0のサーバーフレームワーク実装をみると、対応していないものも多く、対応していても以下のような単純な実装が見られました。
- リクエストで来たJSONをdecodeする
- Arrayではなかったら通常のリクエストとして処理する
- Arrayの場合(batch request)、for loop でそれぞれ順番に処理し、全て終わったらまとめてレスポンスを生成する
この場合、とてもシンプルではありますが、10個分のbatch requestを投げたら処理に10倍時間がかかることになります。あんまりイケてるとはいえません。並列にそれぞれのプロセス(スレッド)が処理してくれるのが理想です。 for loopではなく並列で処理すれば良いのですが、言語によっては複雑な実装になるでしょう。
実装2. proxy serverをたてる
上記の1-3の処理をするだけのproxy コンポーネントを立てるのは、処理を並列化する方法の1つでしょう。 batch requestはまずproxyが処理することにし、proxyがJSON Arrayのそれぞれに対し、非同期で並列にバックエンドのJSON-RPCサーバーにリクエストを投げ、全て返ってきたら1つにまとめてレスポンスを生成する形です。 nodeなどで書けばおそらくシンプルな実装になるでしょう。
しかし管理コンポーネントが増えることになり、下手したら client => nginx1 => proxy => nginx2 => app の経路を辿ることになり、運用の複雑さは増すでしょう。異常が起きた場合、どの経路で死んだのか多数のログを漁ることになります。proxy serverの台数管理も考えないといけません。
(なぜ間にnginx2が必要かは、複雑ですが以下の理由によります。まず、single requestの場合はproxyを挟まない client => nginx2 => app の経路にするとします。このときbatch requestで proxy => app の経路にしてしまうと、appへのreverse proxy設定をnginx2とproxyで二重管理することになります。proxy => nginx2 => app とすれば二重管理を防ぐことができます)
実装3. nginxで行う
という訳で今回提案したいのが、nginx-lua-moduleを用いてnginxで処理をする形です。 nginx => app の経路はそのままで、nginxのレイヤーでbatch requestはバラしちゃおうぜというものです。前項のproxy server の役割をluaが担当することになります。
backendのJSON-RPC APIはsingle requestさえ捌ければ良く、サーバー言語に依らずこの構成が取ることができ、管理コンポーネントも増えないのがメリットです。
イベントドリブンなnginxの性質を活かし、ノンブロッキング(=backendの処理を待たなくてよい)で並列なbatch requestを簡単に実装することができるのがポイントです。
nginx-lua-moduleにはlocaltion.capture
という機能があり、nginx内でノンブロッキングな内部リクエスト(subrequest)を発行することが簡単にできます。また、location.capture_multi
では複数のsubrequestを並列で投げられます。これを用いて、single requestを捌けるlocationへ並列にsubrequestを発行することで、簡単にbatch requestを実装することができます。
(また、内部リクエストなので、single requestのreverse proxy設定はそのまま流用することができ、前述した二重管理問題もおきません)
ややこしいことを書きましたが、今回batch requestに必要な機能をluaのライブラリ化したため、これを用いるだけで簡単に使うことができます。
lua-resty-jsonrpc-batch
という訳で以下はライブラリの紹介です。
使い方
以下のような設定をnginxに書くだけで、batch requestが並列に、ノンブロッキングに実装できます。
server { location /jsonrpc { # jsonrpc single request endpoint } location /jsonrpc/batch { lua_need_request_body on; content_by_lua ' local jsonrpc_batch = require "resty.jsonrpc.batch" client = jsonrpc_batch:new() local res, err = client:batch_request({ path = "/jsonrpc", request = ngx.var.request_body, }) if err then ngx.exit(500) end ngx.say(res) '; } }
/jsonrpc
はjsonrpcのsingle requestを処理できるendpointとします。
/jsonrpc/batch
のエンドポイントは、request bodyにJSON Arrayが来た場合は、バラして並列に /jsonrpc
のエンドポイントにsubrequestを投げます。
シンプルですね!
以下はもう少し凝った使い方がしたい人のための例です。
http { init_by_lua ' local jsonrpc_batch = require "resty.jsonrpc.batch" client = jsonrpc_batch.new({ -- バッチリクエストのArray sizeの制限 max_batch_array_size = 10, -- バックエンドの処理時間をロギングするためにnginx変数に入れる before_subrequest = function(self, ctx, req) ctx.start_at = ngx.now() end, after_subrequest = function(self, ctx, resps, req) ngx.var.jsonrpc_upstream_response_time = ngx.now() - ctx.start_at end, }) '; server { set $jsonrpc_upstream_response_time -; location ~ /jsonrpc/method/.* { # jsonrpc endpoint } location /jsonrpc/batch { lua_need_request_body on; content_by_lua ' local res, err = client:batch_request({ -- リクエストごとにendpointを動的に選択できる path = function(self, ctx, req) return "/jsonrpc/method/" .. req.method end, request = ngx.var.request_body, }); if err then ngx.log(ngx.CRIT, err); ngx.exit(500); end ngx.say(res); '; } } }
この例は以下のような機能を実装しています。
- lua-resty-jsonrpc-batch オブジェクトをリクエストのたびに作るのは馬鹿らしいので、nginxのinit時に作るようにしている
- でかいArrayが来ると攻撃になるので、最大サイズを10としている
- subrequestの処理時間をnginx access logに入れるために、nginx変数に値を入れている
- before_subrequest, after_subrequestのフックポイントを利用している
- 「jsonrpc "substract" methodは
/jsonrpc/method/substract
のパスで受けたい」など、動的なlocationに対応している
この例でわかるように、lua-resty-jsonrpc-batchには、ロギングしたりするためのフックポイントをいくつか用意しています。 とはいえ本質はnginxのsubrequestを利用しているだけなので非常にシンプルです。 詳しくはmosasiru/lua-resty-jsonrpc-batch · GitHubをご覧ください。
インストール方法
まずはnginx-lua-moduleをnginxに入れないといけません。 インストール方法はLua | NGINX にありますが、書いてある通りopenresty (luaやら色々はいった拡張版nginx)を使うことが強く推奨されています。
brew install ngx_openresty
自力でビルドする場合は以下です。
curl -O http://openresty.org/download/ngx_openresty-1.9.3.1.tar.gz tar xzvf ngx_openresty-1.9.3.1.tar.gz cd ngx_openresty-1.9.3.1/ ./configure make make install
lua-resty-jsonrpc-batchのインストール方法は、以下の2通りです。
(1) lua-rocksで入れる
luarocks --local install lua-resty-jsonrpc-batch
(2)ファイルを読み込む
あるいはlua-resty-jsonrpc-batchをcloneして、libディレクトリを適当なところにおき、以下のようにlua_package_pathを指定することでライブラリを読み込むことができます。
http { lua_package_path '/hogehoge/lua-resty-jsonrpc-batch/lib/?.lua;;';
最後に
長々と紹介してきましたが、まとめると、以下の3点になります。
- jsonrpc 便利! batch requestも便利!
- batch requestには色んなサーバー実装が考えられるけど、nginx + luaでやると良さそう!
- lua-resty-jsonrpc-batch っていうライブラリ作ったから使ってね
です。
lua-resty-jsonrpc-batchはまだ色々いじる余地があると思うので、どんどん利用していただけるとありがたいです。
最後に。nginx + luaには本当にデメリットはないのでしょうか?実運用に耐えられるモジュールなのでしょうか?パフォーマンスは?などの疑問が残ります。 これらは、実際に技術調査したり運用して地雷を踏んだ結果をまとめて、次回の記事にしたいと思います。 先に結論だけいうと、少し気をつけるところはあるけど普通に使えるぜ!!です!
ハッカドールにおけるElasticsearch利用法について発表しました
DeNA社内でのElasticsearch勉強会にて、アプリ「ハッカドール」におけるElasitcsearch利用法について発表してきました。
スライドはこちら。
ブログ移転しました
旧ブログ(中身はない)