ishikawa_pro's memorandum

若手webエンジニアの備忘録です.

Swift Node.js Docker AWS etc...色々やります。

ISUCON 12 予選にNode.js実装で参加しました

今年も ISUCON が開催されたので予選に参加しました。
色々忙しくてブログに起こすのに時間がかかってしまいましたが、来年のためにしっかりやったことを記録しておきます。

予選までにやったこと

さくらインターネットさんが参加者にクーポンを発行してくださっていたので、VPS を使って過去問を解いたりしていました。
内容としては MySQL の slow query の設定であったり、 nginx のログフォーマットを変えて alp で解析できるようにするなど、大会開始後にやる下準備をスムーズにできるように練習したりドキュメントにまとめたりしていました。
あとは、 普段 MongoDB しか使ってないせいで MySQL の作法をほぼ覚えてないので、 index の見方や貼り方なども一通り復習してよく使うクエリとかはドキュメントにまとめたりしていました。
今年は複数台のインスタンスを活用しようと思って、インスタンスを2台立てて1台を MySQL サーバー用でもう一台をアプリケーションサーバー用にして、アプリケーションから外のMySQL に接続しにいく練習などもしました。

当日

細かい時間などは記録していなかったので、やったこと順で記述していこうと思います。

言語の切り替え

まずはセットアップを完了したら、初期設定の Go でベンチを試しました。
初期スコアを記録していませんでしたが、 3000 ちょっとくらいのスコアでした。
次に Node.js 実装に切り替えてベンチを実行しました。
一気に 2500 くらいまでスコアが下がって少し悲しくなりましたが、Node.js の名誉のためこのまま Node.js 実装で進みました。  

コードのバージョン管理

まずは作業に入る前に、コードを git 管理して github 上に push しました。
github.com

DB の設定などは面倒だったので別リポジトリに分けて管理しました。
github.com

基本的に作業単位でブランチを切って PR 作成して、セルフマージするスタイルで進めていました。

visit_history のインデックス追加

alp や MySQL の slow query log の設定をしたところで、とりあえず pt-query-digest を使って slow query の集計をしてみました。
初期の状態だと、 visit_history の SELECT がかなり遅かったので、

# Profile
# Rank Query ID                            Response time  Calls R/Call V/M
# ==== =================================== ============== ===== ====== ===
#    1 0x676347F321DB8BC7FCB05D4948FC2248  120.8258 55.1%  1311 0.0922  0.06 SELECT visit_history
#    2 0x94A9E43DFAAFA029A1FC19A5563AD0F5   91.4036 41.7%  8626 0.0106  0.00 REPLACE id_generator
# MISC 0xMISC                                7.0060  3.2%  2393 0.0029   0.0 <11 ITEMS>

解析結果の詳細を確認しつつコードの方も見て、

# Query 1: 16.39 QPS, 1.51x concurrency, ID 0x676347F321DB8BC7FCB05D4948FC2248 at byte 1400628
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.06
# Time range: 2022-07-23T02:34:33 to 2022-07-23T02:35:53
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count         10    1311
# Exec time     55    121s   214us   563ms    92ms   230ms    76ms    65ms
# Lock time      0     3ms     1us   306us     2us     2us     8us     1us
# Rows sent     97 103.91k       0     199   81.16  174.84   51.71   62.76
# Rows examine  88  24.05M       0  49.19k  18.79k  49.01k  13.36k  13.78k
# Query size    25 182.98k     141     144  142.92  136.99    0.73  136.99
# String:
# Databases    isuports
# Hosts        localhost
# Users        isucon
# Query_time distribution
#   1us
#  10us
# 100us  ####
#   1ms  ###
#  10ms  ################################################################
# 100ms  ######################################
#    1s
#  10s+
# Tables
#    SHOW TABLE STATUS FROM `isuports` LIKE 'visit_history'\G
#    SHOW CREATE TABLE `isuports`.`visit_history`\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT player_id, MIN(created_at) AS min_created_at FROM visit_history WHERE tenant_id = 79 AND competition_id = '55bf3a2d2' GROUP BY player_id\G

とりあえず tenant_idcompetition_id の複合index を貼りました。

github.com

結果はベンチを回すたびにばらついていましたが、2452 -> 2699 と思ったより上がりませんでした。
ただ、Count が 1311 から 2801 に上がっていたのと、 query_time distribution も 10ms の分布が一番多かったが 1ms の分布に移っていたので、結構効果自体はあると判断してそのまま先へ進みました。

# Query 2: 38.37 QPS, 0.65x concurrency, ID 0x676347F321DB8BC7FCB05D4948FC2248 at byte 2910547
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.34
# Time range: 2022-07-23T03:02:40 to 2022-07-23T03:03:53
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count         21    2801
# Exec time     34     48s   218us      2s    17ms    36ms    76ms     7ms
# Lock time      0     6ms     1us    64us     1us     2us     2us     1us
# Rows sent     98 329.53k       0   4.88k  120.47  174.84  367.49   80.10
# Rows examine  62   5.15M       0  78.96k   1.88k   2.76k   5.78k   1.26k
# Query size    42 391.02k     141     144  142.95  136.99    0.71  136.99
# String:
# Databases    isuports
# Hosts        localhost
# Users        isucon
# Query_time distribution
#   1us
#  10us
# 100us  #####
#   1ms  ################################################################
#  10ms  ###########################################
# 100ms  #
#    1s  #
#  10s+
# Tables
#    SHOW TABLE STATUS FROM `isuports` LIKE 'visit_history'\G
#    SHOW CREATE TABLE `isuports`.`visit_history`\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT player_id, MIN(created_at) AS min_created_at FROM visit_history WHERE tenant_id = 1 AND competition_id = '547f03113' GROUP BY player_id\G

[GET] /api/player/player/[0-9a-z]+ api の改善

次に alp の結果を眺めつつ、さっと直せそうな場所を探して、

+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------+-------+--------+--------+----------+--------+
| COUNT | 1XX | 2XX | 3XX | 4XX | 5XX | METHOD |                     URI                      |  MIN  |  MAX   |  AVG   |   SUM    |  P99   |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------+-------+--------+--------+----------+--------+
| 537   | 0   | 498 | 0   | 39  | 0   | GET    | /api/player/competition/[0-9a-z]+/ranking    | 0.004 | 14.488 | 1.889  | 1014.383 | 13.556 |
| 461   | 0   | 432 | 0   | 29  | 0   | GET    | /api/player/player/[0-9a-z]+                 | 0.004 | 15.336 | 1.679  | 774.176  | 13.724 |
| 106   | 0   | 95  | 0   | 11  | 0   | GET    | /api/player/competitions                     | 0.004 | 0.112  | 0.026  | 2.804    | 0.108  |
| 55    | 0   | 47  | 0   | 8   | 0   | POST   | /api/organizer/competition/[0-9a-z]+/score   | 0.004 | 13.140 | 2.788  | 153.356  | 13.140 |
| 38    | 0   | 37  | 0   | 1   | 0   | POST   | /api/organizer/competitions/add              | 0.004 | 0.064  | 0.029  | 1.096    | 0.064  |
| 34    | 0   | 33  | 0   | 1   | 0   | POST   | /api/organizer/competition/[0-9a-z]+/finish  | 0.004 | 0.048  | 0.019  | 0.644    | 0.048  |
| 24    | 0   | 24  | 0   | 0   | 0   | GET    | /api/organizer/players                       | 0.004 | 0.032  | 0.008  | 0.184    | 0.032  |
| 20    | 0   | 20  | 0   | 0   | 0   | GET    | /api/organizer/billing                       | 0.004 | 1.168  | 0.179  | 3.584    | 1.168  |
| 14    | 0   | 13  | 0   | 1   | 0   | POST   | /api/organizer/player/[0-9a-z]+/disqualified | 0.004 | 0.024  | 0.016  | 0.228    | 0.024  |
| 11    | 0   | 6   | 0   | 5   | 0   | POST   | /api/admin/tenants/add                       | 0.004 | 0.092  | 0.047  | 0.520    | 0.092  |
| 10    | 0   | 8   | 0   | 2   | 0   | GET    | /api/admin/tenants/billing                   | 3.720 | 25.909 | 11.587 | 115.870  | 25.909 |
| 8     | 0   | 8   | 0   | 0   | 0   | POST   | /api/organizer/players/add                   | 2.040 | 5.964  | 4.025  | 32.200   | 5.964  |
| 1     | 0   | 1   | 0   | 0   | 0   | POST   | /initialize                                  | 2.648 | 2.648  | 2.648  | 2.648    | 2.648  |
| 1     | 0   | 1   | 0   | 0   | 0   | GET    | /css/app.83b4c321.css                        | 0.000 | 0.000  | 0.000  | 0.000    | 0.000  |
| 1     | 0   | 1   | 0   | 0   | 0   | GET    | /js/app.3a4ec98c.js                          | 0.000 | 0.000  | 0.000  | 0.000    | 0.000  |
| 1     | 0   | 1   | 0   | 0   | 0   | GET    | /index.html                                  | 0.000 | 0.000  | 0.000  | 0.000    | 0.000  |
| 1     | 0   | 1   | 0   | 0   | 0   | GET    | /api/organizer/competitions                  | 0.004 | 0.004  | 0.004  | 0.004    | 0.004  |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------+-------+--------+--------+----------+--------+

[GET] /api/player/player/[0-9a-z]+ が結構叩かれていて、直せそうなところがあったので改善に取り組みました。

やったことは、player の詳細情報取得 api で、その player が属する tenant の competition を全て取得後に、 大会情報と 大会毎の player_score を for...of で1個ずつ直列に取得していたので、Promise.all を使って並列実行するように直しました。
今ブログを書きながらコードを見て思ったのですが、処理の最初の方で tenant に属する competision を全て取得してるので、ここで再度competisionを取得すること自体が無駄ですね。。

github.com

スコアはなぜか 2195 と落ちてしまいましたが、

+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------+-------+--------+-------+----------+--------+
| COUNT | 1XX | 2XX | 3XX | 4XX | 5XX | METHOD |                     URI                      |  MIN  |  MAX   |  AVG  |   SUM    |  P99   |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------+-------+--------+-------+----------+--------+
| 533   | 0   | 503 | 0   | 30  | 0   | GET    | /api/player/player/[0-9a-z]+                 | 0.004 | 19.148 | 1.301 | 693.451  | 14.260 |

リクエストの Count が増えていたのと、Latency も下がってはいたので、悪くはなっていないと判断して先にすすみました。

visit_history index の修正

最初の visit_history の index を貼ったことでそれなりに改善はしましたが、 pr-query-digest でみるとまだ SELECT visit_history が遅いようだったので、再度チューニングポイントがないか確認しました。

# Profile
# Rank Query ID                            Response time  Calls R/Call V/M
# ==== =================================== ============== ===== ====== ===
#    1 0x94A9E43DFAAFA029A1FC19A5563AD0F5  111.6499 66.4%  8822 0.0127  0.01 REPLACE id_generator
#    2 0x676347F321DB8BC7FCB05D4948FC2248   48.2423 28.7%  2908 0.0166  0.25 SELECT visit_history

該当のクエリはこういうクエリで、

SELECT player_id, MIN(created_at) AS min_created_at FROM visit_history WHERE tenant_id = 1 AND competition_id = '547f03113' GROUP BY player_id\G

tenant_id と competition_id の複合index を貼っているので WHERE 句は index が効いてそうでしたが、player_id で GROUP BY している部分で index が効いてなさそうでした。
そのため、tenant_id と competition_id の複合index から、 tenant_id, competition_id, player_id の複合indexへ変更しました。
before

# Query 2: 39.30 QPS, 0.65x concurrency, ID 0x676347F321DB8BC7FCB05D4948FC2248 at byte 2728622
# Scores: V/M = 0.25
# Time range: 2022-07-23T04:07:39 to 2022-07-23T04:08:53
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count         19    2908
# Exec time     28     48s   173us      2s    17ms    40ms    65ms     7ms
# Lock time      0     6ms     1us   129us     2us     2us     3us     1us
# Rows sent     98 331.91k       0   4.88k  116.88  183.58  361.27   76.28
# Rows examine  62   5.17M       0  78.96k   1.82k   2.89k   5.68k   1.20k
# Query size    40 405.93k     141     144  142.94  136.99    0.69  136.99
# String:
# Databases    isuports
# Hosts        localhost
# Users        isucon
# Query_time distribution
#   1us
#  10us
# 100us  #######
#   1ms  ################################################################
#  10ms  ###############################################
# 100ms  #
#    1s  #
#  10s+
# Tables
#    SHOW TABLE STATUS FROM `isuports` LIKE 'visit_history'\G
#    SHOW CREATE TABLE `isuports`.`visit_history`\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT player_id, MIN(created_at) AS min_created_at FROM visit_history WHERE tenant_id = 1 AND competition_id = '59cb9aad2' GROUP BY player_id\G

after

# Query 2: 40.55 QPS, 0.53x concurrency, ID 0x676347F321DB8BC7FCB05D4948FC2248 at byte 2180291
# Scores: V/M = 0.17
# Time range: 2022-07-23T05:56:48 to 2022-07-23T05:58:06
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count         23    3163
# Exec time     24     41s   184us   872ms    13ms    31ms    47ms     6ms
# Lock time      0    10ms       0     5ms     3us     1us    82us     1us
# Rows sent     98 365.81k       0   4.88k  118.43  183.58  346.65   80.10
# Rows examine  64   5.72M       0  78.96k   1.85k   2.89k   5.45k   1.33k
# Query size    46 441.47k     141     144  142.92  136.99    0.66  136.99
# String:
# Databases    isuports
# Hosts        localhost
# Users        isucon
# Query_time distribution
#   1us
#  10us
# 100us  ####
#   1ms  ################################################################
#  10ms  #################################
# 100ms  #
#    1s
#  10s+
# Tables
#    SHOW TABLE STATUS FROM `isuports` LIKE 'visit_history'\G
#    SHOW CREATE TABLE `isuports`.`visit_history`\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT player_id, MIN(created_at) AS min_created_at FROM visit_history WHERE tenant_id = 1 AND competition_id = '547f03113' GROUP BY player_id\G

結果は、 latency が少し改善されたのと、Query_time distribution で 100ms の分布が少し減って、 100us と 1ms の分布が少し増えたので、遅めのクエリが改善されたようでした。
スコアは記録を忘れていましたが、そこまで上がらず 2000 前半くらいで留まっていました。

csv upload を改善

csv upload で score を一括 upload するエンドポイント( POST /api/organizer/competition/:competition_id/score ) は、加点されるポイントが多い api だったため、改善に取り組みました。
SQLite を真面目に取り扱ったことがなかったですが、 for...of で一行ずつ直列に insert するのは明らかに良くないだろうと思い、改善してみることにしました。
調べると、bulk insert するときは transaction を貼った方が良いとのことだったので、とりあえずトランザクションを貼って、 insert を Promise.all で並列実行するように修正しました。

github.com

スコア 2642 と思ったほど上がらなかった(多分 flock のせい) ですが微増したので、マージしました。

id をライブラリで生成するように修正

当初から気になっていた、 REPLACE id_generator の方を改善みてみることにしました。

# Profile
# Rank Query ID                            Response time  Calls R/Call V/M
# ==== =================================== ============== ===== ====== ===
#    1 0x94A9E43DFAAFA029A1FC19A5563AD0F5  111.6499 66.4%  8822 0.0127  0.01 REPLACE id_generator
#    2 0x676347F321DB8BC7FCB05D4948FC2248   48.2423 28.7%  2908 0.0166  0.25 SELECT visit_history

これが何をやっているか調べてみたところ、 dispenseID というシステム全体で一意なIDを生成する関数で

REPLACE INTO id_generator (stub) VALUES ('a')\G

を実行して、 id_generateor の id を auto increment させて一意なIDとして使っているようでした。
REPLACE文 を発行してデッドロックで失敗したら再度 REPLACE 文を発行するというforループで、スピンロック的なことをやっていました。
この対応に関しては横着してしまおうと思い、 DBは使わずに uniqid という npm pacakge を使ってid生成するようにしました。

github.com

依存の無いシンプルなライブラリで node_module の管理が面倒だったので、アプリケーション内に uniqid ライブラリを内包するような形にしてしまいました。

スコアは、DBを使わなくなったぶん良くなり 2908 となりました。

その他

id_generator の対応をし終わったところで 17:00 手前でした。
残り時間もすくないため、細々とした対応でできそうなものだけやってみようといくつか取り組みました。
1つは、 MySQL のクエリキャッシュの設定をしようと思いました。
これは事前に練習していたので、設定方法などをメモっており、メモ通りに設定してみました。
ところがうまく再起動してくれません。
よくよく調べると今回は MySQL 8 が動いており、 MySQL 8 からクエリキャッシュは廃止になったそうです。。
MySQL 8 は実務でも全く使ったことがなく予習もしていなかったので、残り時間で何かするのは無理だと判断して、改善を諦めました。

2つ目は、 node cluster 対応しました。
node cluster とは、Node.js は本来シングルスレッドのイベントループを回して処理をするデザインパターンで実装されていますが、 cluster という標準モジュールを使うことで、メインプロセスから fork してワーカープロセスを起動することができます。
この機能を利用して、今回のサーバーアプリケーションをワーカー化し、複数スレッドを利用してリクエストを捌けるようにしました。  

github.com

起動するワーカー数は調整してみましたが、 3個が一番スコアが高く、 3317 でした。
この対応でちょうど finish となってしました。

まとめ

下記がスコアの推移です。

スコアの推移

Golang の初期スコアを少ししか越えられなかったのが、ちょっと敗北感を感じましたw 昨年は、あんまりできることがなくて時間を持て余していた感がありましたが、今回は全然時間たりないな〜という感じだったので、まあ去年よりは成長したかなと思いました。
あとは去年から業務で、Datadog APM とか色々ツール使って計測したりすることが増えて、計測した結果を眺める力は前より付いたなと実感しました。
ただ成績は結構イマイチだったので、また来年までしっかり業務も生かしながらスキルを伸ばしていこうと思います。

さいごに運営の皆様、今年も運営お疲れ様でした!

2021年振り返り

こんにちは。
2021年も終わりなので今年は振り返りブログを書こうと思います。

仕事編

仕事というかエンジニア的な活動の振り返りをしようと思います。

1月

今年も相変わらず Node.js や TypeScript でサーバーサイドのコードを書いてました。
1月末くらいまでは、昨年リリースしたかった案件がなかなか完成せずに、死にそうになりながら追い込み的なことをしていました。
その頃作った機能は、上司と一緒に会社のテックブログにまとめました。
初めて amazon chime sdk を使ったサービスを作りましたが、webRTC とかについて詳しくなくても簡単にビデオ通話機能が実装できて便利でした。
cam-inc.co.jp

その後、まだ日本での amazon chime sdk を使ったサービスの事例も少なかったことなどもあり、 aws の公式ブログでも紹介していただきました。
自分の名前も載せて頂けてとてもうれしかったです。死ぬまで自慢すると思います。
aws.amazon.com

2~7月

その後2月以降は、そこまで技術的になにかやった記憶はあまりないですが、仕事自体はそれなりに忙しかったです。

8月

8月は今年も ISUCON に参加しました。
細かいやったことなどは、別の記事にまとめてあります。
ishikawa-pro.hatenablog.com 記事にも書いたと思いますが、去年やれたことは今年もスムーズにやれたので前半は結構いいペースで進めた気がしますが、そこから先が全然スコアを上げることができなかったです。あと、前日の仕事の疲れが出てしまい、後半で集中力が切れてしまったのもありました。
ISUCON は、前年の自分のやったことや結果と比べて自分の成長を測れる機会だと思ってるのですが、あまりいい結果ではなかったので、最近仕事はこなしてるけど技術成長できていないなと色々悩むようになりました。

9 ~ 10 月

ISUCONもイマイチ結果が出なかったことや2~7月が特に技術的なことが何もできなかったことなどから、今後どうしていくか色々考えたりするようになってました。
そして、技術力を上げるために転職して環境を変えるのも1つの手かもしれないと思い、とりあえず相談するアテもなかったのでTwitter で転職希望ツイートをしたところプチバズってしまいました。
思ったより色々な人に声をかけていただき驚きました。
当然社内の人の目にもとまったので面談が入りました笑
結果的に転職はせず、現在は前とは違うチームに異動して、今は自分の伸ばしたいことなどとマッチしていて楽しく仕事をしています。
皆さんも悩んだ時はTwitterではなく、まずは信頼できる上司などに相談しましょう。
異動後は、結構心のゆとりができたので、今年まだ1冊も読めていなかった技術書を読み始めたり色々技術をキャッチアップしたりするようになれました。

11 ~ 12月

異動後は、社内のプロダクトの改善周りに取り組んでいます。
ちょうど読んでいた技術書を参考に、改善できたりしたこともあって色々勉強しながらできてるなと感じています。
あとは AWSGCP などインフラ周りは苦手だったのであまり触ってこなかったですが、最近は機会があれば自分で触って勉強したりしてます。

今年読んだ技術書

今年読んだ本はこちらです。

読んでる最中なのは、

  • データ指向アプリケーションデザイン
  • 並行プログラミング入門

です。 来年はもう少し沢山読みたいと思います。

プライベート編

最後はサクッとプライベート編。
今年から始めた趣味は、読書と映画鑑賞です。
読書も映画鑑賞も今まで人生でほとんどやってこなかったですが、今年はそれなりにやりました。
読書に関しては、月に3~5冊は読んだのでトータルで40冊くらいは読んだと思います。
なかでも江國香織さんと村上春樹さんの本は沢山読みました。
村上春樹さんの本は10冊以上買ったのですっかりハルキストです。(ちゃんと内容を理解してるかは微妙ですが)
映画は、今までほとんど映画館にも行ってなかったし家で見ることもなかったですが、今年は映画館にもよく行ったし家でも映画を見るようになりました。
今年映画館で見た映画でお気に入りは

  • 花束みたいな恋をした
  • ファーザー
  • 街の上で
  • ドライブ・マイ・カー

が結構好きでした。
家で見た映画でお気に入りは

です。 読書も映画も気を抜くとすぐに時間を取らなくなりそうなので、来年もしっかり継続できればなと思います。

まとめ

いろいろ振り返ると、アレはまだ1年も経ってないのかと驚くことが結構あります。
それだけ濃い1年だったということだと思うので来年もそうできるように頑張ろうと思います。
色々迷惑をかけた人が多いと思いますが、今年1年ありがとうございました。
来年精進するのでよろしくお願いいたします。

「分散システムデザインパターン」を読んだ

こんにちは。
最近はかなり日も短くなって寒くなり始めましたね。
今日は、ちょっと前に読み終わった「分散システムデザインパターン」についてメモです。

読んだモチベーション

この本を読もうと思ったきっかけは、最近 container 技術や k8s に関しての本を読んでいて、この本もよくおすすめに出ていたのがきっかけです。
あとは、普段の業務でマイクロサービスアーキテクチャであったり k8s や ECS を採用しているので、分散システムデザインについてもちゃんと知識をつけておきたいなと思っていたのもあります。

感想など

今回も電子版で読みましたが、物理本でA5サイズの200ページなのですぐに読めると思います。

本の構成はこんな感じでした。

1章 はじめに
第Ⅰ部 シングルノードパターン
2章 サイドカー
3章 アンバサダ
4章 アダプタ

第Ⅱ部 マルチノードパターン
5章 レプリカがロードバランスされたサービス
6章 シャーディングされたサービス
7章 スキャッタ・ギャザー
8章 ファンクションとイベント駆動処理
9章 オーナーシップの選出

第Ⅲ部 バッチ処理パターン
10章 ワークキューシステム
11章 イベント駆動バッチ処理
12章 協調的バッチ処理

13章 まとめ:新しい始まり?

第1部では、シングルノードでのデザインパターンを紹介して、2部ではマルチノードでのザインパターン、3部は分散システムでのバッチ処理デザインパターンについて紹介されていました。
まったく知らないデザインパターンとかはなかったですが、それぞれのパターンごとにサンプルもまじえて詳しく解説してあったのでとてもよかったです。
サンプルでは k8s を使っているので、k8s の基本的な部分の理解とかはあった方がより内容が理解しやすいかもしれないです。
個人的には、シャーディング、マスタの選出、ロックについてあまり詳しくなかったので、それらについて深くしることができたのはよかったです。

本自体は結構ページ数が少ないですが、内容としてはかなりよかったのでおすすめです。
ちょうどこの本を読み終わった頃に、たまたま業務でアプリケーションをシャーディングする機会があったのでめちゃくちゃ役に立ちました。
次は「データ指向アプリケーションデザイン」を読んでいるのですが、結構読みごたえあるので分けてブログにメモしていこうかなと思っています。

「Kubernetes 完全ガイド」を読んだ

こんにちは。
読書の秋ということで、小説・技術書ともに消化が捗っています。
小説は村上春樹にはまってます。
今日は、「Kubernetes 完全ガイド」を読んだのでその記録です。

読んだモチベーション

読もうと思った理由は、業務でk8s (GKE) を使っているのですが、普段はインフラ専門のエンジニアの方に管理して頂いてるのもあり、あまりガッツリ触る機会もなく、ふんわりとした知識しかなかったためです。僕が携わってるサービスは、マイクロサービスアーキテクチャを採用しており、k8sの理解を深めておくことで、よりよいアプリケーションの設計をするのにも役立つと思ったのも理由の1つです。

感想など

ページ数は650ページ以上あるので中々読みごたえがありました。

構成は、

  1. Docker の復習と「Hello, kubernetes
  2. なぜ Kubernetes が必要なのか?
  3. Kubernetes 環境の選択肢
  4. API リソースと kubectl
  5. Workloads APIs カテゴリ
  6. Service APIs カテゴリ
  7. Config & Storage APIs カテゴリ
  8. Cluster APIs カテゴリと Metadata APIs カテゴリ
  9. リソース管理とオートスケーリング
  10. ヘルスチェックとコンテナライフサイクル
  11. メンテナンスとノードの停止
  12. 高度で柔軟なスケジューリング
  13. セキュリティ
  14. マニフェストの汎用化を行うオープンソースソフトウェア
  15. モニタリング
  16. コンテナログの集約
  17. Kubernetes 環境での CI/CD
  18. マイクロサービスアーキテクチャとサービスメッシュ
  19. kubernetesアーキテクチャを知る
  20. kubernetes とこれから

1 ~ 3章はDockerの復習やk8sとは何かという感じでした。
4章がいきなり kubectl や k8s のリソースなどについて解説がされていて、僕がまだあんまりk8sの全体像が分かってない状態だったので、ちょっとこの本は早すぎたかもと思って一旦読むのをやめました。それで前の記事でも書いた 「イラストでわかるDockerとKubernetes」 を読んでから、 続きを読みなおしました。
ishikawa-pro.hatenablog.com 「イラストでわかるDockerとKubernetes」を読んだおかげで、コンテナ技術や k8s が裏で何をしているかなどについてある程度理解したおかげで、それ以降の章は特に苦戦することなく読むことができました。
4 ~ 8 章がk8sのリソースに関する説明を1章ごとに分けて説明されていました。 k8sを扱ったり理解するうえで大事な基本的な部分だったので勉強になりました。
9 ~ 12章も実際にk8sで開発するうえで関わってきそうな内容が多かったです。
13章のセキュリティ関しては、SericeAccountなどの基本的なところから 、SecurityContext とか普段あんまり触らないところも多かったので、部分的に軽めに流したりしながら読みました。
14 ~ 18章は、k8s に関する周辺のツールやサービスなどの紹介で、実際に業務で使ってるものや知らないものなどもあって結構楽しく読めました。
19 ~ 20章は、 「イラストでわかるDockerとKubernetes」 の内容と重複してる部分が多かったので復習がてら読みました。
僕は、実際に業務で使ってるマニフェストなどを見たり、こう書き直したりもできるかななど考えながら、理解を深めていく感じで読み進めました。
かなりページ数が多くて読みごたえのある本ですが、おかげでk8sに対する苦手意識がかなりなくなったのでよかったです。
最近は技術書を読む習慣がついてきたので、この調子で継続して勉強していきたいと思います。

「イラストでわかるDockerとKubernetes」を読んだ

こんにちは。
今日は、「イラストでわかるDockerとKubernetes」を読んだのでその記録です。
余談ですが今回は、iPad air4 を使って電子書籍で読んでみたのですがApple Books アプリは結構使い勝手が良くて便利ですね。電子だといいアプリないしなんか読みずらいなと思って最近は物理本に戻っていたのですが、Apple公式アプリがこんなに便利になっていたとは思いませんでした笑

読んだモチベーション

最初にこの本を知ったのは fukabori.fm で著者の徳永航平さんがゲスト出演されていてコンテナランタイムについてお話されていて、この本が紹介されていたからです。

fukabori.fm

このエピソード内でのコンテナランタイムなどのお話もとても面白くて、コンテナランタイムについて興味がわいてきて、本もわかりやすいとiwashiさんが紹介されていたので、エピソードが終わった後に即ポチしました。
あとは、k8sの勉強をしようと思って 「Kubernetes 完全ガイド」 を買ったはいいものの、思ったよりk8sに関する仕組み的な部分は書いてなさそうで、この本を読む前にもう1ステップはさみたいなとちょうど思っていたところだったのもあります。

感想

最初に読みやすさ的な部分ですが、タイトルの通りでイラストが多めなのと、ページ数も200ページくらいなのでサクッと読めました。(2日くらい)
タイトルからは初心者向けの本なのかなと思ってしまいそうですが、中身はコンテナランタイムに関する話やコンテナ技術とは何で、DockerやKuberntesが何をしてるのかについて解説してあります。なので、初心者というよりはある程度Dcokerなどを使い慣れててDockerやk8sが何をしてるのかしっかり理解したい人向けかなと思います。
構成は、

  • 1章 コンテナ技術の概要
  • 2章 Dockerの概要
  • 3章 kubernetesの概要
  • 4章 コンテナランタイムとコンテナの標準仕様の概要

となっています。
2章での、コンテナのレイヤ構造についての解説は、今までざっくりとした知識しかなかったので勉強になりました。 実際にoverayファイルシステムを使って、ファイルを重ね合わせてマウントしてみたりする部分や、 docker save コマンドでコンテナイメージをtar形式で出力して実際に中身を見てみる部分は、どのようにしてイメージからルートファイルシステムを作成しているのかが理解できてとてもよかったです。
3章は、k8sの役割についてや、Pod, Deployment, Service などについても図を使って分かりやすく説明されていました。 3章の最後の方では、 kubectl apply などを実行したときに k8s がどのようにして Pod を作成・実行してるのかについて説明されていました。正直この辺の知識は全然なかったので、 kubelet がPodの設定などを受け取り CRI ランタイムへ命令して実際にイメージ取得をしたりコンテナ群をPodとして作成したりしていると知りました。
4章では、コンテナランタイムについて解説されていました。 CRIランタイムやOCIランタイムについての役割や、それぞれが実際にどのように協調してコンテナが作成され動いているのかが説明されていました。また、CRIとOCIの仕様に準拠したそれぞれのランタイムについていくつか紹介などもされています。
全体的に200ページとは思えないくらいの内容の濃さで、とても勉強になりました。今までは コンテナ=Docker で、コンテナをいい感じにオーケストレーションしてくれるのが k8s みたいな認識でしたが、コンテナ技術の仕組みなどを理解することで、いろいろな用語の理解や docker run などのコマンドを叩いたときに裏で何が起きているのかなどをしっかり理解することができました!
dockerなどのコンテナ技術について、踏み込んで理解したい人にはおすすめだと思います。
あと、この本を読んだ後に fukabori.fm のコンテナランタイムのエピソードを聞くとより会話についていきやすいかもしれないです。(逆も復習できていいかもしれない)
次は 「Kubernetes 完全ガイド」を読んでみようと思います。

「入門 監視」を読んだ

こんにちは。
「入門 監視」という技術書を読んだので、その記録です。

読んだモチベーションとしては、普段サーバーサイドエンジニアをしていて、業務の中でアプリケーションやインフラなどでアラートが飛んできた時に色々対応したり見たりすることも多いですが、雰囲気で数値などをみてたり、今までの経験から対応してしまってることも多く、ちゃんと監視に関する知識がないなと思ったのが1つです。
あとは、最近関わってるプロジェクトに Datadog APM を入れて、いろいろアプリケーション内の数値なども見れるようになったけど、もっとこの情報をうまく活用できるようになりたいなと思ったのもきっかけの1つです。

感想

A5サイズの本で、ページも200ページくらいなのでサクッと読めました。
内容も入門というだけあって分かりやすかったです。
ツールの使い方とかはほぼ載ってなくて、いろいろなレイヤーごとに何を監視すべきかなどが分かりやすく解説してありました。

構成は、

  • 1章 監視のアンチパターン
  • 2章 監視のデザインパターン
  • 3章 アラート、オンコール、インシデント管理
  • 4章 統計入門
  • 5章 ビジネスを監視する
  • 6章 フロントエンド監視
  • 7章 アプリケーション監視
  • 8章 サーバ監視
  • 9章 ネットワーク監視
  • 10章 セキュリティ監視
  • 11章 監視アセスメントの実行

という感じです。
前半は、監視とはどういうことをするものなのかや、どうやって監視をしていくかなどについて書いてありました。
後半は、ビジネスやアプリケーション、サーバーなどいろいろなレイヤーでの監視の方法や取るべき数値などが説明してありました。 最後の方のネットワーク監視・セキュリティ監視は、僕に監視する以前にネットワークやlinuxに関する知識が足りなくて難しかったので、軽く読み流しました。
監視についてしっかりとした知識がなくて、これから勉強していこうという人にはぴったりな本だと思いました!
いい本だし、読むの早い人なら2日くらいで読めるので興味ある人はぜひ読んでみてください!

ISUCON11 予選に参加しました

こんにちは。
ブログ更新がすごく久しぶりになってしまいました。
ISUCON11 予選に参加したので、今年も出場レポートを残しておきます。

isucon.net

自分の実力とか成長を確かめるために、今年も1人チームでの参加にしました。

使った言語

普段業務では、Node.js や TypeScript でサーバーを書いてるので、Node.js (TypeScript) 実装で挑戦しました。
フレームワークは定番の Express.js だったので、特に心理的障壁などもなくコード自体は読むことができました。

やったこと

来年の自分用に、やったことをまとめておきます。
初期スコアをちゃんと記録してなかったのでかなり曖昧ですが、Node.js 実装だと 1000点 ちょっとくらいでした。
Go言語実装の方が、結構初期スコア高かったような気がしました。

リポジトリ作成など

ソースコード管理のため、GitHubのプライベートリポジトリにコードをpushしました。

github.com

コードの編集は、ローカル環境に clone して作業しました。
やったことを記録しておくために、一応作業内容単位でブランチを切って、プルリクエストを作成して進めました。
1人でやるので、CI/CD環境とかは作成してもそんなにうまみはないかなと思って特に構築しませんでした。

構成確認・ログ周りの設定

コードを読んだりする前に、アプリケーションの構成を確認してログの設定などをしました。
構成は、 Nginx + サーバーアプリ + MySQL(mariaDB) でした。
ログ周りの設定は、初めに Nginx のログフォーマットを修正してalp で解析できるようにしました。
github.com この作業は、事前に復習してて手元に作業手順を用意してたので、10分くらいで終わったと思います。
次に DB が MySQL だったので、slow query log を吐くように設定して、pt-query-digest で解析できるようにしました。

www.percona.com

これは手順書とか用意してなかったですが、15分くらいで終わったと思います。

isu_condition のINSERTを改善

alp でアクセスログを見ると、[post] /api/condition/:jia_isu_uuid のリクエストが多かったので、とりあえずそこのコードを確認しました。
1リクエストで複数の isu_condition をINSERTする際に、for文で1行ずつ INSERT をしていたので、1クエリで複数行INSERTできるようにSQLと実装を改善しました。

github.com

この改善で、スコアが 2809 まで上がりました。

isucondition テーブルにindexを貼る

次に確か、slow query log などを見てました。
isu_condtionを jia_isu_uuid で引いてるところがあり、EXPLAIN で実行計画を見たらテーブルスキャンしてそうだったので、インデックスを貼りました。
結果をちゃんとメモってなかったんですが、これは結構効果があり、スコアが9000前半くらいまでは上がりました。
確かここまででやって、13:30 くらいだったような。

isu_condtion の INSERTを改善(その2)

次にまた、 [post] /api/condition/:jia_isu_uuid の改善をしました。
レギュレーションによると、

POST /api/condition/:jia_isu_uuid で受け取ったコンディションの反映が遅れることをベンチマーカーは許容しています。

とのことだったので、 insert のクエリを投げた後に、 await で結果を待たないでレスポンスを返してしまうように実装を修正しました。
github.com これもそれなりに効果があり、スコアが 10204 になりました。
ここらへんまでで 14:00 過ぎくらいだったはずです。
順位は100位手前くらいだったような気がします。
お腹が空いたのでここら辺で一旦休憩しました。

DBの調整

去年の自分の参加ブログを確認して、thred_cache_size, query_cache_size, innodb_buffer_pool_size などを調整していたので、今回も値を調整してみました。
ISUCON10 予選に参加しました - ishikawa_pro's memorandum
これで、スコアが max 11000 くらいまで上がったはずです。(記録残してなかった。。)

その他もろもろ

前日の仕事の疲れが出てしまい、後半からかなり集中力が切れてしまいました。。

データの取得時にトランザクション貼ってるところが何箇所かあったので、一旦外してパフォーマンス見てもいいのではと、なんとなく思って、試したりとかしてました。
graph取得のトランザクション削除 by ishikawa-pro · Pull Request #3 · ishikawa-pro/isucon11-qualifying · GitHub
あんまり効果なかったです。

最後に isu の一覧取得で、 isuに紐付くisu_conditionのデータをfor 文を使ってシリアルに取得していたので、 Promise.all でパラレルに取得するようにしました。
github.com これもパフォーマンス的には変わらなかったです。
そもそも N+1 なので、SQLでJOINして1クエリで取得できるようにすべきだと思ったんですが、時間がなかったので無理でした。

最終的にDBのチューニングでは、値をいじりすぎたりして、ちょっとスコアを落としてしまい、9000点後半代でフィニッシュ。

振り返り

去年と比べると、自分が去年やれたことは、お昼過ぎくらいにはこなしててよかったです。ただ、そこからさらに改善するところまで持っていくことができなかったのが後悔です。
DB周りは、Indexを貼ったりなどをもっと積極的に試行錯誤してもよかったなと後になって思いました。
普段の業務でMongoDBを使ってて、SQLを全く書いてないのもあり、SQLの改善などに手を出せなかったので、なんとか来年までにSQLRDBも勉強する時間を作りたいなと思いました。
あと、いまだに3つのインスタンスを活かすところまで改善ができないので、そういうインフラとかの設定周りも勉強して伸ばしていきたいです。

来年もISUCONがあるかはまだ分からないですが、来年に向けて技術を高めていこうと思います!
運営や他の参加者の皆様、お疲れ様でした!!