私が歌川です

@utgwkk が書いている

2024 🔜 2025

はじめに

2023年の振り返り記事はなかった、そしていろいろやっていたら2025年になっていたのであった。

仕事

2024/8からチームのテックリードになった。同じチーム・メンバーなので生活がガラッと変わるというものでもなくて、テックリード宛ての連絡が来るので対応したり、会議に出たりするぐらい。lintルールの整備とか、技術検討のフォーマットを決めておくとかそういうのもやったけど、これは別にテックリードになっていなくても提言・実行していただろう。

大きな機能開発がリリースされた。リリース直後はいろいろあったけどいったん落ち着いている。なかなか面白いものを作っていると思う。これに匹敵するものを探すのが大変なぐらい。あと、いわゆる「文化祭」の面白さがだんだん身にしみてきている。里浜ウミカだから……。

相変わらずGoとTypeScriptを主に書いている。実はGoがすごく好きというわけではなくて*1、仕事やらISUCONやらでよく触っているので高速に手が動く・わけが分かっている言語としてGoを使っている、というぐらい。TypeScriptは現代的なWebフロントエンド開発をやる上でもう欠かせないだろう。思ったよりもプログラミング言語に対して気持ちはなくて、道具としての面白さにハマることはあるけど、このプログラミング言語じゃなきゃダメという理由が今のところ手が動くこと以上にない。

とにかく面白いことだけをやっていたいね。忙殺され続けるのではなくて、それ自体が自分の興味を惹きつづけるようなことばっかりを……。

ゲーム

ブルアカのストーリーを読んでいる、と言えるかでいうとまあまあ怪しいけど、名目上は読んでいることに……なってないかも。まだいくらか読めてないメインストーリーがある。ストーリーを読むには心構えを作らないといけないのでコストが大きい。少し遠くからコンテンツを吸引しているぐらいがちょうどいい。後述するようにブルーアーカイブがきっかけで韓国語への関心が大きくなった年でもあった。

あとはだいたいスプラトゥーン3でサーモンランをやっていたと思う。こっちは無心でできるので気楽でいい。キケン度MAXを何度もクリアしているけど再現性があるかどうかは不明。けどずっと野良でやってるしこんなもんか?

旅行

月イチぐらいの頻度で東京に行ってるけど、それは置いといて……。

今年はパスポートを獲得して韓国に行ったのが大きいと思う。ブログにまだ書いてないけど、12月にも行った。そして2月末ぐらいにもまた行くことが決まっている。もともとはブルーアーカイブの二次創作を摂取するために韓国語を学びはじめたけど、pixivやTwitterを見ているだけでは飽き足らなくなって、渡航をキメているところ。

blog.utgw.net

国内で東京以外だとだいたいこんな感じか。東北方面のカバレッジが多少上がったと思うのでもうちょっと。

登壇

2024年の登壇は6件。40分トークだったり、100人単位の聴衆の前で話したり、というのが新しめポイントだろうか。登壇してないのも含めて遠征もけっこうしていた。全てを諦めて気絶するための場所に泊まる、という気持ちを持つと宿泊地の候補が少し増える、ということを改めて実感している。

speakerdeck.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

同人活動

ブルーアーカイブの二次創作を吸引しているのは相変わらず。これによって月の出費が大きく左右される。

C104, C105と本を出すことはできているけど、成果物には満足が行っていない。〆切直前になんとか書き上げて嵐のように入稿するのをそろそろやめたほうがよさそう。C106は技術書以外で書けそうなテーマができてきたので、そっちにシフトしてみる。

おわりに

抱負とかはないです。あるとすれば「2025年も飲みに行く」ぐらい? よろしくお願いします。

*1:社内ではたまに言っているけど、改めてブログにも書いておく

goquでMySQLのクエリを組み立てるときはBOOLEAN型の比較先にGoのbool値を使わない / goqumysqllinterというlinterを書いた

はじめに

これは Go - Qiita Advent Calendar 2024 - Qiita の記事です。

tl;dr

  • goquでMySQLのクエリを組み立てるとき、BOOLEAN型のカラムの値を比較するとき
    • Eq メソッドなどや goqu.Ex 型にGoのbool型の値を渡さない
    • IsTrue IsFalse メソッドを使わない
  • MySQLでは、BOOLEAN型のカラムを IS 演算子で比較するとインデックスが効かなくなる
  • したがって、以下のいずれかの方法を取るべき
    • 1 (真) もしくは 0 (偽) と比較する (整数値を使う場合)
    • goqu.L("TRUE") もしくは goqu.L("FALSE") と比較する (SQLのリテラルを使う場合)
  • これらのコードを検出するlinterを書いた

goquについて

goquはGoのクエリビルダです。SQLを文字列ではなくGoの式として組み立てることで、よくあるミスや文法エラーを回避したり、クエリの再利用性を高めたりすることができます。

goquでBOOLEAN型のカラムを比較するときの注意点

さて、goquでBOOLEAN型のカラムの値を比較して絞り込むようなクエリを記述することを考えましょう。

よくない例

「カラム col の値が TRUE である」という条件で絞り込むとき、普通に考えると以下のいずれかの書き方を試すと思います。

goqu.C("col").Eq(true) // Eqメソッドを使う場合
goqu.C("col").IsTrue() // IsTrueメソッドを使う場合
_ = goqu.Ex{"col": true} // goqu.Exを使う場合

先述した書き方はいずれも同じ条件式に変換され、実際にクエリを生成すると以下のようになります。

SELECT * FROM `tbl` WHERE `col` IS TRUE;

このクエリは一見正しそうだし、実行結果も意図したものになる (col の値が TRUE である行だけが取得できる) と思います。

が、実はパフォーマンスがよくありません。col カラムに対してインデックスが貼ってあっても、それがうまく使われないクエリが発行される可能性があります。

よい例

じゃあどうしたらいいかというと、以下の2つのいずれかの方法を使いましょう。

  • 1 (真) もしくは 0 (偽) と比較する (整数値を使う場合)
  • goqu.L("TRUE") もしくは goqu.L("FALSE") と比較する (SQLのリテラルを使う場合)
goqu.C("col").Eq(1)
_ = goqu.Ex{"col": 1}

goqu.C("col").Eq(goqu.L("TRUE"))
_ = goqu.Ex{"col": goqu.L("TRUE")}

MySQLにおける BOOLEAN 型は TINYINT(1) 型と同一であり*1TRUE FALSE はそれぞれ 1 0 のエイリアスです*2。整数値を使うことで、Eq メソッドや goqu.Ex 型を使って比較したときに = 演算子で比較されるようになります。

または、goqu.L 関数を使うことでSQLのリテラルとして比較する値を渡すことでも = 演算子を使うことを強制できます。

実験

以下のようなテーブルを用意して実験してみましょう (MySQL 8.4.3で実験しました)。

CREATE TABLE `tbl` (
  `id` BIGINT UNSIGNED NOT NULL,
  `is_active` BOOLEAN NOT NULL,
  PRIMARY KEY (`id`),
  KEY `is_active` (`is_active`)
) ENGINE=InnoDB;

このテーブルに1000000行のデータをINSERTします。INSERTするときに、is_active カラムの値がおよそ0.5%の確率で TRUE になるようにします。

今回は以下のような行数になりました。is_active = TRUE の行が5054行、is_active = FALSE の行が994946行あります。

mysql> SELECT is_active, COUNT(*) FROM tbl GROUP BY is_active;
+-----------+----------+
| is_active | COUNT(*) |
+-----------+----------+
|         0 |   994946 |
|         1 |     5054 |
+-----------+----------+
2 rows in set (0.12 sec)

このようなテーブルに対して、先ほどの例で取り上げたようなgoquが生成するSQLを模したクエリを流してみましょう。

mysql> SELECT COUNT(*) FROM `tbl` WHERE `is_active` IS TRUE;
+----------+
| COUNT(*) |
+----------+
|     5054 |
+----------+
1 row in set (0.12 sec)

is_active カラムの値が TRUE である行数を数えるのに0.12秒かかっています。EXPLAINしてみるとどうでしょうか。

mysql> EXPLAIN SELECT COUNT(*) FROM `tbl` WHERE `is_active` IS TRUE;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+--------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key       | key_len | ref  | rows   | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+--------+----------+--------------------------+
|  1 | SIMPLE      | tbl   | NULL       | index | NULL          | is_active | 1       | NULL | 998568 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+--------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

インデックスは使っていそうだけど、rows の値を見るとテーブルのほぼ全行をなめてしまっていることが分かります。おやおや……。

今度は IS 演算子ではなく = 演算子で比較するようにしてみましょう。EXPLAINも一気に見ます。

mysql> SELECT COUNT(*) FROM `tbl` WHERE `is_active` = TRUE;
+----------+
| COUNT(*) |
+----------+
|     5054 |
+----------+
1 row in set (0.01 sec)

mysql> EXPLAIN SELECT COUNT(*) FROM `tbl` WHERE `is_active` = TRUE;
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key       | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | tbl   | NULL       | ref  | is_active     | is_active | 1       | const | 5054 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)

今度は行数を数えるのに0.01秒しかかからなかったし、EXPLAIN結果のrows の値を見ると、なめる行数が5054行で済んでいます。こっちはインデックスがしっかり効いていると言えるでしょうね。

どうしてこうなった

以下のStackoverflowの回答が詳しいです。

stackoverflow.com

IS TRUE という式でカラムを絞り込むと、MySQLはカラムがtruthyな値であるかどうかをキャストして確かめます*3。なのでインデックスが効かないわけですね。

= TRUE と書くことで、定数との等号比較クエリになってインデックスを効かせられるようになります。

先ほどの実験で発行したクエリに対してEXPLAIN ANALYZE句で解析するとより分かりやすいですね。

mysql> EXPLAIN ANALYZE SELECT COUNT(*) FROM `tbl` WHERE `is_active` = TRUE\G
*************************** 1. row ***************************
EXPLAIN: -> Aggregate: count(0)  (cost=1012 rows=1) (actual time=1.8..1.8 rows=1 loops=1)
    -> Covering index lookup on tbl using is_active (is_active=true)  (cost=507 rows=5054) (actual time=0.15..1.45 rows=5054 loops=1)

1 row in set (0.01 sec)

mysql> EXPLAIN ANALYZE SELECT COUNT(*) FROM `tbl` WHERE `is_active` IS TRUE\G
*************************** 1. row ***************************
EXPLAIN: -> Aggregate: count(0)  (cost=200171 rows=1) (actual time=152..152 rows=1 loops=1)
    -> Filter: ((0 <> tbl.is_active) is true)  (cost=100314 rows=998568) (actual time=151..152 rows=5054 loops=1)
        -> Covering index scan on tbl using is_active  (cost=100314 rows=998568) (actual time=2.16..109 rows=1e+6 loops=1)

1 row in set (0.15 sec)

goqumysqllintを書いた

この問題を検出するためのlinterを書きました。

github.com

このlinterは単一のlinter・go vet -vettool 経由で・golangci-lintのプラグイン などさまざまな方法で使えます。

$ go vet -vettool=`which goqumysqllint` ./...

今のところテストコードにあるようなコードをlinterで検出できるようになっています。

github.com

おわりに

IS 演算子でBOOLEAN型のカラムを比較するとインデックスが効かない問題は id:SlashNephy に教えてもらいました。みなさまも気をつけましょう。クエリビルダやO/Rマッパーが発行するSQLを気にしてみる・EXPLAINの結果を注意深く見てみる といいかもしれません。

PostgreSQLやSQLiteだと問題にならないのでしょうか? 今後の課題とします。あるいは知ってる方がいれば教えてください。

Kyoto.go #56 に参加し、「ゆるやかにgolangci-lintのルールを強くする」という話をした #kyotogo

kyotogo.connpass.com

参加し、登壇しました。発表資料はこちら。

speakerdeck.com

開発中のプロジェクトに導入しているgolangci-lintのルールをどうやって強くするか、というテーマでした。golangci-lintの設定について検索すると、disable-allしてからenableするという主張とenable-allしてからdisableするという主張が両方出てきます。間をとって、徐々にgolangci-lintの設定を強くしていくという選択肢もあるよ、そのために使えるgolangci-lintの設定項目やディレクティブ (マジックコメント) を伝授するよ、ということをまとめて発表させてもらいました。「linterが足枷になってはいけない、linterとうまく付き合うべき」というのが自分の主張です。

スライドの後半は、golangci-lintに関する雑多な話題をせっかくなのでまとめておくか、と思ったらめちゃくちゃ長くなった、というやつです。「トーク枠 (10分+Q&A)」で登壇して、残り半分は懇親会のテーマということにするか、と考えていたのですが、トーク枠の運用をゆるふわにしていただけたので結局ぜんぶ話せました。10分トーク+LT みたいな感じですね。

有効にしたいけどしていないlinter

この話を発表資料に盛り込むのを忘れていたので、記事として放出します。

bodyclose

http.Request 型のbodyが正しくcloseされていることを検査するlinterです。

テストコードが大量に引っかかるのでどうしたものか……と思っていったんペンディングにしています。

nilnli

(T, error) という多値を返す関数が nil, nil (値もエラーも nil) を返していないか検査するlinterです。

gqlgenのGraphQLリゾルバ実装を中心に return nil, nil というコードが多いため、いったんペンディングにしています。とはいえゼロ値を返してしまうと意図しないバグの元になりやすいので、隙を見て有効にはしたいですね。

perfsprint

fmt.Sprintf 関数よりももっとパフォーマンスの良いコードを提案するlinterです。

気持ちはわかるけど引っかかるコードが多いのでいったん置いてあります。

prealloc

予めlenやcapを確保していないスライス変数の定義を検出し、よりメモリ効率よく定義できないか提案するlinterです。

引っかかるコードが多いのでいったん置いてあります。

testifylint

github.com/stretchr/testifyというテストライブラリに関するlinterです。

引っかかるコードが多いのでいったん置いてあります。

おもしろかった

GoのGCについて、GOGC などのパラメータがあること以外は実はあんまり知らなかったし、世代別GCじゃないんだ〜という学びがありました。懇親会でも鹿さんと話していたと思うけど、arenaを有効活用するにはデータ構造に前提*1を置く必要があるのが大変そうですね。まだここがボトルネックにはなっていない (つもり) ので回避している……。

speakerdeck.com

面白さでいうと、命名をリントする話がなんだかんだで一番おもしろかったかもしれません。その発想はなかった、というのと、けっこう実用性を考えてlinterが実装されている、というののバランスがよかったです。

speakerdeck.com

来年も参加します。朝は起きてコンディションを調整する必要があると思うので、また次回のオフライン会で……。

あと、三条の山ちゃんに何気に初めて入店した気がします。

*1:structのフィールドにポインタを含めない、time.Timeを使わない、など

ISUCONの感想戦を支えるEC2の自動開始・停止、そしてAWS Step Functions

はじめに

これは はてなエンジニア - Qiita Advent Calendar 2024 - Qiita 14日目の記事です。昨日は id:ymsecompose.yamlはマージができるし、YAMLのtagでその挙動をコントロールできる - 風に吹かれても でした。

id:utgwkk です。来週末に韓国に行くことが決まりました。それはさておいて……

先日 (12/8)、ISUCON14が開催されました。ISUCON14に参加したことの記録は以下の記事をご覧ください。

blog.utgw.net

話は変わりますが、みなさまはAWS Step Functionsを使っていますか? 今日はISUCONの感想戦を支えるStep Functionsについてお話しします。

できたもの

いきなりですが以下のステートマシンをご覧ください。どうですか?

できあがったものがこちらになります

このステートマシンは、ISUCONの競技用EC2インスタンスを起動・停止するためのステートマシンです。そんなのAWSコンソールからぽちぽち起動・停止して回ったらよくない?? と思われる方もいるでしょうが、まあそう言わずちょっと読んでいってください。

使い方

EventBridge Scheduler経由で起動する

仕事中はさすがに感想戦をやっている場合ではない (なぜなら仕事があるから)、けど仕事が終わったらすぐに感想戦に取りかかりたい、あとインスタンスの実行費用をケチりたい……そういうときはEventBridge Schedulerでステートマシンの起動をスケジュールしておくと便利です。

夜19時にEC2インスタンスを起動するスケジュールと、朝10時にEC2インスタンスを停止するスケジュールの両方を用意する、というのをやっています。定時を過ぎるとステージング環境が停止するのの逆みたいで面白いですね。

developer.hatenastaff.com

EventBridge Schedulerではスケジュールの終了日時を設定できるので、感想戦用にダッシュボード・ベンチマーカーが公開される期間が終わったらEC2インスタンスを起動するスケジュールを終了させる、という設定を入れることで、インスタンスの実行費用がかさむのを防げます。まあ最終的にはCloudFormationのスタックごと消すのがいいと思いますが……。

EventBridge+SNS経由で通知する

EventBridgeに以下のようなイベントパターンでSNSに通知する設定を用意します。ステートマシンの実行名を test- から始めた場合は通知しないようにしています。こうすることでテスト実行の通知がSlackに流れまくるのを防げます。

{
  "source": ["aws.states"],
  "detail-type": ["Step Functions Execution Status Change"],
  "detail": {
    "status": ["SUCCEEDED", "TIMED_OUT", "FAILED", "ABORTED"],
    "stateMachineArn": ["ステートマシンのARN"],
    "name": [{
      "anything-but": {
        "prefix": ["test-"]
      }
    }]
  }
}

SNSのサブスクリプションにAWS ChatBotを登録し、Slackチャンネルにステートマシンの実行を通知するようにしています。EventBridge→SNS→ChatBot という感じで、Slack通知をするための登場人物がやや多い気もしますが、こんなもんでしょうか。

AWS ChatBot経由で起動する

ISUCON用のプライベートなSlackチャンネルにステートマシンの実行通知を流しているのですが、そのメッセージから競技用EC2インスタンスを起動・停止できるようにしてあります。このようにしておくことで、チームメンバーがAWSアカウントにログインできなくても、Slackに流れてきたメッセージのボタンを押すだけで感想戦に取りかかれます。

gyazo.com

通知メッセージのハンバーガーメニューで開くモーダルからCreate a new custom action buttonを押すことで、任意のAWS CLIのコマンドを実行するボタンを設置できます。ステートマシンを実行するなら stepfunctions start-execution コマンドを記述して保存すればよいです。

ステートマシンの解説

定義

以下にこのステートマシンの定義を記述します。リトライ処理は省略しています。

{
  "StartAt": "DescribeInstances",
  "States": {
    "DescribeInstances": {
      "Type": "Task",
      "Parameters": {
        "Filters": [
          {
            "Name": "tag:aws:cloudformation:stack-id",
            "Values": [
              "ISUCON競技用インスタンスのCloudFormationスタックID"
            ]
          }
        ]
      },
      "Resource": "arn:aws:states:::aws-sdk:ec2:describeInstances",
      "ResultSelector": {
        "InstanceIds.$": "$.Reservations[*].Instances[*].InstanceId"
      },
      "Next": "DetermineNextInstanceStatus",
    },
    "DetermineNextInstanceStatus": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$$.Execution.Input.DesiredState",
          "StringEquals": "running",
          "Next": "StartInstances"
        },
        {
          "Variable": "$$.Execution.Input.DesiredState",
          "StringEquals": "stopped",
          "Next": "StopInstances"
        }
      ],
      "Default": "Nop"
    },
    "Nop": {
      "Type": "Pass",
      "End": true,
      "Result": {
        "Nop": true
      }
    },
    "StartInstances": {
      "Type": "Task",
      "Parameters": {
        "InstanceIds.$": "$.InstanceIds"
      },
      "Resource": "arn:aws:states:::aws-sdk:ec2:startInstances",
      "End": true
    },
    "StopInstances": {
      "Type": "Task",
      "Parameters": {
        "InstanceIds.$": "$.InstanceIds"
      },
      "Resource": "arn:aws:states:::aws-sdk:ec2:stopInstances",
      "End": true
    }
  }
}

やっていること

やっていることは簡単です。

  • DescribeInstances APIを叩き、ISUCON14の競技用EC2インスタンスIDを列挙する
  • ステートマシンへの入力に応じて、列挙した競技用EC2インスタンスを起動もしくは停止する

みどころ

ResultSelectorでインスタンスIDの配列を入手する

DescribeInstances APIの実行結果は、ものすごくざっくり書くと*1以下のような形式のJSONになっています。

{
  "Reservations": [
    {
      "Instances": [
        {
          "InstanceId": "i-0001",
          // 省略
        }
      ]
    },
    // 省略
  ]
}

Reservations フィールドの配列の各要素のオブジェクトにある Instances フィールドの配列の各要素のオブジェクトの InstanceId フィールドの値がインスタンスIDです。これを使ってEC2インスタンスを起動・停止するためのAPIを呼び出します。

ところで、EC2インスタンスを起動・停止するためのAPIにはインスタンスIDを複数渡して一括操作することができます。これを使いたいのでなんとか工夫します。

先に答えから書いておくと、ResultSelectorを使ってDescribeInstances APIが返すJSONからインスタンスIDの配列を得ることができます。$.Reservations[*].Instances[*].InstanceId というJSONパスを書いている箇所がそれに対応します。

こうして DescribeInstances stateの出力を以下のようなインスタンスIDの配列 (を持つオブジェクト) にできました。ここまで来たらやるだけですね。

{
  "InstanceIds": [
    "i-0001",
    "i-0002",
    "i-0003"
  ]
}
$$.Execution.Input でステートマシンそのものへの入力を得る

$$ から始まるJSONパスはcontext objectを指しています。context objectを使うことで、ステートマシンの実行全体にまつわる情報を任意のstateで参照できます。

ステートマシンの入力の DesiredState フィールドの値に応じてEC2インスタンスを起動するか・停止するか の分岐を書くために使っています。こうするとstateごとに元の入力を引き継ぐことを考えなくて済んで便利ですね。

途中のステートの出力を引き回したいときは変数が使えると思います。

なぜStep Functionsで書くのか

AWSの各種APIを順番に叩くのはLambda関数でもできるのでは? と思われるかもしれません。Amazon States Languageを頑張って覚えるよりは、馴染みのあるプログラミング言語で実装できる方が完成するのは早いと思います。また、実行頻度によってはStep FunctionsよりもLambdaのほうが圧倒的に安くなることもあるでしょう。それでもなぜStep Functionsで書くのか?

面白い

ステートマシンを書くときは、普段プログラムを書くときとはまた別の筋肉を求められます。入出力をうまく加工したり持ち回したりするにはどうするのか、状態遷移数を最小にできるか、など、意外とやりこみ要素があって面白いです。

データを加工するだけのLambda関数を使わない、みたいな縛りを入れるのもいいですね。入出力の加工をLambda関数なしで達成できると脳汁がドバドバ出ます。とはいえ、コストを最適化するにはLambda関数を通すほうが安くなる場合もあるだろうし、Step Functionsだけでは実現できないことも出てくると思うので、要はバランスですね。

言語ランタイムのEoLを気にしなくてよい

プログラミング言語には、処理系のEoLがつきものです。Lambda関数のランタイムだってそうです。長いこと放置して安定稼動していたLambda関数のランタイムもいつかは寿命を迎えます。処理系のバージョンアップに追従するコストを払わなければなりません。

Step Functionsで書いておくと、AWSのAPI呼び出しのためのLambda関数などは不要になります。このLambda関数のランタイムのバージョンが勝手に上がってもいいんだっけ……などと気にする必要はありません。

リトライ処理が簡単に書ける

DescribeInstances APIが内部エラーで失敗したときに、ジッターを入れつつリトライする、という処理をすらすら書けますか? Step Functionsの場合は、state定義に Retry フィールドを書くだけでよしなにやってくれます。

docs.aws.amazon.com

手札を知る

この節はStep FunctionsやAWSに限らない話題になるのですが、いろいろな道具を組み合わせて何がどこまで・どれくらいの労力で実現できるのか、を知っておくのがいいと思います。Step Functionsが銀の弾丸に見えているうちにいろいろ作ってみて、こんなに簡単に実現できるとか、実はStep Functionsを通す必要がなかったとか、そういう経験を積んでおくのがいいでしょう。

おわりに

とはいえ生のJSONでステートマシンを記述するのはさすがに大変です。ちょっと分岐して直列に進むぐらいならいいけど、分岐が複雑になったらJSONだといずれは認知の限界が来そう。デザインエディタで可視化して作りつつIaCしたくなったらJSONエクスポートするとか?

みなさまはどうやってステートマシンを書いていますか? AWS CDKでステートマシンを定義できるやつとかを活用するともっと見通しよく書けるのかな。

株式会社はてなでは、サーバーレス技術でかっこいいシステムを作りたい方を募集しています。

hatena.co.jp

明日の担当は id:mizdra です。

*1:ちゃんとした定義は DescribeInstances - Amazon Elastic Compute Cloud を参照してください