ContractS開発者ブログ

契約マネジメントシステム「ContractS CLM」の開発者ブログです。株式会社HolmesはContractS株式会社に社名変更しました。

課題解決のためにスキルマップ(星取表)を作成

はじめまして、Holmesでスクラムマスターの役割を担っているid:tomoya_misudaです。

今回は、スキルマップ(星取表)を実施したことをお伝えしたいと考えます。

組織が抱えていた課題

企業、組織としての成長過程で、組織を構成するメンバーには一定の流動性が伴います。
こうした中で、現時点で組織として足りないスキルが何か、について考えたことがきっかけでした。

  • チームで足りていない知識ってあるのか?
  • 自分たちに必要なスキルってなんだろう?
  • 他のメンバーのスキル、やりたいことはなんだろう?

問題を深く捉えるため、上記の問を自分自信に投げかけたことで、「メンバーのこと知らなかったな」という感想を持ちました。
特に、普段一緒に仕事をしているチーム以外のメンバーについては、わからないことだらけでした。

解決への模索

市谷さんカイゼンジャーニー、チームジャーニーを読むことで解決方法を探すことにしました。

www.amazon.co.jp

www.amazon.co.jp

チームジャーニーを読んでいる際、「チーム・ジャーニー 著者による本読みの会」に出席しました。市谷さんがスキルマップ(星取表)の良さを共有してくれました。私は「これだ」と感じました。 そこからは、本当に「スキルマップが自身のチームに必要か」を考察しました。

やると決めて、また自問自答

やると決めて、また自問自答で意味、実施方法、確認しました。

  • 「スキルマップをやる意味は?」
    → 課題のスキルの足りない箇所確認よりは、メンバーことを知ることで、コミュニケーションの向上とモチベーション維持が大切と設定しました。また、目標決めに役に立てばと考察しました。

  • 「スキルマップをやることの注意点はあるのか?」
    → メンバーには、素直に記載してもらうことを伝える必要がある。評価利用することもできるが、今回はメンバーの興味や自身が得意なことを見える化しようと決めました。

  • 「どんなスキルがあるだろうか?」
    → Holmesで利用しているスキルをメインで抽出して、Development、Operations、マネイジメント、プロダクトオーナー色々な観点からスキルを一覧化しました。

  • 「メンバー評価のスキル段階は?」
    → 各メンバーの評価軸を合わせることは、やらないことを決めました。結果、各自の自己評価で「◎:好き、◯:一人でできる、△:助けがあればできる、空欄:できない」回答してもらいました。ここは、メンバーに意見を求めて一番工夫しました。

スキルマップを作成

スキルマップを作成して、開発部のメンバーを招待して30分のタイムボックスで実施しました。アジェンダは下記の通りです。

  • やる目的
  • 記載方法
  • 全員で記載
  • まとめ

忙しい中、全員参加してくれました!

結果

メンバー間のコミュニケーションを手助けできました。「ここを得意としているんだ!」「憧れているメンバーのスキルはこんな感じなんだ!」色々な観点を生み出すことができました。また、採用へのスキル確認ができました。

まとめ

問題に対して解決方法は色々ある中で、今自身のチームに必要か?を自問自答することはとても大切で、私たちに合う形を考え実行しました。
スキルは、それぞれのチームで違うので合うかわかりませんが、スキルの項目を下記に記載します。
少しでもチームのコミュニケーションや目標共有の手助けができればと思います。

デザイン     
        UX設計
        UI設計
        User Story Map
        Mindmap
        ビジネスプロセスモデリング
        ワイヤフレーム
        画面設計
        アイコン作成
フロント        
        Storybook
        HTML
        CSS
        SASS
        Vue.js
        Nuxt.js
        Jest
        JavaScript
        JQuery
        Webpack
サーバー        
        Gradle
        Spring Boot
        Java
        Kotlin
        Python
        Groovy
        SQL
考え方       
        Atomic Design
        テスト駆動開発
        ドメイン駆動開発
        オブジェクト指向
        DB設計
        Git-flow
        スクラム
リリース        
    リリース手順検討作成  
        ECS リリース
        Elasticbeanstalk リリース
        Elasticseach リリース
        API Gateway
        RDS リリース
        コードリリース
テスト       
    テスト設計 
        サーバーサイドテスト設計
        フロントテスト設計
        負荷テスト
    テスト自動化  
        テストシナリオ作成
        python selenium
        selenium IDE
インフラ        
    AWS 構築、保守、設計    
        EC2 / ELB
        RDS for mysql
        ECS / ECR
        MQ
        Elasticbeanstalk
        Lambda
        Amazon Connect
        S3
        API Gateway
        Cloudfront
        Elasticsearch
        SES
        Route53
        Lightsail
    gitlab 運用保守 
        パイプライン作成
        unittest runner サーバーチューニング
        unittest Docker Image メンテナンス
    その他   
        apache / tomcat の middleware 構築、保守、設計
サポート・トラブルシュート     
    監視  
        Cloudwatch alarm / Lambda / SNS / Slack による監視構築
        ログ確認
        RDS SQLチューニング
プロダクト     
        仕様理解
        タスク出し
        デザインチェック
        コードレビュー
        バグ(障害)調査対応
その他       
        ファシリテーション
        1on1
        KPI
        PDCA
        セキュリティ
        ISMS

スキルマップを定期更新することで、コミュニケーション、メンバーの成長を実感できるチームを目指していきたいです。

おわりに

この記事で、チームのコミュニケーション、モチベーション維持に貢献できたらと考えます。 Holmesはエンジニア・デザイナーを募集しています 興味がある方はぜひこちらからご連絡ください!

lab.holmescloud.com

lab.holmescloud.com

REST APIのレスポンスステータスについて改めて考える

こんにちは。7月にHolmesにジョインした友野です。サーバーサイドエンジニアをしています。

今回はREST API実装時のHTTPレスポンスステータスについてです。
基本的なことですが、重要なことなので備忘録もかねて記事にしておきます。

余談ですが、Holmesでは秋口のUI/UXリニューアルに向けて開発を進めています。ゴールに向けて、複数のスクラムチームが並行して開発をしているため、各々のチームで経験に基づいた暗黙知による実装差分が稀に発生します。
都度共有もしていますが、エンジニアが増えてきた今だからこそ、「ちゃんと決めなきゃね」という話から今回の内容も改めてまとめておこうと思い立ちました。

利用するHTTPメソッド

GETPOSTPUTDELETE の4つです。リソースの更新はPUTで行い、PATCH は使いません。 べき等性を意識するという側面もありますが、なによりシンプルに実装を進めるためです。

レスポンスステータス

チームで話をするきっかけになったのが、このレスポンスステータスです。
正常時、GET200 OKPOST201 Createdを返すという認識は合っていたのですが、 PUTAPIを実装する際に、

  • 200 OKを返す
  • 204 No Contentを返す

の2つに意見が分かれました。 宗教戦争、とは言いませんが、様々なバックグラウンドを持つメンバーから成るチームだからこそあり得ることです。

定義がどうなっていたか、改めてRFCを確認してみます。

RFC7231 PUT *1

If the target resource does have a current representation and that representation is successfully modified in accordance with the state of the enclosed representation, then the origin server MUST send either a 200 (OK) or a 204 (No Content) response to indicate successful completion of the request.

リクエストが正常に完了したことを200 OK204 No Contentのいずれかで示さねばならないとあるのみでした。 これを踏まえて、開発チームではPUTで既存のリソースを更新する場合は、204 No Contentを返すと取り決めました。

200 OKではなく、204 No Contentを選択した理由は、特に返却する情報はないと考えたためです。 リソースが必要な場合は改めて取得し直すべき、と判断しました。
なお、リソースを作成する場合は明示的にPOSTを利用するので、PUTによるリソース新規作成はしません。

レスポンスヘッダー/レスポンスボディ

さて、そうするとレスポンスヘッダーとレスポンスボディについても明らかにしておいた方が良さそうです。 こちらもRFCを念のため確認した上で、特に意見も分かれなかったので以下としました。

RFC7231 POST
RFC7231 DELETE

  • 201 Createdを返却する場合
    • Locationヘッダーに作成したリソースのURIを合わせて返却する
    • レスポンスボディは不要
  • 204 No Contentを返却する場合
    • レスポンスボディは不要

まとめると、以下の通りです。

HTTPメソッド レスポンスステータス 備考
GET 200 OK 単一/複数リソース取得に利用する
POST 201 Created Location ヘッダーに作成したリソースのURIをセットする
レスポンスボディは不要
200 OK 複雑/組合せの条件などクエリパラメータではリクエスURIの文字数に懸念がある場合*2にリソース取得(検索)時にも POST を利用する
PUT 204 No Content 既存のリソースを更新する
レスポンスボディは不要
DELETE 204 No Content 既存のリソースを削除する
レスポンスボディは不要

Springによる実装イメージ

レスポンスボディを持たない実装イメージは以下の通りです。

  • 201 Created
@PostMapping("/api/tasks")
public ResponseEntity<Void> createTask(@RequestBody CreateTaskCommand command) {
    String taskId = createTaskService.handle(command);
    URI location = UriComponentsBuilder.fromUriString("http://example.com/api/tasks/" + taskId).build().toUri();
    return ResponseEntity.created(location).build();
}
  • 204 No Content
@DeleteMapping("/api/tasks/{taskId}")
public ResponseEntity<Void> deleteTask(@PathVariable("taskId") String taskId) {
    deleteTaskService.deleteWith(taskId);
    return ResponseEntity.noContent().build();
}

最後に

当たり前だと思っていたことが、わずかな思い違いが事故につながることも少なくありません。このような事故を未然に防ぐために、少しずつ明文化していきたいと思います。

Holmesでは現在エンジニアを募集しています。 興味がある方は是非こちらからご連絡ください!

lab.holmescloud.com

*1:当該RFCは執筆日現在で"PROPOSED STANDARD"ステータスです

*2:RFCでは最大文字数は定義されていないようですが、サーバーやブラウザの実装に依存するのを回避するためです

AWS App Mesh 導入のはまりどころとポイント

はじめに

弊社は、SPA化するためにフロントエンドとAPIを分離することにしました。それによってコンテナ間の通信状況とパフォーマンスをより簡単に把握したいニーズが生まれ、監視の高度化・インフラの安定化を目的として、AWS App Meshの導入を検討しました。
AWS App Meshとは、AWS上に存在するコンテナ間の通信を監視し、かつ制御できるサービスメッシュです。
aws.amazon.com 導入にあたって、はまったポイントとその解消方法を記載します。

構成

フロントエンドとAPIを分離した状態での検証をするために、以下の構成でApp Meshの設定を行いました。

  • 1層目 : Frontend Micro Service
  • 2層目 : Bff Micro Service (バックエンドAPI)
  • 3層目 : 各種 Micro Service 群

f:id:k-kitahara:20200805191413p:plain

はまったポイントその1. DBにつながらない

事象

APサーバにHTTPリクエストは到達したのですが、内部でエラーが発生してしまいました。
対象となるECSのコンテナにApp Meshの設定をしたことで、Envoyプロキシを全ての通信が通過するようになりました。 このEnvoyプロキシがTCP通信を全て遮断するため、APサーバからDBにつながらなくなってしまったようです。

解決方法

以下の手順で、Envoyに対してバイパスするポートを指定することで回避しました。

1. Amazon ECSのタスク定義から、「新しいリビジョンの作成」を選択。 f:id:k-kitahara:20200730183901j:plain 2. プロキシ設定から無視された出力ポートに、通信を許可したいポート番号を入力。 f:id:k-kitahara:20200730183910j:plain 3. 画面の一番下にある「作成」を選択。

タスク定義の無視された出力ポートに、2で入力したポート番号が表示されていればOKです。

はまったポイントその2. タイムアウトエラー

事象

ダウンロードやアップロードのような、比較的時間がかかる処理でタイムアウト(upstream timeout)が発生してしまいました。

解決方法

App Meshにデフォルトで設定されているタイムアウト時間は15秒だったため、以下の手順でタイムアウト時間を延長することで回避しました。

仮想ルータ側

  1. AWS App Meshの仮想ルータから、「編集」を選択。 f:id:k-kitahara:20200817172314j:plain
  2. Request timeoutIdle durationを任意の時間に設定。 f:id:k-kitahara:20200730193958j:plain
  3. 画面の一番下にある「保存」を選択。

仮想ノード側

  1. AWS App Meshの仮想ノードから、「編集」を選択。 f:id:k-kitahara:20200817172501j:plain
  2. Request timeoutIdle durationを任意の時間に設定。 f:id:k-kitahara:20200730195625j:plain
  3. 画面の一番下にある「保存」を選択。

仮想ノードと仮想ルータそれぞれの設定にあるRequest timeoutIdle durationの欄に、2で入力した値が表示されていればOKです。

最後に

どのエンドポイントに何秒かかったか、どのトラフィックが一番時間がかかっているかなどといった情報が表として出力されるので、非常にわかりやすくなりました!

クライアントが閲覧しているブラウザやデバイスの統計が出るので、ハイレベルな戦略を立てる場合の一つの材料にすることができます。
また、エンドポイントが呼ばれた回数が把握でき、どの機能がよく使われているのかがわかるようになるので、フィードバック開発の意思決定に役立てることもできます。


Holmesはエンジニア・デザイナーを募集しています
興味がある方はぜひこちらからご連絡ください! lab.holmescloud.com lab.holmescloud.com

Pixelaを使ってデプロイを可視化してみた #pixela

こんにちは、id:c-terashimaです

Pixela とはGitHubの草のように数値を可視化するツールです

pixe.la

弊社はGitLab CIを利用してmasterブランチにソースがマージされると自動的にステージング環境へデプロイが行われます
ちょっとしたことではありますが、ステージング環境へのデプロイを1日どのぐらい行っているのか Pixela を使って数えてみることにしました

手順

ユーザ登録

Pixelaにアカウントを作成します

$ echo `uuidgen`
$ dummy-uuid
$ curl -X POST https://pixe.la/v1/users -d '{"token":"dummy-uuid", "username":"holmes", "agreeTermsOfService":"yes", "notMinor":"yes"}'

Request Body

Key description
agreeTermsOfService 利用同意
notMinor 未成年確認

グラフ作成

デプロイ回数を描画するグラフを作成します

$ curl -X POST https://pixe.la/v1/users/holmes/graphs -H 'X-USER-TOKEN:dummy-uuid' -d '{"id":"staging","name":"deployCounter","unit":"deploy","type":"int","color":"shibafu"}'

Request Body

Key description
unit 単位
type 数量の種類(int or float)
color 芝生の種類 (shibafu (green), momiji (red), sora (blue), ichou (yellow), ajisai (purple) and kuro (black))

カウントアップ

以下のAPI呼び出しをCIに組み込むことで自動的にカウントアップが行われます

$ curl -X PUT https://pixe.la/v1/users/holmes/graphs/production/increment -H 'X-USER-TOKEN:dummy-uuid' -H 'Content-Length:0'

実際にできたやつ

f:id:c-terashima:20200720173604p:plain

あ、だれか土日にデプロイしてる!!
なんてことも可視化され、休日にもデプロイされていることがわかりましたw
今回はステージングのみですが、本番環境も可視化してセールスチームなどに共有してあげればいつリリースしたか振り返ることにも利用できそうですね

最後に

とっても容易に可視化することができました
芝生が見慣れているというのもあるかもですが視覚的に見やすくわかりやすいですね
デプロイだけではなく定量的に取得している数値などがあれば Pixela で可視化してみてはどうでしょうか?


Holmesはエンジニア・デザイナーを募集しています
興味がある方はぜひこちらからご連絡ください!

lab.holmescloud.com

lab.holmescloud.com

スクラムチームに新しい仲間が入るので、ドラッカー風エクササイズによるチーム期待値調整ワークをやってみた

こんにちは。Holmesでスクラムマスターをしている吾郷です。

今回はチームビルディングの一環として行ったドラッカー風エクササイズについて振り返っていきたいと思います。

前提・状況

現在弊社では、毎月会社に数名の新しい仲間が入ってくれています。嬉しいことに、半年一緒にやってきたスクラムチーム(開発メンバー4人)にも、7月から新しい仲間が1名加わってくれることになりました。既存のメンバーはすごく楽しみにしています。

また、タックマンモデルを参考として、チームの成長段階から考えてみましょう。

f:id:seiseiholmes:20200717112638j:plain
仕事ができる人は「正しい衝突」が超得意!から抜粋

半年ほど活動している自チームの段階は混乱期〜統一期といったところでしょうか。チームメンバーが変更になることで、再度形成期を過ごしながらチームを作り上げる必要があります。

目的

そこで、半年やってきたチームの練度をできるだけ保ちながら、チームにおける不安や緊張を緩和し、スムーズに新しい仲間の受け入れを行いたいです。 また、形成期を迎えるチームにおいてお互いのスキルや価値観、期待値を共有・すり合わせを行うことで、今後のチーム活動の基盤とし、成長への後押しとしたいです。

ドラッカー風エクササイズとは

ドラッカー風エクササイズは、アジャイルサムライで紹介されたチームにおける期待をすり合わせるための手法です 具体的には、以下の4つの質問に答えます。
①自分は何が得意なのか?
②自分はどうやって貢献するか?
③自分が大切に思う価値は?
④チームメンバが自分に期待していることは?

また、今回はカイゼン・ジャーニーを参考として5つ目の質問を追加することにしました。質問は以下です。
⑤その期待はあっているか?
カイゼン・ジャーニーで言及されていますが、最初の4つの質問は自分に視点を置いていましたが、5つ目の質問ではメンバーからの回答となります。メンバーからの回答はよりよいフィードバックとして、自信の期待値の調整ができます。

今回は期待値の共有にとどまらず、期待値のすり合わせまで行いたいと考え、質問を追加しました。注意としては、期待値の差異の受け取り方は人によってはマイナスにとらえてしまう場合もあるかと思います。今回は差異がわかることは良いことで、すり合わせのきっかけになることが大事である旨をワークの冒頭で丁寧に説明しました。

実施方法

今回は、リモートワーク状況ということで、オンラインホワイトボードのMiroを使用して行いました。 タイムボックスは一時間とします。

流れは下記です。

  1. チェックイン(実施日がたまたま7月7日でしたので七夕を題材にして行いました。)
  2. 4つの質問にそれぞれ回答2分ずつで回答を入力(質問×2分)
  3. 各メンバーに補足してもらいながら記入内容を共有(メンバー×2分)
  4. 各メンバーの記入内容について質問・深堀り(10分)
  5. 各メンバーの最後の質問に対して、自分の期待とあっているかどうか三段階で評価(3分)
  6. 評価結果についてすり合わせ(残り時間)

また、ワーキングアグリーメントとして以下を設定し最初に共有しました。

・メンバーを絶対に否定しない(否定はしないけど、意見はありです。)

実施結果

以下のような表が出来上がりました。

f:id:seiseiholmes:20200717122631p:plain
結果表
横列が質問、縦列をメンバーとしています。
また、期待の合致度を表す星のカラー定義は以下としています。

  • 青色・・・いい感じで合致している
  • 黃色・・・まあまあ合致している
  • 赤色・・・ちょっと違う

途中で出てきたコメントに関してはオレンジや濃い黄色、青色の付箋で追加しております。
また、画像を見ると気づくかもしれませんが、期待の合致度を表す星についてはメンバーによって青が多めの方、黄色が多めの方とそれぞれ違いが見えて面白かったです。

チームメンバーの感想(一部抜粋)

KEEP
・普通に楽しい会だった
・スプリントでの良い息抜きになった
・メンバー間のコミュニケーションが更に活発になった気がする
・新しく入ったメンバーが他メンバーを把握するのに良いと思いました
・自分自身の見直しが出来た

TRY
・POも声をかけるべき
・コンパクトバージョンでもいいのでQ毎にやってもよいと思いました
・発表者毎に質疑するともっと良さそう

このように嬉しい声がありました。また、カイゼン案などの意見もくれたので、参考にしながら次回以降のTRYとして実践していこうと思います。

発表者毎に質疑するともっと良さそう

こちらに関しては、各メンバーの記入内容についての時間をまとめて取ってしまったため、前の時間で最初の方に共有してくれた方の内容を思い出しにくく深堀りづらいという状況が生まれたためです。一人ひとりに焦点を当て、チームで共通認識を持てる方が良いと思いますので、要カイゼンとしました。

POも声をかけるべき

また、こちらに関してですが、スクラムを知っている方はご存知の通りスクラムチームは、PO・SM・開発チームで構成されています。しかし、最近スクラムイベントはまだしも、こういったチームビルディングイベントになるとPOの招待を忘れがちになる現状があります。非常に痛いです。POもよく嘆いております。そこで、チームメンバーとPOの架け橋となるべきSMが、POのことを忘れないようにPOの写真を机の横に置いておくTRYをやってみようかなと思った次第です。

更に、今回もう一つのポイントであった新しい仲間からの感想も抜粋させていただきます。

・メンバーの個性(落ち着きと秩序と盛り上げ)や、フロントとバックエンドのバランスがいいなと思いました。
・みんなが謙虚で、承認し合う雰囲気は素晴らしいです。
・自分が考える期待されていることと、メンバーから期待されていることが近しく、やりたいことが受け入れられており、チームで動く土台にりました。

ということで、なんかいい感じですねΣ(゚∀゚ノ)ノキャー 実施してよかったです!

感想

前提として、スクラムにおいてチームメンバーが変更されることはあまり良しとされておりません。しかし、新しい仲間が加わることによる新しい風(経験、知識、観点)はチームにとって新鮮さを保ち続けるいい要素になります。また、新しいチームを作り上げていくという課題を乗り越えることで一層チームとして成長できるのではないかと考えております。とはいえ、頻繁な変更(ここでいう頻繁の定義はチームや会社の状況によります)を行うことによるチームへの負荷というもの存在しますので、状況を判断してチームの変更可否を決めていくのがいいかと思います。

また、今回のワークで加筆すべき良かった点としては、メンバー同士の期待値のすり合わせができたというのはもちろんのこと、スクラムマスターとしてチームと接していた自分自身への期待値・評価のすり合わせも行うことができたということです。これまでは見えにくかったチームから自分への評価を聞けたことは、今後の活動を行う上でも大きな後押しとなると思います。

チームビルディングに迷っている方、スクラムマスターとしての成果に不安がある方は是非おすすめします!

最後に

Holmesはエンジニア・デザイナーを募集しています
興味がある方はぜひこちらからご連絡ください!

lab.holmescloud.com

lab.holmescloud.com

AWSでWebSocketのネットワーク構成を考えてみる

Holmesでエンジニアをしているid:w-miuchiです。

先日弊社のサービスでリアルタイム通知の構築がトピックに上がりました。
リアルタイム通知の手段としてWebSocketに着目し、そのネットワーク構成を考えてみました。

今回はその考えた中から構成案をいくつかピックアップして紹介します。

条件

考える上で、以下を条件としました。

  1. データベース(Amazon RDS)の更新をきっかけにし、エンドユーザーに通知を行う
  2. 負荷分散を可能とする
  3. 仮定としてWebSocketコネクション情報はAmazon ElastiCache for Redisを利用

想定されるトラフィック量の算出も必要とはなりますが、今回は現在のHolmesのサービスと同じ量と仮定します。

注意

本内容は構成案であり、検証や実装は行っておりません。
構成内容は弊社SREに確認済みです。

利用サービス

今回の条件で利用する(できる)サービスを洗い出したいと思います。

Routing Application DataStore Cache
ELB (ALB, CLB)
API Gateway
ECS
EC2
Lambda
RDS
DynamoDB
Redis (ElastiCache)

こちらを元に組み合わせて構成を考えます。

構成案

1. ALB + ECS + RDS + ElastiCache for Redis

f:id:w-miuchi:20200703173730p:plain
構成1

ALB(Application Load Balancer)を利用する方法です。

ALBはパスベースルーティングが可能なため、例えば/websocketというパスで切り分けることができます。
またサブドメイン(例:websocket.xxxxxx.com)で切り分けるのであればCLB(Classic Load Balancer)でも可能です。

上記ではECS(Amazon Elastic Container Service)ですが、EC2でも問題はありません。WebSocket専用のサーバーを用意するのも有効かと思います。

処理としてはサーバーでElastiCacheのRedisにconnectionを保存しハンドシェイクします。
サーバーはデータベースにポーリングし更新通知を受け取ります。
ElastiCacheのRedisに保管されたSocketのconnectionからエンドユーザーに通知します。

非常にシンプルな構成で、弊社サービスではすでにALBを利用しているため比較的に導入しやすいです。 正直なところ弊社サービスを考えるとこの構成がベストプラクティスと考えます(笑)

メリット

  • 現サービスと同じ構成のため導入しやすい
  • コスト計算が行いやすい

デメリット

  • ポーリングによってRDSの負荷がボトルネックになる可能性があり(Socketで利用するサーバ台数を制限するなど検証が必要)

2. ALB + Lambda + DynamoDB + ElastiCache for Redis

f:id:w-miuchi:20200703173734p:plain
構成2

構成1との違いは2点です。

1点目はALBのターゲット先をECSからAWS Lambdaにしています。
WebSocketの通知だけであれば処理は軽量でAWS Lambdaでも可能かと考えます。

2点目はデータベースの更新をAmazon DynamoDBにし、Lambdaでイベントを受け取っている点です。

Lambdaを利用するためサーバーの負荷分散を任せることが可能です。

メリット

  • Lambdaというマネージドサービスを利用するため負荷分散が容易

デメリット

  • DynamoDB, Lambdaのコスト計算が必要

3. API Gateway + Lambda + DynamoDB + ElastiCache for Redis

f:id:w-miuchi:20200703173738p:plain
構成3

構成2との違いはALBをAmazon API Gatewayに変えています。

ステートフルなフロントエンドとして、WebSocket API を作成できます。

API Gatewayペイロードサイズやリクエスト数等の制限をかけたいならこちらがおすすめです。
またWebsocketをServerlessのサービスとして独立させることが可能です。

メリット

  • Lambdaに加えAPI Gatewayというマネージドサービスを利用するため負荷分散が容易
  • API Gatewayの制限が活用できる

デメリット

  • API Gateway, Lambda, DynamoDBのコスト計算が必要

4. API Gateway + Lambda + RDS for PostgreSQL(RDS Proxy)

f:id:w-miuchi:20200703173741p:plain
構成4

こちらはAmazon RDSにPostgreSQLを利用した場合です。

PostgreSQLでは通知を受け取る機能(NOTIFY/LISTEN)があり、こちらを利用します。 接続にはAmazon RDS Proxyを利用します。
RDS ProxyはLambdaを同じVPC内に配置することでデータベースとの接続をプールすることが可能です。 Lambdaが起動するたびに発生していたデータベースとの接続を緩和します。

ただし、最大のネックがRDS Proxyがプレビューであること...と本記事を書いている時にRDS ProxyがGAになりました!

https://aws.amazon.com/jp/blogs/aws/amazon-rds-proxy-now-generally-available aws.amazon.com

GAになったばかりのためこちらはかなり検証が必要です。

メリット

  • マネージメントサービスにおける負荷分散が利用できる
  • RDBPostgreSQLの場合はそのまま使うことができる

デメリット

  • RDS ProxyがGAになったばかりで、コストもかかる

最後に

いかがだったでしょうか。

RDS Proxyを利用した構成はかなりチャレンジングですが、個人的興味で加えさせていただきました!
今後は実際に構築し検証も行ってみたいと考えています。 その結果はまた記したいと思います。

Websocketの導入検討をしている方の一助になりましたら幸いです。


Holmesはエンジニア・デザイナーを募集しています
興味がある方はぜひこちらからご連絡ください!

lab.holmescloud.com

lab.holmescloud.com

Spring Bootのgradle bootRunによる起動を高速化してみる

Holmesでエンジニアをしている山本です

Holmesでは、サーバーサイドアプリケーションをGradle管理のSpring Bootで実装しています。現在、ローカル環境での gradle bootRun によるSpring Bootアプリケーションの起動まで、数十秒かかっているため、多少なりとも短縮できないかと思い、調査を行いました。

参考としたのは、以下のページです。

bufferings.hatenablog.com

起動時間としては、利用可能メモリが4GBほどある状態で5回 gradle bootRun を実行し、起動ログに表示される Started Application in ... seconds の秒数を平均したものを使用します。

作業前

Started Application in 39.092 seconds
Started Application in 35.281 seconds
Started Application in 39.612 seconds
Started Application in 39.754 seconds
Started Application in 47.941 seconds # 外れ値
Started Application in 38.136 seconds

5回目はそれ以外の値と10秒ほど乖離があるため、外れ値として除外します。それ以外の平均は、 38.375 となりました。これを基準としていきます。

作業方針

実行環境がAmazon Corretto 8 64bit + Spring Boot v2.1のため、それらのバージョンで利用できるもの、かつ、本番など既存の環境には影響せず、ローカル環境での実行に閉じた方法を選択します。

1. Gradle起動オプションの調整

まずはGradleの起動オプションを調整します。

対象となるGradleプロジェクトの gradle.properties は、以下の通りです。

org.gradle.jvmargs=-Xmx2048M
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true

デーモンが有効となっています。Gradleの起動オプションの設定は、デーモンが無効な場合は GRADLE_OPTS 、有効な場合は org.gradle.jvmargs で指定します。

ローカル環境にのみ設定を反映させたいので、グローバル設定ファイルの ~/.gradle/gradle.properties を作成し、以下のように記述します。

org.gradle.jvmargs=-Xms2g -Xmx2g -XX:TieredStopAtLevel=1 -noverify

この状態で実行した結果が、以下になります。

Started Application in 36.176 seconds
Started Application in 34.452 seconds
Started Application in 37.581 seconds
Started Application in 39.238 seconds
Started Application in 37.324 seconds

平均は 36.954 となりました。1.4秒程度、3.7%の改善です。

オプションはそれぞれ、以下の意味を持ちます。

Xms, Xmx

メモリ割り当てプール(Javaヒープサイズ)の最小値および最大値です。同じ値とすることで、実行中のメモリ再割り当てを回避します。

XX:TieredStopAtLevel

XX:+TieredCompilation にて階層型コンパイルが有効化されている場合に、JITコンパイラを指定します。

64bit Javaの場合、階層型コンパイルはデフォルトで有効化されます。

  • 0: インタプリタ
  • 1~3: C1。それぞれプロファイル利用の有無などが異なる
  • 4: C2

C1がHotSpot VMのClient VM、C2がHotSpot VMのServer VMに相当します。

以下のページに詳しいです。

Oracle JDK8 JavaVMオプション - ソフトウェアエンジニアリング - Torutk

noverify

バイトコードの検証なしに、classのロードを有効化します。

2. コンポーネントのインデックススキャンの有効化

Spring Framework 5より追加された、コンポーネントのインデックススキャンを有効化します。詳細は以下に詳しいです。

qiita.com

build.gradledependencies に、 annotationProcessor "org.springframework:spring-context-indexer:${SPRING_VERSION}" を追加してビルドすると、クラスファイル出力先のMETA-INF配下に、spring.components というテキストファイルが生成されます。

このファイルが存在すると、起動時にクラスパスをスキャンしてDIコンテナに登録するのではなく、ファイルを読んでDIコンテナに追加するという挙動になります。

ファイルの内容は、 コンテナ管理Beanの完全修飾クラス名=付与されているアノテーション となります。検証時点で、662クラスが出力されていました。

この機能について検索すると、大して変わらない、かえって遅くなった、という情報が散見されます。これだけのクラス数ではどうなるでしょうか...

Started Application in 35.902 seconds
Started Application in 39.535 seconds
Started Application in 41.434 seconds
Started Application in 40.117 seconds
Started Application in 39.228 seconds

平均は 39.243 となりました。 36.954 と比べると約2.3秒、6%ほど遅くなっています。

また、Mavenであれば <optional>true</optional> を設定しておくことで、 spring.components をWARやJARから除外することができますが、Gradleではoptionalに該当するオプションがないため、プロパティ spring.index.ignore=true を設定するなどの作業が別途必要となります。

改善効果が薄そうなため、インデックススキャンについては除外することとしました。

3. コンテナ管理Beanの遅延初期化

コンテナ管理Beanは、デフォルトではアプリケーション起動時にすべてDIコンテナに登録されます。遅延初期化を有効化することで、コンテナ登録のタイミングをBeanの初回利用時に変更できます。

Spring Boot v2.2.0 からは、プロパティとして spring.main.lazy-initialization=true を指定することで、一律で遅延初期化が適応されますが、v2.1では使えないため、以下のクラスを追加します。

@Configuration
@Profile("local")
@Slf4j
public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        log.debug("bean lazy load setting start.");

        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            beanFactory.getBeanDefinition(beanName).setLazyInit(true);
        }

        log.debug("bean lazy load setting end.");
    }

}

@Profile("local") を付与しているのは、本番環境などに影響を与えないためです。

GradleでSpring Bootのプロファイル名を指定するには、環境変数 SPRING_PROFILES_ACTIVE を設定するか、Java起動オプションの -Dspring.profiles.active を設定します。

export SPRING_PROFILES_ACTIVE=local してから、bootRunを実行します。

Started Application in 29.95 seconds
Started Application in 28.846 seconds
Started Application in 30.67 seconds
Started Application in 30.493 seconds
Started Application in 29.685 seconds

平均は 29.929 となりました。 36.954 から約6秒、19%の短縮です! これまでに比べ、大きく短縮できました。

その他の改善策

Thin Launcher化 + AppCDSを行えば効果がありそうです。

また、bootRunによる起動であれば、JARやWARを生成しないため、AppCDSの効果があるかもしれないですが、OpenJDKベースのJavaではv10以降でないとAppCDSが使えません。

nowokay.hatenablog.com

試しに、 org.gradle.jvmargs-XX:+UseAppCDS を追加してみましたが、 Unrecognized VM option 'UseAppCDS' で起動に失敗しました。

もっとシンプルな高速化の方法としては、Java8からはJavaヒープからPermanent領域が廃止され、ネイティブメモリ上にMetaspace領域が取られるようになったため、空きメモリを確保しておくことも重要です。空きメモリが少ない状態だと、倍近く遅くなることもありました。

Javaのメモリ管理については、以下のページが詳しいです。

equj65.net

奇跡の一枚

Webブラウザ、ビデオ会議アプリ、IDE、エディタ、その他もろもろを終了し、空きメモリを12GB程度にした状態では、20秒で起動できました。

f:id:h-yamamoto_holmescloud:20200618183045p:plain

振り返り

Gradle起動オプションの調整と、コンテナ管理Beanの遅延初期化で、 38.375 から 29.929 と約8.4秒、22%の起動時間短縮を行えました。

コンテナ管理Beanが662件と多いため、インデックススキャンの有効化と遅延初期化はそれぞれ効果があるのでは推測していましたが、遅延初期化は予想通り大きな時間短縮につながった一方、インデックススキャンはかえって遅くなってしまいました。

また、改善できたとはいえ、まだまだ30秒程度かかってしまいます。Spring Bootの場合、起動時間は @SpringBootTest などを使用したテストの実行時間にも関係するので、もう少し短縮したいところです。

最後に

Holmesではエンジニア・デザイナーを募集しております。ご興味がある方はこちらからご連絡ください。

lab.holmescloud.com

lab.holmescloud.com