ISUCON12予選作問ネタメモ

ISUCON12が大盛況のうちに無事終わって早3ヶ月が過ぎました。もうすっかり冬ですね。

この記事はISUCONアドベントカレンダー 2日目の記事です。

私はISUCON12の予選作問チーム(兼ポータル運用係)だったので、この記事ではISUCON12の予選作問に当たってチームでどのような出題を考えていたのか、没ネタ、やらなかったこと、やれなかったことを含めてざっくばらんに公開してしまいたいと思います。

予選に参加されたかたは、実際にどの要素が採用されていたのかなどを問題を思い出しつつ、ご笑覧ください。

アイディア出し

まず最初に、予選作問チーム全員で問題の要素ネタを出しました。

  • 箱庭諸島(CGIゲーム)みたいなやつ
    • データベースがファイルデータベース  - 初期実装がRDBじゃない  - ファイルロックが必要
    • ユーザーアクセスのタイミングでターン進行処理がある(定期実行バッチの代わり)
      • これをどうにかするのも面白そう
  • 確率的にエラーになる外部APIをなんとかするとかやりたい
    • 確率を入れると1分では運ゲー要素がでちゃって微妙かも
  • 外部コマンド呼び出し、初期は都度起動stdinから入力、stdoutから出力して終了
    • だけど起動しっぱなしでstdinからストリームで流しこむことができるようになっているのでそうすると起動コストが0になる
  • データベースがsqlite
    • 無闇に全部MySQL/PostgreSQLに持っていかないで、更新不要なmasterdataはそのままsqliteにすると複数台で参照がスケールするのでよいとか
    • マルチテナントデータベース(同一スキーマのDBが大量にある
      • 掲示板を作るたびにcreate databaseするとか
      • ベンチ開始時のinitの初期化が難しそう
  • 複数台かつ非同期が本質的な解決になる問題
    • Goで単にgoroutineで解決されないやつがいいな
  • 2014年頃に流行った技術スタックで「お買い得なサービスを買ってきたのねん、プロモするのねん」みたいなやつ
  • Railsとかに代表されるフルスタックなWAFで作って「さぁ軽くして…!」みたいなやつ
    • 移植する言語によってどんだけ重いWAFとかORMになるかみたいなのがあるから言語差激しそう
  • 既に十分速いんだけどさらに雑巾を絞りきらないといけないサービス
  • 予選の問題を解いてもらって高速化したものを機能追加しつつ本選にだして「さらに速くして!」

そしてこれらの要素を元に、具体的な問題を3案ほど立案しました。

【案1】マルチテナントな給与計算システム

  • ****(伏字) みたいなSaaS
  • 複数DBは初のお題
    • 3台あるので最初から分割した状態、1000個DBありまーす
    • 最初から水平分割
    • マルチテナント
      • テナントを作るとDB(SQLite)が1つ作成される
      • 複数台にするにはSQLiteをやめてMySQL / PostgreSQL にするか、ワールド単位で物理ホストを分配するか
  • 全体の管理用DBも1個ある (これはMySQL?)
    • 全テナント横断でクエリするなにか(検索とか?)がある
  • SaaSなのでアクティブユーザー人数課金の計算が必要
  • 更新した人のログが各テナントのDBに格納されていてすべてのテナントを収集する必要がある
  • 全テナントをロックかけないと総課金売上の数字に不整合が起こる
  • 全テナントに対してALTER TABLEが掛かる
  • SQLアンチパターンで言うところのマルチカラムアトリビュートなテーブルがある
    • 運営からのおしらせにタグカラムがあるみたいな
  • テナントごとにリクエストがflockで排他されることでデータ保護を保証する仕組みがある
    • SQLiteをやめても安易に複数台にするとロックがかからなくなって壊れる
    • 何か別の手段でロックするかDBのトランザクションをちゃんと使うか
    • 初期はテナント単位で丸ごとロック
    • ロックを細粒にすれば並列に動作できるようになる
  • テナントごとのアクセス量を公平にする必要がある
    • テナント単位でレスポンスに掛かった時間を積算、単位時間当たりの消費が多ければ回復までブロックされるみたいなの
    • レイテンシが大きいといっぱいアクセスできない = スコアが伸びない
  • 定期実行ジョブがある
    • ターン進行(n秒に1回)
    • 初期状態ではリクエストが来たタイミングで、処理されていなかったものがまとめて実行される
      • これをちゃんとバックグラウンド処理にすると表のリクエストを阻害しない(低レイテンシ)
      • バックグラウンドでcacheを作って配信とかすれば更に低レイテンシに
      • 負荷増加の要素としてターン進行が最初が10秒に1回程度だったのがどんどんリアルタイムに近くなっていく
  • 社員に入社順に連番を出す必要があり、しかも退職したら間を詰めないといけない

【案2】データの署名をするだけのマイクロサービス

  • テーマは「毎分cronをqueueにしてなんとかする」
  • ジョブキューになにを使うかなど選択肢は多そう
  • MySQLにインデックスやります、みたいな初手が意味なくなっちゃうから本選向きかも
  • POSTとGETのAPIが1個ずつあるだけ
    • 画面とかないので動作確認用 cli のバイナリ(リファレンス実装)が提供される
  • マイクロサービスなのでクライアントはいろんなやつがいる
    • リトライを exponential back off してくるやつ
    • 即時リトライでひたすら爆撃してくるやつ
  • 署名を作る方法は運営が提供したバイナリを動かすしかない
    • 署名には有効期限がある(生成後10秒とか?)
  • マイクロサービスなのでmetrics機能がある
    • 過去5秒間のtokenごとのリクエスト回数、署名発行回数、発行した署名のバイト数合計、署名が取得されるまでのレイテンシ(avg, p90, p95, p99)
    • POST時にログテーブルにエントリ作成
    • GET時にログテーブルを更新
    • 管理API /api/metrics で集計結果を取得できる

【案3】ユーザークラスごとの優先度をちゃんと処理するpriority qeueu的な何か

  • 案2と同様の「なにかを遅延処理して返す」サービス
  • 無料ユーザー、プレミアムユーザーがある
  • free のアクセスはお金にならないのでスコアにならない、premium のアクセスだけスコアとする
  • free には所定の rate limit を掛けてよい
    • 規約には書いてあるが手が回っていないので現状何もしていない、という設定が初期状態
    • やるつもりだけはあるので、アクセスログをDBに毎回書き込んでいる
    • 別の手段で適切な rate limit を掛けられるなら、DBに書き込む必要はない
  • premium は遅延処理のレイテンシに保証がある
    • 「n秒以内に完了していること」
    • 保証レイテンシを(error budget的な意味で一定以上)超過するとサービスとして障害 = 失格
  • freeユーザーはrate limitを食らうと一定確率でpremiumになってくれる
    • しかし、レイテンシがfreeの時と変わらないことを観測すると、premiumを解約してしまう
    • ちゃんとpremiumとfreeで優先度に差を付けてあげる必要がある
  • freeユーザーの処理も一定以上遅延すると障害
    • 単に放置するのではだめで遅くてもちゃんと処理はしないといけない
  • 負荷が増えすぎてレイテンシが保証できなくなりそうなら、premiumの登録をsuspendしてよい

最終的には

案1をベースに、題材を「マルチテナントISUCONリーダーボードのSaaS」とアレンジして着地しました。

実際の予選問題でやらなかったネタ

これは作問中にやりたかったけど、予選問題にしては複雑すぎる作りになるので、諦めたネタです。

  • SQLiteだけで実装されたシングルテナントアプリケーションがある
  • それをマルチテナントに改修するため、表にMySQLを使った管理アプリケーションを立てた(という想定)
    • テナント管理、認証などはこっちでやる
  • 2つのアプリケーションは独立したプロセスで動いていてHTTPで通信している

予選問題はSQLiteMySQLを1つのアプリケーションで共用していて、そんな作りのアプリは実際にはない!といわれたりしました。こういうふうに分割されていたら、それっぽいと感じてもらえたかも、とは思います。

ecspresso v2をもうすぐリリースします (v1.99をお試し下さい)

Amazon ECSデプロイツールのecspressoについて、もうすぐv2.0をリリースする予定ですのでお知らせします。先行してバージョン v1.99.x をプレリリースしていますので、利用できる方はお試し頂ければと思います。

もし不具合や不審な挙動を見つけた場合、GitHub issue や作者の Twitter (@fujiwara) へのメンションで教えていただけると嬉しいです。

github.com

CircleCI Orb をご利用の方に大事なお知らせ

まず最初に大事なお知らせです。CircleCI Orbを利用していて次の条件に両方合致している場合、v2が正式リリースされるとv2がインストールされるため、ワークフローが期待通り動かなくなる恐れがあります。

Orbのバージョンを@1.0.0 にあらかじめ変更するか、versionを明示的に v1.7.14 などと明示することをお勧めします。

v2の安定版がリリースされた後、version: latest を指定した場合の挙動は次のようになります。

  • fujiwara/ecspresso@0.0.15 (それ以前を含む): v2系の安定版をインストール
  • fujiwara/ecspresso@1.0.0 : v1系の安定版をインストール
    • ecspresso v2がリリースされた後、@2.0.0をリリース予定です
    • fujiwara/ecspresso@2.0.0: v2系の安定版をインストール

なお、GitHub Actionsでは、現時点で以下の挙動になっています。@v1では latest を指定してもv2系がインストールされることはありません。

  • kayac/ecspresso@v1 : v1系の安定版最新をインストール
  • kayac/ecspresso@v2 : v1.99系のプレリリース版をインストール
    • ecspresso v2がリリースされた後、v2系の安定版をリリースするように変更予定です

バージョンを明示的に指定した場合は、v1, v2のリリース状況に関わらず、そのバージョンをインストールします。

v1.99 の利用方法

GitHubのリリースページから、バイナリをダウンロードしてインストールできます。 Release v1.99.5 · kayac/ecspresso · GitHub

homebrewでは、brew install ecspresso@1.99 でインストールできます。これは ecspresso v1 と衝突するため、インストール済の場合は一旦 brew uninstall ecspresso で削除してから ecspresso@1.99 をインストールし直して下さい。

GitHub Actionsでは kayac/ecspresso@v2version: latest を指定するか、kayac/ecspresso@v1version: v1.99.5 など特定のバージョンを指定できます。

steps:
  - uses: kayac/ecspresso@v2
     with:
       version: v1.99.5 # or latest

CircleCI Orbでは、fujiwara/ecspresso@1.0.0 を利用して、version に特定のバージョンを指定して下さい。

orbs:
  ecspresso: fujiwara/ecspresso@1.0.0
jobs:
  install:
    steps:
      - ecspresso/install:
          version: v1.99.5

v1からv2への変更点

関連するISSUEとPull Request、コントリビューターの方などはドキュメントに記載しています。(もし漏れがあったら教えて下さい)

ecspresso/v1-v2.md at v2 · kayac/ecspresso · GitHub

非互換な変更

基本的にはv1.xと互換を保っています。ただし、次の非互換があります。

ecspresso create コマンドを廃止しました。deployコマンドでサービスを作成できます

事前のサービスの有無に関係なく、常に deploy を実行するだけでよくなりました。

なおこの挙動と Terraformの null_resource を組み合わせることで、TerraformによるECS関連リソースの作成とecspressoのデプロイまでが terraform apply 一発で完了できるようになります。具体的な方法については別途記事にする予定です。

rollback --deregister-task-definition フラグはデフォルトで真になりました

rollback済のタスク定義を残しておきたい場合(あまりないと思います)、--no-deregister-task-definition を指定できます。

ecspresso render コマンドは、レンダリング対象を指定するフラグの代わりに、引数で指定するようになりました

これまではフラグを何も指定しない場合、空の出力が得られるが正常終了する、という不思議な挙動だったため、必須の引数を指定するように変更しました。複数指定も可能です。

v1

$ ecspresso render --task-definition

v2

$ ecspresso render task-definition service-definition

ecspresso verify コマンドは、ELB関係のAPI呼び出しを taskExecutionRole で行いません

verify時のELB関係のAPI操作を、これまではタスク実行ロールで行うことがありました。

ECSタスク実行ロールには一般的にはELBへの権限は必要がなく、権限を最小化したい場合に不便なため、mタスク実行ロールによるELB API呼び出しは行わないようにしました。常に現在のecspressoを実行しているIAM権限で呼び出します。

ecspresso diff --unified フラグはデフォルトで真になりました

短くなるので便利ですね。

ログメッセージの出力でターミナルを同一行で書き換えたり、メッセージを行折り返ししなくなりました

v1ではデプロイ中の進行状況を表示するためにターミナルの同一行を書き換えたり、その都合でメッセージを固定文字数で折り返したりしていました。しかし、

  • ecspressoはCI/CD環境で実行される場合も多く、その場合は同一行書き換えに意味がない
  • 行が折り返されるとID (task IDとか)をコピーするのに不便

などの理由があったため、ログメッセージは単純に行を送って出力するように変更しました。

設定ファイルでの filter_command 指定は非推奨になりました

filter_commandは、ターミナルでタスクなどを選択する際のキーボード操作をアシストする peco, fzf などのツールを指定する設定です。これは個人向けの性格が強い設定のため、複数人で共通で使用する設定ファイルに定義するのは相応しくないと判断しました。

代わりに 環境変数ECSPRESSO_FILTER_COMMANDを使用して下さい。

設定ファイルの記述も引き続き動作しますが、警告が表示されます。ECSPRESSO_FILTER_COMMANDが指定されている場合はそちらが優先されます。

機能強化

v2で対応予定、としてお待たせしていた機能追加が含まれています!

複数のtfstate読み込みをサポートしました

v1では、読み込む対象のtfstateは1つだけでした。

v2では、プラグイン定義時に func_prefix という設定を指定することで、tfstateごとに呼び出す関数名を変更できます。

# ecspresso.yml
plugins:
   - name: tfstate
     config:
       url: s3://tfstate/first.tfstate
     func_prefix: first_
   - name: tfstate
     config:
       url: s3://tfstate/second.tfstate
     func_prefix: second_

このように定義すると、テンプレートで first_tfstate で呼び出すと s3://tfstate/first.tfstate を対象に、second_tfstate を呼び出すと s3://tfstate/second.tfstate を対象にしてtfstate参照を行います。

[
  "{{ first_tfstate `aws_s3_bucket.main.arn` }}",
  "{{ second_tfstate `aws_s3_bucket.main.arn` }}"
]

設定ファイルに Jsonnet が使用できるようになりました

v1では、タスク定義ファイルとサービス定義ファイルがJSONもしくはJsonnet、設定ファイル(ecspresso.yml) がYAMLのみでした。

v2では、設定ファイルに JSON, Jsonnet, YAML のいずれも使用できます。

既存のECSサービスを設定ファイル化する ecspresso init コマンドで --jsonnet を指定することで、全てのファイルがJsonnetで生成されるようになりました。

設定ファイルのデフォルト値は ecspresso.yml のままです。ただし ecspresso.yml が存在しない場合、ecspresso.json, ecspresso.jsonnet を探して見つかったらそれを使用します。

各種ファイルを解釈して標準出力に書き出す ecspresso render コマンドにも --jsonnet フラグが追加されました。

設定ファイルでCodeDeployアプリケーション名とデプロイグループ名を指定できます

CodeDeployを利用する場合、ecspressoはデプロイ対象のECSサービスに関連するCodeDeployアプリケーションとデプロイグループを自動で発見します。

しかしCodeDeployに大量のアプリケーションが存在する場合には、この発見処理のための大量のAPI呼び出しが原因でスロットリングエラーが発生することがありました。

設定ファイルにあらかじめアプリケーション名とデプロイグループ名を明示することで、API呼び出しを減らすことができます。

# ecspresso.yml
codedeploy:
  application_name: myapp
  deployment_group_name: mydeployment

CodeDeploy でのデプロイ中にプログレスバーが表示されます

デプロイの進行自体はecspresso以外の手段(マネージメントコンソール)で行う必要があります。ecspressoでデプロイ完了を待っている状態で、トラフィックの移行状況がターミナルのプログレスバーに表示されるようになりました。

ecspresso verifyでコンテナイメージのplatformも確認するようになりました

タスク定義でCPUアーキテクチャにARM64を指定している場合に、verifyコマンドでimageを検証する場合にArm64用のイメージが存在するかを確認するようになりました。

SSMパラメータストアを参照するプラグインが追加されました

SSMパラメータストアの値を直接参照できるようになりました。

# ecspresso.yml
plugins:
  - name: ssm
{
  "string": "{{ ssm `/path/to/string` }}",
  "stringlist": "{{ ssm `/path/to/stringlist` 1 }}",
  "securestring": "{{ ssm `/path/to/securestring` }}"
}

秘匿値はタスク定義のsecretsを利用してタスク実行時に取得するべきですが、特に秘匿しなくてもよい値はタスク定義に直接埋め込むことができます。

その他の変更点

見た目の機能の追加/非互換よりも、内部的な変更が実は多かったりします。

AWS SDK Go v2 に切り替えました

AWS SDK Go v1も現時点ではEoL予定はなく引き続き更新されていますが、今後のことを考えて依存ライブラリも含めて v2 への移行を行いました。

CLI option parserを変更しました

alecthomas/kingpin がメンテナンスモードになったため、同作者の alecthomas/kong へ移行しました

単体タスク操作関連のコードを ecsta に切り出しました

ecspresso には tasks, exec など、ECSタスク単体を相手に操作する便利なコマンドがいくつかあります。

これらの操作を ecspresso 管理下以外のタスクにも行いたい需要があったので、ecsta というCLIツールを作成しました。どうぞご利用下さい。

github.com

ecspresso側のコマンドはv1と変更ありませんが、内部的にはecstaをライブラリとして呼び出すようになっています。

まとめ

  • ecspresso v2をもうすぐリリースします
  • v1.99.x を試していただいて、フィードバックを頂けると嬉しいです
  • CircleCI Orb @0.0.15 かつ version: latest を指定している方はご注意下さい

「達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践」を執筆しました

「達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践」という本を6名の共著で執筆しました。技術評論社さんから、2022年6月4日発売予定です。電子版もでます。

gihyo.jp

Amazon はこちら。

タイトルの通り、ISUCON で出題されるようなWebサービスを例にして、Webサービスのサーバーサイドパフォーマンスチューニングを指南する内容です。通称「ISUCON本」と呼んでください。

2020年の末に、技術評論社さんからWebサービス高速化 × ISUCONに関する書籍を執筆しませんか、と藤原までお誘いをいただいたのが発端でした。

書きたい気持ちはあったものの、内容的にとても一人では書き切れる気がしなかったので、とあるISUCON関連のSlackチャンネルで一緒に書きたい人はいませんか、と募集しました。結果、6名での共著という形で本にすることができました。

執筆者のプロフィールは以下です。大変豪華なメンバーで、"ISUCON"がゲシュタルト崩壊を起こすレベルで埋まっておりますね…

藤原俊一郎 @fujiwara (2011年より面白法人カヤック。SREチーム所属。ISUCON優勝4回、出題3回)

馬場俊彰 @netmarkjp (株式会社X-Tech 5取締役CTO、株式会社iCARE技術顧問。ISUCON第一回にプロジェクターを持ち込んで参加しSELinux=Enforcingで入賞)

中西建登 @whywaita (株式会社サイバーエージェント所属。ISUCON8にて史上初の学⽣総合優勝)

長野雅広 @kazeburo (さくらインターネット株式会社所属。ISUCON1、ISUCON2、ISUCON9予選で問題作成。参加者として優勝も予選落ちも経験)

金子達哉 @catatsuy (株式会社PR TIMES開発本部長CTO。ピクシブ・メルカリを経て現職。ISUCON9予選・ISUCON6本選出題)

草野 翔 @rosylilly (宇宙海賊合同会社代表、株式会社ハンマーキットCTO、株式会社 Tech Consiglie CTO、プロモータル株式会社相談役、IPTech特許業務法人技術顧問。ISUCON9優勝、ISUCON4とISUCON10出題)

目次

  • 1章 チューニングの基礎知識 (@netmarkjp)
  • 2章 モニタリング (@whywaita)
  • 3章 基礎的な負荷試験 (@fujiwara)
  • 4章 シナリオを持った負荷試験 (@fujiwara)
  • 5章 データベースのチューニング (@kazeburo)
  • 6章 リバースプロキシの利用 (@catatsuy)
  • 7章 キャッシュの活用 (@catatsuy)
  • 8章 押さえておきたい高速化手法 (@catatsuy)
  • 9章 OSの基礎知識とチューニング (@whywaita)
  • 付録A private-isuの攻略実践 (@fujiwara)
  • 付録B ベンチマーカーの実装 (@rosylilly)

どんな人向けの本か

通称「ISUCON本」ですが、ISUCONに特化した本ではありません。一般的なWebアプリケーションやミドルウェアに対して、パフォーマンスを改善するためにはどうしたらいいかをまとめた本になります。

ただしパフォーマンスチューニングを説明するための例は、共著者の @catatsuy さんが公開している private-isu という社内ISUCONの問題を採用しています。そのため「ISUCONに学ぶ」というサブタイトルになっているわけですね。

github.com

本書「はじめに」からの引用です。

ISUCONは技術のコンテストですが、そこで題材として提供されるWebサービスは、通常のWebサービスと同様の技術で作られています。つまり ISUCON での高速化手法は、業務で作成されるような 通常のWebサービスにも適用できるのです。本書は、次のような方にお勧めします。

  • Webサービスを開発、運用しているが動作が重くて困っている
  • これまでWebサービスの速度について深く考えたことがなかった
  • ISUCONにこれから出場してみたい
  • ISUCONに出場したことはあるが、よい成績を収められなかった

本書を書いてみて改めて実感したのですが、Webサービスのパフォーマンスチューニングには関わる技術が非常に多く、多数のコンポーネントに対する知識が必要です。

しかも、これも本書内で再三触れたことなのですが、システム全体の中でボトルネックになっている1点見つけ出してそこを改善しない限り、他の何をやっても大きな性能の向上は見込めないという厄介な性質があります。

そのため、小手先のチューニングテクニックというよりは全体的な概念を掴むこと、ボトルネックを見つけてそこに向き合うことを重視してほしいなという気持ちで執筆しました。

この本に書いてあることを理解していれば、仮にISUCONに参加しても手も足も出ないことはないと思います。予選通過ラインに近いところまではいけると思いますが、実際に予選を突破できるかどうかはさすがに運次第、というところでしょうか(著者陣でも予選を突破できないことがあるのでこれは仕方ないですね)。

個人的な見所

こういってはなんですが、本編もですが付録も面白いです!なにしろ付録だけで70ページ以上あります。

付録Aは本書内で例として取り上げた catatsuy/private-isu を実際に段階を踏んで攻略してみた、いわゆる攻略記事になっています。初期スコアの650点から最終的に32万点まで500倍に性能アップするところはもちろんですが、最後にとあることをして1万点に失速するところまで含めて、なかなか面白い読み物になっていると思います。

付録Bは、ISUCON のベンチマーカーを書くためのフレームワーク isucandar を使って実際にベンチマーカーを書くための勘所を、isucandar の作者が解説しています。普段から ISUCON の作問をする人はそれほどいないと思いますが、社内 ISUCON を開催するとか、本物 ISUCON の作問が突然降ってきたみたいな場合には必読です。

github.com

謝辞

執筆のお話を頂いた技術評論社の皆様、共著者の皆様、レビューに協力していただいた皆様、ISUCONを運営しているLINE株式会社の皆様(特に@941さん)、大変ありがとうございました。

(お断り) 本書と ISUCON 12 との関連

自分は今年の ISUCON 12 の作問担当でもありますが、作問の話自体はこの本がほぼ書き上がってから来たため、本書の内容と ISUCON 12 で出題される問題の内容に、関連は特にありません。念のため。

ecspresso tasks --trace でECSタスクのイベントログを一括表示する tracer を使えるようにした

メリークリスマス!(フライング)

AWS Containers Advent Calendar 2021 6日目の記事です。


先日、github.com/fujiwara/tracer という結構便利なやつを作りました。Amazon ECS タスクに関連するイベントとログを一括で出してくれる、シンプルな CLI です。

github.com

解説記事はこちら(会社のアドベントカレンダー)に書きましたのであわせてご覧下さい。

techblog.kayac.com

tracer は単体の CLI で動作するのでどのような手段で管理している ECS タスクにも使えるのですが、これを ecspresso からも呼び出せると便利では? と思い立ったのでそうしました、というのがこの記事です。

ecspresso tasks --trace

tracer は ECS クラスタ名とタスクIDを指定して動作します。ecspresso には tasks コマンドがあり、これは ecspresso が管理しているクラスタからタスクを特定する機能があります。つまり、

$ ecspresso tasks --trace

として、ecspresso 側でタスクを特定して、そのIDをtracerに引き渡して実行すれば完成です。実質10行ちょっとのpatchで実装できました。よかったですね。(tracer のコードを package として ecspresso 内部に組み込んであるので、tracer コマンドは不要です)

動作デモはこんな感じです。

ecspresso v1.7.2 で使えます

先ほどリリースした ecspresso v1.7.2 から使えます。

github.com

どうぞご利用ください。

ecspresso v1.7.0 をリリースしました

3ヶ月ぶりの ecspresso リリースのお知らせです。

Amazon ECS デプロイツール ecspresso の v1.7.0 をリリースしました。

github.com

新機能

定義ファイル (サービス / タスク / run --overrides-file) で Jsonnet の直接読み込みをサポートしました

これまで JSON 形式が要求されていた各種定義ファイルで、Jsonnet の読み込みがサポートされました。

ファイルの拡張子が .jsonnet の場合、ecspresso 内部で先に Jsonnet を処理して JSON に変換後、これまで通り (テンプレート記法による env, tfstate の展開などが行われて) 読み込まれるようになります。

JSON は人が読み書きするのは不便なことが多いため、 ecspresso advent calendar 2020 day 20 - Jsonnetによる定義ファイル生成 の記事などで Jsonnet 形式をお勧めしていました。これまでは事前に自分で jsonnet コマンドを実行して JSON に変換してから ecspresso を起動する必要がありましたが、その処理が不要になりました。

github.com/google/go-jsonnet をライブラリとして組み込んでいるため、jsonnet コマンドは不要です。CI/CD 環境でも jsonnet のインストールが不要になって嬉しいですね。

各種定義ファイルで使用できない key を発見すると警告を表示するように

ecspresso が使用するタスク/サービス定義のファイルは aws-sdk-go で定義している構造体にマッピングされます。

これまでは JSON ファイル中の要素名(key) を間違った場合など、構造体に対応するものがない場合は、単に無視されていました。そのため要素名を typo した場合など、気が付かないうちに要素自体が省略されてしまい、意図しない構成がデプロイされてしまうことがありました。

v1.7 では、構造体に対応する要素が存在しない場合は warning を表示するようになりました。

f:id:sfujiwara:20211101152937p:plain

タスク定義リビジョンを取り扱う revisions, deregister コマンド追加

revisons コマンドは、存在しているタスク定義のリビジョンを表示できます。 IN USE の欄には、現在サービス内で利用しているタスクの情報が表示されます。単体タスクが使用している場合は RUNNING task, サービスに設定されているものは *** deployment となります。

$ ecspresso revisions
ecspresso --envfile envfile revisions
|        NAME        |       IN USE       |
+--------------------+--------------------+
| ecspresso-test:386 |                    |
| ecspresso-test:387 |                    |
| ecspresso-test:388 |                    |
| ecspresso-test:389 | ACTIVE deployment  |
| ecspresso-test:390 | RUNNING task       |
| ecspresso-test:391 |                    |
| ecspresso-test:392 | PRIMARY deployment |

古い revision を登録解除する、deregsiter コマンドも追加されました。

deregister コマンドでは以下のオプションのどちらかが必要です。

  • --revision=リビジョン番号 を指定すると指定したリビジョンを登録解除します
  • --keeps=保持する数 現在利用中のものを除いて指定した数だけリビジョンを保持し、古いリビジョンを登録解除します
    • 「現在利用中のものを除いて」= revisions コマンドで IN USE の欄が空欄のものが対象

revision が増えすぎて困る!とか、間違って動作しない revision を登録してしまったのでうっかり使わないようにしたい、という場合にご利用ください。

Fargate Windows Containers 対応

先日リリースされたばかりの、Fargate での Windows Containers の起動に対応しました。

aws.amazon.com

タスク定義の image, runtimePlatform を適切に指定すると起動できます。

// せっかくなので Jsonnet 形式で
{
  image: 'mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019',  
  runtimePlatform: {
    operatingSystemFamily: 'WINDOWS_SERVER_2019_CORE',
  },
  // ....
}

f:id:sfujiwara:20211101155543p:plain

バグ修正

サービス定義の desiredCount が設定されている(未定義ではない) 場合に create を行うと、その設定を無視して desiredCount=1 を設定してしまうバグを修正しました。


おしらせ

ecspresso handbook で、コマンドリファレンスの章も追加で無料公開しました。

現在 v1.5 対応版ですが、近日中に v1.7 対応に更新予定です。

zenn.dev

ISUCON11で優勝しました

勝った!!引退!!!

取り乱しました。

ずっと参加してきているWebアプリケーションパフォーマンスチューニングコンテスト ISUCON、ISUCON11本選にチーム「fujiwara組」で参加して、優勝しました。

ISUCON11 まとめ : ISUCON公式Blog

fujiwara組は初回のISUCONから参加している老舗チームで、自分(fujiwara)以外のメンバーは都度入れ替わっているのですが、今回はISUCON10の時と同様に会社(面白法人カヤック)の同僚である acidlemon と macopy とのチームです。

f:id:sfujiwara:20210921113931p:plain
チーム紹介スライド

過去に ISUCON1, 2, 5 で優勝しているので、6年ぶり4度目の優勝になりました。もう引退していいよね!(というか941さんに出禁って言われた気がする…)

やったこと

リポジトリはこちらです。

github.com

アプリケーションの変更は PR になっているので、それを順に見ると分かりやすいかと思います。 Pull requests · fujiwara/isucon11-f · GitHub

方針としては以下で、結果的には全員のやることがなくなってしまうことがなく、最後まで手を打ち続けられたのが勝因かなと思います。

  • fujiwara がインフラまわりの初期設定とチューニング、モニタリング、複数台構成を組むなどの足回りを整備
  • その間に acidlemon, macopy が大きめのアプリケーション改造に取りかかる
    • GetGrades の SingleFlight 化
    • JOIN はずし
    • 既読管理の Redis 化
  • fujiwara が細かいコードの改善を片付けて露払いする
    • zip生成での外部コマンド呼び出しを archive/zip 化
    • 1行だけ DB を引くような部分をオンメモリキャッシュ化 (メモリになければDBを見てメモリ(sync.Map)に載せる
    • 外部キーを全部やめる
    • echo(フレームワーク) の JSONSerializer を自作して goccy/go-json
    • 参照だけのエンドポイントで無駄にトランザクションを張っていたのをやめる
  • 大きめの改善を merge して伸ばしていく

fujiwara組が負けるときのパターンは、インフラ担当の自分がやることがなくなってしまって手が空いてしまうことが多いのですが、今回は3人がフル回転できてよかったですね。

個人的ハイライトは、archive/zip 化を決意して、10分で一発実装、ベンチ通過できたところでしょうか。AWS Lamba デプロイツールである lambroll を実装したときに Go での zip 生成は経験があったので、それをみながらスッとできました。いろんなコードを書いてみるものですね。

archive/zipにする by fujiwara · Pull Request #5 · fujiwara/isucon11-f · GitHub

スコア推移

やっていて、これだけ苦しい ISUCON は初めてかも知れません。何をしてもスコアに明確に響かない(けどサーバの負荷は僅かに下がる気がする)手を、最後には効くと信じて打ち込み続けるというのが本当にしんどい。

リアルでのパフォーマンスチューニングでは、明確に数字が上がらない施策を無闇に入れるのは避けるべきですよね。得られるパフォーマンスと、コードや運用の複雑さがペイしないと技術負債(しかもメリットなし)まっしぐらなので。競技中、ずっとしんどいしんどいって言ってました。

f:id:sfujiwara:20210921113651p:plain
スコア推移(最終上位3チーム+特別賞)

スコア推移を見ると、リーダーボードで自チーム以外のスコアが閲覧できなくなる17時直前に大きく伸びているのが分かります。

この時点で大きめの改善がマージ済みだったものの、db.SetMaxOpenConns(10) のままで MySQL の負荷が上がりきらないことに気が付いたので、 MaxOpenConns を 10 -> 20 -> 40 -> 60 と調整して最適値を探ってスコアを上げ、最後にダメ押しで今までずっと有効にしていた MySQL の slow query log を無効化して更に数字を伸ばした、というやつでした。

最終的には2位の NaruseJun もこの直後に10万点を超えて来ていたようなので、スコアが公開されている17時前に少しは盛り上げられてよかったかなと思います。

実際のスコアはリポジトリに保存してあります。 isucon11-f/scores.txt at main · fujiwara/isucon11-f · GitHub

最後に

近年の ISUCON ではもう年を食った社会人は若者や学生には勝てないんじゃないか、などといわれたこともありましたが、平均年齢38.7歳のおじさんチームでもまだまだ戦えることが示せてよかったです。

ただ個人的には、競技として洗練されて高度化していく方向が極まってきて、8時間以内に運営が想定したポイントを何個クリアできるか、あらかじめ設定された問題をどれだけ早く正確にこなすかのゲーム、になっているのが多少気になるところではあります。初期の頃の、誰がどんな手を繰り出して何をやってくるのか分からないみたいな空気を多少懐かしく思いつつ、まあこれも業界や時代の流れかなとは思いますが。

最後になりますが、楽しく快適な競技を提供して頂いた運営チーム、作問チーム、アドバイザリーの皆様ほか、スタッフの皆様にお礼申し上げます。本当に快適で楽しい ISUCON でした!(しんどかったけど…)

ISUCON11予選を4位で通過しました

今年もやってきました ISUCON の季節です。

ISUCON は 8 までは(出題を含めて)予選本選の全てに参加できていたのですが、9, 10 では連続で予選落ちしていました。10は予選であと1チーム上回れば、というところで及ばずでしたが、本選には並行参加チームというオープン参加扱いで参加させてもらい、ひっそり全体の3位相当のスコアを出していたんですよね。なのでチームのポテンシャルとしてはまだまだいけるはず!ということで ISUCON 11 にも10と同様、同僚の @acidlemon と @mackee_w (macopy) と参戦しました。

結果、全体の4位のスコアで予選を通過できました!

isucon.net

やったこととか

使用したのは Go 実装です。ミドルウェアは特に変更せず、最後まで nginx + MariaDB のままでした。 3台のうち1台を MySQL(MariaDB)専用、残りの2台を nginx + アプリケーションにしています。

コードの変更については、リポジトリを公開しています。

github.com

最終的に取りこまれた PR のタイトルとスコア、時刻をざっと並べるとこんな感じでした。

  • isu_conditionのorder by狙い #1 by mackee
    • 最初に slowlow をみて index を貼ったやつ。これで初期スコアから 16,168 へ (11:10:12)
  • いつものinterpolateParams=true #2 by acidlemon
    • Go の MySQL ドライバでデフォルトだと server side prepeare をするのをやめるやつ。17,918
  • debugのログをoffにする #3 by mackee
  • generateIsuGraphResponseが全部取るのは意味がないので1日の範囲だけにする #4 by mackee
  • trendは最新だけだからLIMIT 1 #6 by acidlemon
    • このあたりでスコア 30,000 程度 (まだ1台) (12:27:17)
  • isu_character_idx 追加 #7 by fujiwara
  • ログレベルをINFOにしてアクセスログも黙らせる #8 by mackee
  • 9割落としてるのはわかったので静かにしてもらう #9 by acidlemon
  • 9割おじさんから8割おじさんへ #10 by acidlemon
    • dropProbability という数値の調整でスコアの傾向を探っています
    • ここで3台構成にして 39,704 までアップ (13:00:43)
  • 9割に戻しつつpostIsuをbulk insert #11 by mackee
    • 48,346 (13:04:11)
  • initializeでalter #16 by acidlemon
  • image はファイルに書く#17 by fujiwara
    • isu.image カラムに入っていた画像バイナリをファイルで読み書きするよう改修
  • condition_true_countカラムに対応するstruct定義にする #18 by acidlemon
    • 1カラムに文字列で入っていたフラグを分解して true の数を別に持つ改修
  • true_countで絞り込み #19 by mackee
  • userテーブルへのfetchをキャッシュする #23 by mackee
    • オンメモリ化を進めている 73,570 (15:31:31)
  • 全量さばけるようになったので全部コメントアウト!#24 by mackee
    • ここで dropProbability=0 で通るようになったのでスコアが跳ね上がりました。一気に桁が上がって 374,366 (15:41:40)
  • isu nameをオンメモリ #26 by fujiwara
  • isuの画像をNULLで潰す #28 by acidlemon
  • isuを引いてくるところでcacheする #29 by mackee
    • このあたりで並行して Redis branch を作っていました(by acidlemon)がスコアが出ず、取り込みは諦め
  • インデックス1個にまとめて書き込み負荷を下げる #30 by mackee
    • 最後、isu_condition の primary key を変更して復号 pkey にしたのが効果を発揮して 684,959 (18:31:27)
    • id bigint AUTO_INCREMENTid bigint DEFAULT NULL (カラム自体は使わないけど残す)
    • PRIMARY KEY(jia_isu_uuid, timestamp)

再起動試験後、18:38:02 に 704,779 を記録して打ち止めでした。

f:id:sfujiwara:20210823173306p:plain
スコア推移(最終上位4チーム)

感想とお礼

出題はなるほど IoT で時系列データ(今までなかったのでそろそろ来そうだとは思った)、極端なところがなくて、いい問題でしたね。最終的には isu_condition の書き込みをどう捌くかに帰着するので、ここを上手く圧縮できたらもっとブレイクスルーできたのかな…と思います。時系列DBを使ったチームとかあったんでしょうか。

ベンチは大変快適で、例年ありがちな謎 fail に悩まされることがほぼなかったので、競技に集中できてとてもありがたかったです。近年、めきめきと ISUCON 運営力の向上を感じますね。頼もしい限りです。

運営の皆様、大変良い問題をありがとうございました。3大会ぶりの本選参加なので、本選でも楽しみたいと思います。よろしくおねがいします!