キムのテックブログ

来世つよつよソフトウェアエンジニア志望の現世ぱんぴー大学生です

voyage groupのsunriseに参加してインフラ修行をした

先日11/14,11/15にvayage groupの「sunrise」インターンに参加してきました!
2日のみのインターンでしたが、学びの多い2日間だったため記憶が新しいうちにまとめます

なにしたの?

テーマは「大規模アクセスにも耐えうるようなインフラ基盤を構築せよ」というもの。
インフラを学べる機会ってなかなかないと感じていたから、このインターンを見つけた際に速攻で応募した。 (しかもAppの実装がGoだったので尚更)

内容をちょーざっくりまとめると、

  • 1.AWS内のEC2サーバーにリクエスト投げまくる(負荷をかける)
  • 2.かけた結果をLog監視、メトリクス監視、リクエストを全て捌けているか確認(大抵うまく捌き切れていない)
  • 3.なんで捌けてないの? => チーム内で議論し仮説を立てて実験

こんな感じ。上のサイクルをひたすら繰り返す。

インフラ構成図

今回の題材のインフラ構成は下記だった。

f:id:mos692:20201119151411p:plain
sunriseインフラ構成図

  • kakeruっていうツールがLBにリクエストを大量に投げる
  • LBが各種インスタンスにリクエストをバランシング
  • EC2内のGoアプリケーションがリクエスト内容をRDSにinsert。
  • GoアプリケーションからのレスポンスがLB、kakeruに返される。レスポンスの結果をS3にアップ。
  • redashを使ってRDSに接続し、DBにデータが保存されているか確認。

この中で、インターン生がパフォーマンス向上を考える際に着目した点は大きく下記の2点だと思う。(RDS数の変更は認められていなかった)

実際に自分のチームで行った施策を書いていきます。

実際の流れ

負荷は200 req/secで3分間のものを与えました。 しかし、5xxエラーがたくさん出る。

Code Highest Rate Mean Rate Total number
200 1189.6 / sec 394.68 / sec 118481
502 1053.5 / sec 272.51 / sec 68127
504 377.4 / sec 58.29 / sec 13406

はじめはこんな感じでリクエストのうちの 1/3がエラー。しかもリクエスト数が期待される総数の360000(200 * 60 * 3)に一致しない。。
こんな感じでスタート。

DBのopenはサーバー起動時に1回

まずぱっと見で目についたGoのアプリケーションの修正。 初期実装ではhandlerの中でsql.Open()が行われていました。

hakaruHandler := func(w http.ResponseWriter, r *http.Request) {
    DB, err = sql.Open("mysql", dataSourceName)
    if err != nil {
        panic(err)
    }

リクエストの度にsql.Open()を呼んでいますが、sql.Openは同じドライバを使い続ける限りサーバー起動時1回で十分です。
そのためinit()中にdbの接続を行わせるように変更。

ただこれだけではerrorは消えず。。

DBにアクセスが集中している

Goアプリケーションのインスタンスで下記のログが出ていたため、「dbへのアクセス過多でerrorが発生しているのでは?」と推察。 f:id:mos692:20201116162709p:plain

そこで、負荷付与中においてどれくらいのconnectionが貼られているのかをnetstatコマンドで調査。

[root@ip-10-1-11-242 ~]# netstat -na | grep 3306 | wc -l
2658
[root@ip-10-1-11-242 ~]# netstat -na | grep 3306 | wc -l
3893
[root@ip-10-1-11-242 ~]# netstat -na | grep 3306 | wc -l
4687
[root@ip-10-1-11-242 ~]# netstat -na | grep 3306 | wc -l
5131
[root@ip-10-1-11-242 ~]# netstat -na | grep 3306 | wc -l
5634
[root@ip-10-1-11-242 ~]# netstat -na | grep 3306 | wc -l
6174
[root@ip-10-1-11-242 ~]# netstat -na | grep 3306 | wc -l
6720
[root@ip-10-1-11-242 ~]# netstat -na | grep 3306 | wc -l
7362

port3306に5000~7000のアクセスが来ている状況。 メンターの社員の方にDB connectionは多くても数十程度という事を伝えられたので、 DBが捌ける量を遥かに超越した数のアクセスが来ていると判断。

DBのconnection数をGo側で制限

Go側で上記の対策を行いました。 具体的にはDB.SetMaxOpenConns、DB.SetMaxIdleConnsを設定し、dbアクセス数を制限するというものです。

DB.SetMaxOpenConnsはコネクションの最大数を表し、DB.SetMaxIdleConnsはidleなコネクションの最大数を設定します。

[参考]
パフォーマンス向上のためのsql.DBの設定 - 技術メモ

Go database/sql(コネクションプール/タイムアウト) - Qiita

コネクション数の管理に関しては上記の記事が参考になりました!
今回はRDSに来るconnectionが最大でも40 ~ 50になるように設定しました。

DB, err = sql.Open("mysql", dataSourceName)
    if err != nil {
        panic(err)
    }
    DB.SetConnMaxLifetime(time.Minute * 3)
    DB.SetMaxOpenConns(10)
    DB.SetMaxIdleConns(10)

これで再度実験。 f:id:mos692:20201116171642p:plain port3306へのconnection数はかなり減った。

Code Highest Rate Mean Rate Total number
200 1628.3 / sec 641.24 / sec 192441
502 6 / sec 0.98 / sec 167
504 624.4 / sec 91.37 / sec 21928

しかしerrorは消えていない・・

インスタンス数を増やす

これまではインスタンス数を2つで回していた。ただ、2つだとDBからの処理が遅れた際にGoに来たリクエストがタイムアウトしてしまうのでは?(ここは正直仮説)という話になり、インスタンス数を5台に増やして検証。

Code Highest Rate Mean Rate Total number
200 1894.6 / sec 1217.31 / sec 290796

ようやく秒間200reqを捌けました。 数の暴力ってすげーなーってのと、ロードバランサーくん優秀だなあって感じでした。

学び

各種メトリクスの見方

AWSのcloudwatch等を使って、アプリケーションの状態を監視する基礎的な手段が身についた。 満たしたい用件に対して、どのメトリクスに着目すべきかっていう判断の部分も鍛えらたと思う。

DBへのconnection poolingとか最大connection数に関して

最大コネクション数とかほとんど気にした事なかった。ただ結果的にGoのapplicationコードに

DB.SetConnMaxLifetime(time.Minute * 3)
    DB.SetMaxOpenConns(10)
    DB.SetMaxIdleConns(10)

この3行を追加しただけで5xxがかなり減ったので、大規模サービスではconnection数の管理も大事なんだあと知った。

try and errorを繰り返す

インターン中は、「仮説、実験、結果、考察」のサイクルをworkという単位で定めて、これをひたすら1日中繰り返すみたいな感じだった。(アジャイルでいうスプリントに近い感じ?)
これが大体1~2時間くらいの単位なんだけど、この時間設定が個人的に良かった。

長すぎると検証できる事が増えすぎてまとまらなくなりそうだけど、1時間という時間だと1つの仮説に対して「実験、結果、考察」のサイクルを回すのが丁度良かったように思う。

世間は狭い

twitterで知ってる人とか、別のインターンで一緒だった人がちょこちょこいて、「世間狭い!」ってなった。数として、やっぱ学生エンジニアって少ないやなあという実感を得た()

まとめ

AWSはEC2を使ってアプリデプロイした事がある程度の経験だったけど、監視周りについての基礎的な部分がかなり吸収できた。

あと、インターン中ではあんま触れなかったけどインターンのプロジェクトフォルダが.tfとMakefileで溢れてて「これがインフラの現場かー」ってなったので、その辺りも時間ある時に調べつつ読んで吸収していきたい。