Developers Summit 2014 ...

Post on 01-Dec-2014

47.481 views 11 download

description

http://event.shoeisha.jp/devsumi/20140213/session/407 以上の発表の発表内容となります。

Transcript of Developers Summit 2014 ...

Play2/Scalaでドメイン駆動設計を利用した大規模Webアプリケーションの

スクラム開発の勘所

株式会社ドワンゴ ニコニコ生放送吉村 総一郎 (@sifue)

吉村 総一郎 (@sifue)• 元々は、製造業の業務改革をメインとするシステムコンサルティングで働くSE

• 趣味はゲーム実況 (最近はFF14)とプログラミング

• 好きな言語はJava

• 2012年4月、趣味のゲーム実況やゲーム用ツール開発が高じてドワンゴへ

今日はニコニコ生放送をScalaで書き直している話

そもそもなぜ書き直さねばならなかったのか?

2012年12月

コードの技術的負債• PHPのコード行数 300万行に対し

• コピー&ペースト数1万ヶ所

• 潜在的不具合数(PMD警告値)が4500ヶ所

• 循環的複雑度600超メソッドがいくつも存在

• IDEサポートができない文字列のリフレクションの多い、grep不能なPHPのコード

循環的複雑度600超メソッドは例えるなら終盤のジェンガ

• 人類の英知を結集しても不具合を入れずに不具合改修することが困難なレベル

• 今も解消されていない

• 不具合修正をする数よりもそれによって発現する不具合の方が多くなってしまうという、まさに負債

循環的複雑度

複雑さの状態 バグ混入確率

10以下

30以下

50以下

75以上

非常に良い構造 25.00%

構造的なリスクあり

40.00%

テスト不可能 75.00%

いかなる変更も誤修正を生む

98.00%

なぜこのような事になってしまったのか?

• 企画の要件と締切優先で、ソフトウェアの内部品質やプロセス品質を疎かにする開発体制

• 多くの人員が追加されていたが、根本修正を恐れる余りパッチワーク的な修正が多く実施された

• 結果、コピペばかりの1万行のクラスや4000行のメソッドが生まれ、300万行のいたる所に暗黙の影響範囲を持つコードベースになった

このような状況に対して打ち出した緊急対応

1. 各人のタスクを見える状況にして、効率よくリソースを配分できるようにする

2. いらない機能を削って保守コストを下げる

3. カーボーイ個人開発からアジャイル開発体制へ移行してチームの利点を活かせるようにする

4. 長期案件、突発対応、改善活動の3つのチームに分割し、安定して改善活動を行えるようにする

3ヶ月間の対応の結果

1. 大規模な障害と休日出社が減った

2. コード行数の増加が止まった

3. メンバーが休んでもチームが回るようになった

4. 離職が減った

とはいえ、既に終盤のジェンガには現状維持が精一杯だった

そして、ニコ生を書き直すニコ生2プロジェクトが発足

目的は継続的な大規模配信に耐えられるようにするため以下の性能を担保すること

1. スケーラビリティ

2. 安定性・フォールトトレラント性

3. 保守性と拡張性

4. パフォーマンス

以上の順番の優先順位で目標を設定することとなった。今の設計ではサーバーを足すのに大きなシステム変更コストが必要であり、今後容易にスケールアウトできることに主眼をおいた。

2013年4月に4人でプロジェクトスタート左端が担当部長の鈴木圭一さん、右4人がメンバー

スモールスタート/スモールリリースプロトタイプ開発完了から実際のリリースまでを6ヶ月以内に設定し、要件を絞るために、要件の少ない広告型サイズのプレイヤーとして開発することにした

プロトタイプ作成をしながら常にそこまでの成果を2週間に一度ショーケースで紹介

• 常に全ての関係者がプロジェクトのここまでの成果を確認できるショーケース環境を作成。上のスライドはただの絵だが、実際に6月に動くものをそこで紹介した。

結果10月中には無事、広告型プレイヤーのリリース成功

• 記念すべき最初の番組はチンアナゴでした

最終的にメンバーは13名まで増え、12月の年末特別番組を生2で実施

• 大規模の接続が想定された年末の小林幸子さんのカウントダウン番組で生2のプレイヤーを利用した放送を実施、無事成功に終わる

今日のお話はここから、このプロジェクトの裏側を紹介します。

Play2/Scalaでの大規模Webアプリケーション開発

なぜScalaを選択したのか?

• Java, Scala, PHP, Rubyからの選択

• 保守性の観点から静的型付けの言語を使いたかった

• パフォーマンスの懸念はプロトタイプ作成後に判断することに

• 幸いプロトタイプを作ったメンバーは全員Java/Ruby/関数型言語などの経験があり、Scalaに抵抗はなかった

• ただし、途中から加わったPHPしかやってこなかったメンバーがScalaをやるにあたっては辛い面があった

PHPしかやってこなかったメンバーがScalaをやるにあたってネックになる知識

1. アルゴリズムとデータ構造

2. 純粋関数型プログラミング

3. valとvar

4. implicit

5. 並行プログラミングの知識

6. JVMとJavaライブラリの仕組み

1.アルゴリズムとデータ構造

• PHPは本当に素晴らしい言語で、基本全てのコレクションをarrayで扱うため、データ構造等に詳しい必要がなかった

• 今までPHPのarrayしか使わなかった人に、Scalaで配列と連結リストとツリーセットとハッシュマップ…またこれらの不変、可変、並行コレクションを使い分けさせるのは難しい

• 対策としてプログラミングコンテストチャレンジブックを利用して勉強会を開催。半年で20名に実施した。

プログラミングコンテストチャレンジブック初級編の勉強会

• 第1回 再帰関数と深さ優先探索

• 第2回 スタックとキューと幅優先探索

• 第3回 貪欲法

• 第4回 動的計画法とメモ化

• 第5回 プライオリティーキュー

• 第6回 Union-Find木

• 第7回 ダイクストラ法

2. 純粋関数型プログラミング

• 無理に適用しない

• コード自体もほとんどこのスタイルで書かれておらず、ベターJavaとして利用している側面が強い

• 使う分には簡潔にかけることもあり便利

3. valとvar

• valを徹底

• 可変コレクションも基本的に使わない

• 徹底したからと言ってそんなに困ることはない

• サーバーでメモリリークが発生せず素晴らしい

4. implicit• ライブラリ層を除いては極力利用しない

• 特にimplicit converterをガリガリ使った部分が負債になりやすい

• ひとつぐらいなら良いが3~4個読み出したり、スコープごとに違うコンバーター呼び始めると大変…

• 冗長な記述は減らせて良いが、多くの値がimplicitだとpull requestでのコードレビューでわかりにくくなる

5. 並行プログラミングの知識

• Actorのトラブル解決にどうしても必要になる

• 今も課題で、今後、Java並行処理プログラミングの勉強会を開催したい

• 実際はActorを純粋な分散コンピューティングのフレームワークとしては使わず、ワーカースレッドパターンを使うライブラリとして使っている側面が強い

6. JVMとJavaライブラリの仕組み

• 業務の中で少しずつ学べばOK

• チームに1人はJavaのエキスパートがいれば持っていなくても大丈夫

プロトタイプ開発で模索したニコ生2のアーキテクチャ

インフラメンバーにデイリースタンドアップに参加してもらった

• やはりコミュニケーションが重要

• 最初からインフラの制約などを細かく確認していくためにコミュニケーションコストを払った

• ドワンゴではインフラ運用とアプリ開発で部署が分かれており現在は席を近くにする等の運用もされている

• デスクレビューを実施する際にも気軽にインフラメンバー呼べて助かっている

クラウドではなくオンプレミスという選択

• IOパフォーマンス

• 費用

• 社内方針として

• だが世の中の流れ的にWebの構築にクラウドを利用したりするようになっており、データセンターや大規模Webのインフラ構築に関するノウハウがレアになり、人材育成、人材確保が課題になっている

DBの水平分割と垂直分割• DBにはMySQLを利用

• 水平分割

• 64bitのIDの中にシャードID(12bit)やデータタイプID(10bit)などを組み込んで、DBのシャーディングが容易になるようにしている

• InstagramのID設計、PinterestのID設計、Twitterのsnowflakeなどを参考にした

• 垂直分割は、サイズが小さくシャーディングが不要な共有DBにのみ適用している

2つのキャッシュ中央redisとローカルredis

• キャッシュで利用するローカルredis群と中央で複数のwebインスタンスから利用する速いデータストアとしての中央redis群との使い分け。ただ中央redisはデータ最小化のため工夫している。

web redis

redis

redisredis

twemproxy

redis

冗長化方法• MySQLはMHAで冗長化

• 当初、GTIDを用いた方法も模索したがフェイルオーバーのダウンタイムがMHAの方が短かったため

• 運用としては、VIPを利用して切り替えて対応

• redisはSentinelで冗長化

• 中央redisのみ

• マスター昇格の際に全てのデータのコピーが走るのでダウンタイムを少なくするためにデータを小さくする工夫が必要

外部環境から負荷を分離するアプリケーション構成

web用

websocket用

バッチ用

管理ツール用

外部リソース中継用

データストアクライ

アント

配信系メッセージサーバー

• 高負荷時は、webかwebsokcetが非常に重くなるためその他の機能をできるだけ逃す構成となっている

websocketアプリ1インスタンスで、5万ユーザーの視聴管理を実現

• nginxとNettyサーバーで非同期IOを利用することで最大接続数が改善

• REST APIからWebsocketを利用することによって接続コストが低減

• 結果、CPUバインドの部分では既存の生放送の4倍の効率を実現

• パフォーマンス測定方法

• gatlingが素晴らしいストレスツール、レポートが綺麗

• ただしwebsocket部分はプラグインのパフォーマンスが出ず、結局node.jsでストレスツールを自作。node.js素晴らしい。

ドメイン駆動設計の導入

ニコ生2でDDDをするにあたって最低限徹底したこと

1. ユビキタス言語

2. モデルを使って議論するということ

3. レイヤードアーキテクチャ

4. 正しいモジュール化を行い、変更時に読まなきゃいけないコードを減らす

1. コアの蒸留

2. 汎用サブドメインの分離

5. エンティティと値オブジェクトの使い分け

UI層

アプリケーション層

ドメイン層

インフラ層

依存方向

依存方向

依存方向

図: レイヤードアーキテクチャ

※ DDD: Domain-Driven Design

Play2/ScalaはDDDを実践するにはとても良いフレームワーク

• CoCがきつくなく好き勝手フォルダが掘れる

• マルチプロジェクト機能でサブモジュールの依存関係を作れ、レイヤードアーキテクチャの強制できる

• ドメイン層、インフラ層はplayに依存しないただのjar

• case classがエンティティと値オブジェクトの作成のコストを下げる

• 例えば、インフラ層ORMのマッピングクラスとドメイン層のエンティティを分けることなどが苦にならない。

• play2を捨てられるように開発することが可能。※ CoC:「設定より規約」(CoC:Convention over Configuration)

とは言え、ドメイン駆動設計自が導入ハードルが高い

エリック・エヴァンスのドメイン駆動設計に入る前にメンバーに読んでもらいたい本

多いし、どれも値段が高い!手に入りにくいものもある...

仕方がないから社内で講習会

そんな時の強い味方

Youtubeに上がっているDevLoveで都元さんがDDDを解説した時のムービー

• 社内でこれを見る上映会が3度開かれるほど素晴らしい映像 (42分)

• http://youtu.be/FNEfk-dlIKU

だがユビキタス言語の醸成には、日々モデルや概念について企画、インフラを交えての議論が必要

• 近い座席と雑談

• 会議体も重要

• デイリースタンドアップ

• ショーケース

• 日々細かく確認、議論

DDDを実践する価値とは

DDDではないコードの例• StreamModel.class.phpの議論は難しい... (1万行)

番組であり番組を作るものであり番組をコピーしたり更新したりバックアップをとったりするものでありストリームであり生放送でありタイムシフトでありタイムシフト予約のリストを見れるものでありメッセージサーバーを割り当てるものであり多国籍化をするものでありキャッシュとの通信をするものでありDBとの通信をするものであり

神クラスについての議論は難しいIt’s difficult to talk about a “God class”.

?

ユビキタス言語が導入されると、複数のチームで共通の言語を使って会話ができる

• ストーリーの内容について、チーム内、チーム関係者との議論がしやすくなる

• 結果として、システム内の作りについてのチーム全体と理解度が高まる

• Programというクラスは番組であるだけである

• Streamというクラスがストリームであるだけである

• Liveというクラスが生放送であるだけである

• 生放送がうまく作れないとか、ストリームが割り当てられないとかそれがコード上のどこのはなしかわかりやすい

• 無論ユビキタス言語ができる際には直接、またはIRCでしっかり相談する必要あり

コードと普段要件について話している言語の乖離が少なくなり、リファクタ性能があがる

• ただし、ドメイン層だけ

• ドメイン層へのビジネスロジックの集約に対する意識が根付く

• 結果、ビジネスロジックの変更のリファクタに強くなる

• 何度か生放送の配信インフラやメッセージサーバーの利用の要件が変わる度にリファクタリングを行ったが、問題なく行えた

• 過去はこういうことをやろうとした際、いろいろな場所にビジネスロジックが飛び火していて、大仕事、デグレも多かった

• ただ、うっかりするとビジネスロジックがドメイン層から逃げて、アプリケーション層やインフラ層に行きがちなので、コードレビュー、設計レビューでの入念な監視と継続的なリファクタリングが必要である

アプリケーションの運用とチーム開発

Capistranoによるデプロイ• Playの設定のテンプレート化をerbとymlで実現

• いろんなインスタンスに同じことを書く設定に有効

• 設定プロパティが配列に対応していないが、数を動的に変える必要がある設定に有効

• capistranoへの処理の集約

• minaにしようとしたが分散デプロイ機能がなくて断念

• 他に良い物があれば移行したいと考えている

コード規約としてのScala Guide/ Effective Scala

• Effective Scalaを守らせるScalastyleを作ってgithubで公開中

• https://github.com/dwango/scalastyle

• CPD (Copy and Paste Detector)によるコピペチェックも実施

開発環境はIntelliJ IDEA Ultimateに統一

• 細かい作りこみや安定性が良い

• 会社がチームメンバーに導入

• 理論上はどんなソフトウェアでもメモ帳では作れるが…

• 開発環境を統一することで、環境構築ドキュメントなどを高品質にできる良さがある

• vimmerにも好評なideavimがある

JenkinsのCI/CD運用• メインジョブは、ビルド、テスト、メトリクス、パッケージ作成、自動デプロイをしている

• Github:eにおけるプルリクエスト元のブランチの自動チェックもしている。失敗してると警告が…

CI/CDの成果• 現在は5万行で、Scala 重複コード0、scalastyle警告0、自動テストでの行ベースコードカバレッジ52%

• とはいえど、数値を常によく保ち続けるにはリーダーやCIのメンテナーの啓蒙活動が重要

• 検証期間の大幅な工数削減

• 検証期間に自動ビルド、自動テスト、自動デプロイの工数削減効果がわかりやすい。ビルド職人とデプロイ職人をなくせることは大きい。

Play2/Scala開発の難点1. sbtが遅すぎる

• proxyを作ったり、良いマシンを使うぐらいしかない

2. Play2のscalaテンプレートが、コンパイル遅く、文法がデザイナーにやさしくない仕様

• Groovyテンプレートで作成。 github/dwangoでフォークしたものを公開中

3. テストで困るobjectとtraitと関数引数

• specs2のmockitoの制約。objectのモック作れない、traitはmixinが必要、ジェネリクスパラメーターが付いた関数引数のメソッドを拡張できない。

スクラムの導入

自身のウォーターフォール開発とアジャイル開発のプロジェクトマネージャ経験1. 過去4度のフォーターフォール開発と、大失敗の経験

• 要求が変更されてしまうと地獄

• 不確定要素に対応するためのバッファ重要

• 最後のしわ寄せは品質に

2. 要件が確定できない、また、要求の優先順位が日々変わる場合

• アジャイルとスクラムの考えが良い

• 日々変わるチームの目標への柔軟な対応が必要なWeb開発

アジャイル導入までの大変さ• みんなガントチャートがわかりやすくて好き、という問題

• 啓蒙活動はやはりフェイストゥフェイス (スライド作ったり)

• 実際の現場にあるマネジメント問題の根本は、アジャイルとかウォーターフォールとか関係ないことが多い

• 問題の発見、目的や目標の設定をし、チームでのPDCAの実行ができていれば良いのだがそれができてないこともある

• カーボーイ開発にも良さがあるので強要しないほうが良いことも

• 進捗確認が高コストで、ドキュメントは残らないが、個人のパフォーマンスを引き出せる

スクラムはリーダーを育てるのに素晴らしい開発手法

• 会議体が固定

• JIRA/GreenHopperなどのサポートツール

• 書籍が充実している

• プランニングポーカーでの見積もりが、スクラムマスターを孤立させない

• 全員が全ての帽子をかぶるので段階的にスクラムマスターにさせることが可能

図: GreenHopperの画面

とはいえ、スクラムマスターとしての基礎知識は必要で別途座学の講座を開いている

• マネジメントを通じてメンバーを活かす、というドラッカーの考え方は、アジャイルと相性が良い

対象 教科書

メンバー及びスクラムマスター

スクラムマスター

スクラムマスター

アジャイルサムライ

アジャイルな見積もり

ドラッカーのマネジメント

スクラム開発の課題• コニュニケーションコストがとにかく高いので密なコミュニケーションが負担にならない仕組みが必要

• メンバーの要件定義の能力が身につきにくい

• 要件定義という仕事がストーリーの管理として簡略化されている反面、階層化が不十分だったり抜け漏れが発生しやすい

• 要求や要件が深いコンテキスト付きのドキュメントにまとまることが少ないため(ストーリーは短い文書)、ユーザーの要求の理解には個々努力が必要

• 要求が日々変わるのでリッチなドキュメンテーションが変更時に負債になりがち

• 沢山の帽子をかぶれる人ほど多忙になりがち

ニコ生でのスクラムで重視していること

ユーザーの要求理解に対して時間をかける

• ストーリーの漏れや背景にあるユーザー要件の見落としを防止するため

• 具体例:

• ユーザーインタビュー

• twitterのモニター

• 要望掲示板や不具合掲示板の内容の共有

• 超会議へのスタッフでの参加

ストーリーのポイント数を大体2~3日以内で終わる量にしている

• 1スプリントで達成ポイントが0という状況は、個人にショックが大きい

• 無論、個々人のポイント数をおおっぴらに出したり評価することはないけれども...

みんなでお菓子を食べたり、一緒にゲームをやったりするよう心がけている

• 密なコミュニケーションのストレスをできるだけ減らす

• 具体例:

• お菓子神社

• ボードゲーム、ネトゲ

• できるだけIRC (社内チャット) で済ます

• ほんとは誰ともフェイス・トゥ・フェイスのコミュニケーションを取らず開発していたい人もいるので...まだ課題

スクラムマスターが常にプレイングマネージャーであれるようにする

• ストーリーの理解や分割のために技術背景の知識が重要、できるだけ実装機会があるようにする

• 専任スクラムマスターが必要なほどの大きすぎるチームにしない。目安は4~7名。

ゆとりを持った開発計画

• 1スプリントの目標ポイントをギリギリにするとチームが疲弊する

• 余裕のないスケジュールは達成されない

企画と相談するときには、不確定な時期と優先順位と一緒に持ち込む

• 「2~3スプリント内には開発完了するような優先順位付けになっているがどうするか?」というような持ち込み方をするように心がける

• 重要度ではなく常に要件の優先順位で議論する

インフラチームや他チームとの共同作業は合流バッファを考慮する

• ストーリーの合流には問題がつきもの、バッファのないのコラボレーションは極力しない

• 見積もりの際に見落としがちなコミュニケーションコストや要件漏れの発見などのリスクも加味しなくてはいけない

ニコ生でのスクラムで重視していること

1. ユーザー理解に対して時間をかける

2. ストーリーのポイント数を大体2~3日以内で終わる量にしている

3. みんなでお菓子を食べたり、一緒にゲームをやったりするよう心がけている

4. スクラムマスターが常にプレイングマネージャーであれるようにする

5. ゆとりを持った開発計画

6. 企画と相談するときには、不確定な時期と優先順位と一緒に持ち込む

7. インフラチームや他チームとの共同作業は合流バッファを考慮する

まとめ

• Play2/Scalaは大規模Webサイトでも十分通用する

• Scalaは良い言語だがある程度の技術力を要求する

• Play2/Scalaとドメイン駆動設計による開発は相性が良い

• ドメイン駆動設計は保守性に貢献する

• スクラム開発はリーダーを育成するのに良い開発手法

以上ご清聴ありがとうございました

質疑応答