Slide 1

Slide 1 text

Goを活用した サービス開発・運用の話 DMM.go #3 DMM.com 佐々木勝春 2021-07-21

Slide 2

Slide 2 text

自己紹介 名前:佐々木勝春 略歴: 2017年4月 一般社団法人DMMアカデミーに入社 2019年4月 DMM.comに転籍 | プラットフォーム事業部 2021年7月現在 レビューリプレイス、通知配信&DMMポイントクラブ 余暇:落語(立川流)、映画、ラジオ、 …、 カカオ72% 2

Slide 3

Slide 3 text

今回お話する内容 1:monorepo&共通アーキテクチャによる複数サービス開発の話 2:安定的なサービス運用のための継続的なモニタリング (現在も模索しながら改善中です!) 3

Slide 4

Slide 4 text

現在運用中のサービスについて PFでありながらToC要素のあるサービスを作っているのが特徴的です 4

Slide 5

Slide 5 text

運用中のサービス1: DMMポイントクラブ https://lp.pointclub.dmm.com/ 5

Slide 6

Slide 6 text

運用中のサービス2: 通知配信サービス https://notification.dmm.com/ ↑共通ヘッダーのベルアイコン 6

Slide 7

Slide 7 text

開発業務内容 ・サービスの開発・運用(PRD/DesignDocの作成) ・施策の立案、実施&検証&振り返り   ・定量・定性分析(データ、ユーザーの声、SNS、...) ・外部チームとの連携  ... 「事業のための開発」 引用: https://www.irasutoya.com/2020/03/blog-post_74.html 7

Slide 8

Slide 8 text

効率的なサービス運営を行っていくために ・開発の工数をできるだけ削減して効率よく開発していきたい ・モニタリングをしっかりと行って障害の防止、障害発生後の素早いリカバリーを実現し たい。 ・インフラリソースの最適化も継続的見ていきたい。 8

Slide 9

Slide 9 text

今回お話する内容 1:monorepo&共通アーキテクチャによる複数サービス開発の話 2:安定的なサービス運用のための継続的なモニタリング 9

Slide 10

Slide 10 text

インフラアーキテクチャ 10

Slide 11

Slide 11 text

11

Slide 12

Slide 12 text

ポイントクラブが提供する機能群概要 ・Client(Nativeアプリ)への機能提供→API ・管理系(お知らせ登録・Push通知の配信)→バッチ データ ストア API バッチ 参照・登録 登録 Native アプリ 管理 追加実装部分 12

Slide 13

Slide 13 text

通知配信が提供する機能群概要 ・Client(通知一覧・通知のステータス取得)→API ・ユーザーセグメント毎の通知情報の登録→バッチ データ ストア API バッチ 参照 登録 Web Front 管理 追加実装部分 管理機能はサービスと同じリソー スを扱って一部の処理を共通し て使える ↑ 共通化部分はできれば再 利用したい 13

Slide 14

Slide 14 text

monorepoを利用した既存リソースの活用 https://speakerdeck.com/ymatsuwitter/cloud-native-and-monorepo?slide=20 今回は主にこの観点から → 14

Slide 15

Slide 15 text

+α: 既存インフラ環境の有効活用 ・バッチもAPIと同じネットワーク ・ECS Fargateの構成で構築する ことでインフラ構築工数削減(cliコ マンドとして実装する方針に) (これに加えて短時間の定期実行の処理は Lambdaで実 行してるものもあります ) 15

Slide 16

Slide 16 text

ソースコードアーキテクチャ 16

Slide 17

Slide 17 text

コードアーキテクチャ概要 ui http query usecase infra domain query: ・外部API依存のもの ・ui層用の表示用(CQRSを参考に参照用モデルとしてドメインオブジェクトとは別に定義 ) ポイントクラブと通知配信の両サービスで利用 →アーキテクチャを共通化することで学習コストの削減、仕様さえ把握してい ればすぐに開発に着手できる cli ・関心の分離→ドメインロジックに集中 ・goではinterfaceと構造体を用いたDIの実装 17

Slide 18

Slide 18 text

ディレクトリ構成 ・main関数のファイルを cmd/配下の/apiと/batchで分離 ・app/ui層以下が/batch/とhttp/で分離 ・usecase層以降はapiとbatchで同じディレクトリ apiとbatchで共通のメソッド(主にinfra層)を利用できる 18

Slide 19

Slide 19 text

バッチ実装に利用しているライブラリ https://github.com/spf13/cobra 特徴 ・サブコマンド型のCLIツールの作成 ・POSIX準拠のフラグ機能を提供(long型とshort型共に) ・アプリケーションの雛形を作るための機能も提供 ・多段のネストのサブコマンドを作れる ・helpフラグやmapページの自動生成と柔軟なカスタマイズ性 ... 詳細は公式リポジトリを参考ください 19

Slide 20

Slide 20 text

cobraの選定理由 ・標準ライブラリ(flagパッケージなど)だけでcliを作ることもできるが、なるべく実装の工 数を減らしたい ・cobraはcliのサブコマンド型を作りたいという今回の要件にマッチ   ・今回は利用ケースが限定的→どのフレームワークを使っても実現できそう  では あった(豊富な機能や採用実績からcobraを作用) 20

Slide 21

Slide 21 text

app/cmd/batch/main.go package main func main() { ... prepareNotificationCmd := batch.NewPreparePushCmd(pushHandler) batch.RootCmd.AddCommand(prepareNotificationCmd) if err := batch.RootCmd.ExecuteContext(ctx); err != nil { logger.Write(ctx, logger.LevelError, "エラーが発生しました。", err) os.Exit(1) } os.Exit(0) } main関数内でルートコマンドにサブコマンドを追加 & エラーハンドリング(エラー状況に応じてステータスコードを返却 ) 21

Slide 22

Slide 22 text

app/ui/batch/root.go package batch import ( "github.com/spf13/cobra" ) // RootCmd : ルートコマンド var RootCmd = &cobra.Command{ Use: "pointclub-batch", Run: func(cmd *cobra.Command, args []string) { _ = cmd.Help() }, } main関数で利用するrootコマンドを初期化 22

Slide 23

Slide 23 text

app/ui/batch/notification.go① package batch // Notification : お知らせ通知のためのハンドラー type Notification interface { Prepare(cmd *cobra.Command, args []string) error } type notification struct { u usecase.Notification } // NewNotification : お知らせ通知ハンドラーの生成 func NewNotification(u usecase.Notification) Notification { return &notification{ u: u, } } cliのhandlerのインターフェースと new関数を定義(apiと同じ構成) ↑サブコマンドで実際に呼び出す関数 23

Slide 24

Slide 24 text

app/ui/batch/notification.go② package batch // NewPrepareNotificationCmd : お知らせ通知内容を用意するコマンド func NewPrepareNotificationCmd(n Notification) *cobra.Command { return &cobra.Command{ Use: "prepare-notification", Args: cobra.ExactArgs(1), Short: "お知らせ通知内容を用意(dynamoDBにインサート)する", RunE: func(cmd *cobra.Command, args []string) error { return n.Prepare(cmd, args) }, } } ・サブコマンドを初期化 ・この後紹介するhandlerのメソッドをここでコール 24

Slide 25

Slide 25 text

app/ui/batch/notification.go③ package batch // NewPrepareNotificationCmd : お知らせ通知内容を用意するコマンド func NewPrepareNotificationCmd(n Notification) *cobra.Command { ... } // Prepare : お知らせ通知内容を用意 func (n notification) Prepare(cmd *cobra.Command, args []string) error { targetUser := args[0] err := n.u.Prepare(cmd.Context(), targetUser) if err != nil { return fmt.Errorf("お知らせ通知内容の用意に失敗しました。: %w", err) } return nil } ・handlerのメソッドを定義 ・ここでusecase層以降のメソッドをコール ←apiと同じ構成 25

Slide 26

Slide 26 text

その他、API実装に関して フレームワーク 選定理由 ポイントクラブ Goa ドキュメントとGoを乖離させないため https://speakerdeck.com/yyh_gl/develop-api-server-by-goa 通知配信 echo ・レビューリプレイスの際に利用経験あり ・薄いフレームワークが良かった ・ドキュメントに関しては今回はクライアントはほぼ自分達だけ だったので問題なかった 参考:技術選定に関して https://inside.dmm.com/entry/2019/02/28/technology-selection 26

Slide 27

Slide 27 text

まとめ1 monorepo&共通アーキテクチャによる開発の効率化 ・monorepoによるメソッドの共通利用により開発工数の削減 ・複数サービスで同一アーキテクチャを採用することによる学習コスト・開発コストの削減 +インフラ構成の共通化やフレームワークやライブラリの利用による開発工数の削減 27

Slide 28

Slide 28 text

今回お話する内容 1:モノレポ&共通アーキテクチャによる開発の話 2:安定的なサービス運用のための継続的なモニタリング 28

Slide 29

Slide 29 text

2:安定的なサービス運用のための継続的なモニタリング ・安定的なサービス運用をしていきたい ・初期MVPも後も継続的に施策の実施、機能追加のリリースをしていきたい ・アクセス増減によるサーバー、データストアのスペックの見直し 29

Slide 30

Slide 30 text

モニタリング導入による効果例 ・実際のに負荷とリソースの使用状況に応じて APIのコンテナ数削減によりインフラ費用のコスト 最適化 ・メモリリークの検知 30

Slide 31

Slide 31 text

モニタリングにSaaSのDatadogを利用 Datadog のインテグレーションを使用して、 DevOps スタック全体のメトリクスとイ ベントをシームレスに集約します。 ・インテグレーション ・ダッシュボード ・ログ管理/アラート ・APM ・Continuous Profiler ・ネットワークモニタリング ・Syntheticモニタリング ・リアルユーザーモニタリング ... https://www.datadoghq.com/ja/ DMMPFの標準のモニタリングツールとしても Datadogを採用しています 31

Slide 32

Slide 32 text

Datadog ・様々な言語に対応 C#,Go, Java,JavaScript, Node, PHP, Python, Ruby, Rust… (利用機能により対応言語は変わります ) ・充実したドキュメント ・頻繁な機能追加・アップデート https://www.datadoghq.com/ja/blog/ https://docs.datadoghq.com/ja/ 32

Slide 33

Slide 33 text

Datadogのモニタリング活用例(go関連) ・APM ・継続的なProfiling 33

Slide 34

Slide 34 text

Datadogのモニタリング活用例(go関連) ・APM ・継続的なProfiling 34

Slide 35

Slide 35 text

APM ↓タグで絞り込み検索ができる コードレベルの可視性で根本原因を素早く特定 35 https://docs.datadoghq.com/ja/tracing/visualization/

Slide 36

Slide 36 text

APM実装コード① main関数内でAPMトレース開始と終了の処理をコール package main func main() { // APM トレース開始 apm.StartTrace() defer apm.StopTrace() ... } 36

Slide 37

Slide 37 text

APM実装コード② package apm // StartTrace : トレースを開始 func StartTrace() { tracer.Start( tracer.WithServiceName("pointclub-api"), tracer.WithGlobalTag("service", "pointclub-api"), tracer.WithGlobalTag("env", os.Getenv("APP_ENV")), ) } // StopTrace : トレースを終了 func StopTrace() { tracer.Stop() } 37

Slide 38

Slide 38 text

APM実装コード③ トレースしたい任意の関数でspanの開始処理をコール package foo func Foo(ctx context.Context, hoge Hoge) { span, ctx := apm.StartTraceFromContext(ctx) defer span.Finish() ... } 38

Slide 39

Slide 39 text

APM実装コード④ span開始の関数 package apm // Span : スパン type Span tracer.Span // StartTraceFromContext : スパンを開始 func StartTraceFromContext(ctx context.Context) (Span, context.Context) { return tracer.StartSpanFromContext(ctx, getFuncName(2)) } 39

Slide 40

Slide 40 text

Datadogのモニタリング活用例(go関連) ・APM ・継続的なProfiling 40

Slide 41

Slide 41 text

・最小限の負荷で本番環境全体のパフォーマンスをコードレベルで分析 →コードのボトルネックを素早く見つける ・全てのスタックトレースを1つの管理画面で可視化する ・リリースversion間の比較による継続的なパフォーマンス傾向の分析 継続的なProfiling 41

Slide 42

Slide 42 text

goの標準ライブラリ pprofとの違い ・リリースバージョンごとの比較が標準できる ・分散トレースとの相関関係を理解する ・httpサーバーにエンドポイントを差し込まなくていいので実装が楽(特にフレームワーク利用時) https://golang.org/pkg/net/http/pprof/ その他クラウドインフラ系サービスも提供 https://cloud.google.com/profiler/docs/about-profiler/?hl=ja&refresh=1 https://aws.amazon.com/jp/blogs/news/investigating-performance-issues-with-amazon-cod eguru-profiler/ 42

Slide 43

Slide 43 text

継続的なProfiling cmd/batch/main.go (import文省略) package main func main() { // profilerを開始 err := profiler.Start( profiler.WithService("pointclub-api"), profiler.WithEnv(os.Getenv("APP_ENV")), profiler.WithProfileTypes( profiler.CPUProfile, profiler.HeapProfile, profiler.GoroutineProfile, ), ) if err != nil { logger.Panic("profilerの開始に失敗しました。 ", err) } defer profiler.Stop() } 参考: https://docs.datadoghq.com/ja/tracing/profiler/ enabling/?code-lang=go 43

Slide 44

Slide 44 text

継続的なProfiling https://docs.datadoghq.com/ja/tracing/profiler/intro_to_profiling/ 44

Slide 45

Slide 45 text

継続的なProfiling 項目 データ型 実装レベル 利用用途 メトリクス 時系列データ 利用者側で実装 (メソッド単位、より大きい 単位) ・利用者側で決めた撮り たいデータを取得できる ・前後関係は分からない プロファイル 統計データ・スナップ ショット的 ランタイムレベル (スタックトレースによる 粒度の小さい単位) ・取れるメトリクスは固定 (CPU使用時間、ヒープ サイズ、スレッド数) ・前後関係がわかるので パフォーマンスのボトル ネックを特定しやすい 45 https://logmi.jp/tech/articles/322787

Slide 46

Slide 46 text

その他Datadogのモニタリング活用例 ・インテグレーション&ダッシュボード整備 インフラストラクチャーから全てのメトリクスとログを収集して、統合システムを全体として 把握することができる。 https://docs.datadoghq.com/ja/getting_started/dashboar ds/ 46

Slide 47

Slide 47 text

まとめ ・継続的なサービス運用のためにモニタリングにDatadogを活用 ・データストアのパフォーマンス問題の改善 ・APIサーバーのパフォーマンスの改善は引き続きやっていきたい 47

Slide 48

Slide 48 text

・monorepo&共通アーキテクチャにより共通化の仕組みを活用した開発工数の削減を 解決 ・継続的なモニタリングで安定したサービスの運用の実施 ・失敗して学びながら徐々に改善していってより良いサービスを提供していきたいです まとめ 48