xargs と curl で並列にリクエストを投げる

備忘録。

パフォーマンステストで Vegeta を使うことがたまにある。

Vegeta attack - kntmr-blog

ただ、単純にたくさんリクエストを投げるだけなら xargs + curl が使えそう。あと、Vegeta だとレスポンスヘッダとかが見れないっぽいが、この方法ならなんとか見れそう。

以下は50並列でリクエストを投げるパターン。

$ seq 50 | xargs -n 1 -P 50 sh -c 'curl -I -XGET https://...'

現場からは以上です。

.a.i.SimpleAsyncUncaughtExceptionHandler : Unexpected exception occurred invoking async method ...

備忘録。

@Async で非同期に処理するメソッドの中で例外が発生した場合、表題のようなエラーが出力される。

2022-01-26 22:44:58.608 ERROR 3401 --- [         task-1] .a.i.SimpleAsyncUncaughtExceptionHandler : Unexpected exception occurred invoking async method: public void com.example.SampleService.execute()

デフォルトでは、SimpleAsyncUncaughtExceptionHandler クラスでハンドリングされるため。以下抜粋。

public void handleUncaughtException(Throwable ex, Method method, Object... params) {
    if (logger.isErrorEnabled()) {
        logger.error("Unexpected exception occurred invoking async method: " + method, ex);
    }
}

この例外をハンドリングする場合は、AsyncUncaughtExceptionHandler インタフェースを実装したクラスを用意する。

@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
        log.warn("Unexpected asynchronous exception!!");
    }
}
@Configuration
public class Config extends AsyncConfigurerSupport {
    @Autowired
    AsyncExceptionHandler asyncExceptionHandler;
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return asyncExceptionHandler;
    }
}
2022-01-26 23:12:00.616  WARN 3846 --- [         task-1] c.e.AsyncExceptionHandler  : Unexpected asynchronous exception!!

現場からは以上です。

2021年のふりかえりと2022年に向けて

謹賀新年。年末年始は帰省先で過ごしています。2021年はあっという間に終わってしまった感。

2020年のふりかえりと2021年に向けて - kntmr-blog

仕事に関しては、たぶんこれまでとは違った価値観を身に付ける必要がある気がする。とはいえ、すぐに身に付くようなものではないと思うので、小さなことからコツコツと。その中で自分ができること/やれることをしっかりやって自分の価値を出していければいいかなって。

転職1年のふりかえり から抜粋。

  • スクラムイベントを効果的にやりたい
    • 逆にできないものは無理してやらない判断をしたい
    • 手段が目的にならないように
  • 1on1 を効果的にやりたい
    • 雑談力が欲しい
  • メトリクス監視を習慣にしたい
  • SRE 力を身に付けたい
  • 仕事以外のインプットを増やしたい
  • 副業したい

仕事に関して、2022年にやることはだいたいこんな感じになりそう。

特に、自分の役割的にスクラムやプロジェクトマネジメントのスキル&経験を積むのが最優先かもしれない。前に PMBOKガイド 第7版 を買ったけど、積読しちゃってるので読もうっと。

あと、今年はフルフレックス&フルリモートが基本になりそうで、それ前提でオンボーディングやチームビルディングを考えないと。チームのコミュニケーションが増えるようにモブプロとかペアプロを活用していきたいところ。

リモート中心になるなら仕事の環境をもう少し整えたい。(今は WiFi の都合でリビングで仕事してるので、メッシュ WiFi とかにして別の部屋に移動したい)

今後、SRE のスキルを身に付けて Embedded SRE の役割を担えるようになりたい。というか、結局、AWS SAA の試験を受けずに2021年が終わってしまった...。あと、昨年後半は少し電車通勤を再開してその時間を利用してスタディサプリ ENGLISH を進めてたけど、今年はまたリモート中心になりそうなので進捗なくなりそう...。

以前のふりかえりでも書いたけど、最近、日頃の疲れからか、本を読んだりコードを書いたり情報収集する時間が減っている。こういうのは無理矢理にでも習慣にした方がよいと思うのでなんとかしないと。とりあえず何冊か積読している本を消化する。

2020年頃までは技術関連の情報を中心にキャッチアップしていた感じだったけど、昨年頃からは自分の役割的にチームマネジメント関連の情報収集が増えてきている気がする。

AWS CLI で CloudWatch メトリクスを取得する

調べる機会があったのでメモ。CloudWatch メトリクスは GetMetricData で取得する。

aws.amazon.com

今回は、10分間のメトリクスを取得する例。日時はオフセット付きでも指定可。(2021-12-24T09:00:00+09:00)

$ aws cloudwatch get-metric-data \
  --metric-data-queries '{JSON}' \
  --start-time 2021-12-24T00:00:00Z \
  --end-time 2021-12-24T00:10:00Z

--metric-data-queries に指定する JSON はこんな感じ。今回は、2つの RDS (RoleREADER/WRITER で分かれている) の CPUUtilization を取得し、Metric Math で平均 (AVG) を算出する。

それぞれの CPUUtilization は 60sec 間隔 ("Period": 60) で平均 ("Stat": "Average") を取っている。途中計算に利用するメトリクスは、"ReturnData": false にすることで最終的なレスポンスには表示されなくなる。

docs.aws.amazon.com

[
  {
    "Id": "m0",
    "Expression": "AVG([m1, m2])",
    "Label": "AverageCPUUtilization"
  },
  {
    "Id": "m1",
    "MetricStat": {
      "Metric": {
        "Namespace": "AWS/RDS",
        "MetricName": "CPUUtilization",
        "Dimensions": [
          {
            "Name": "DBClusterIdentifier",
            "Value": "production-cluster-a"
          },
          {
            "Name": "Role",
            "Value": "WRITER"
          }
        ]
      },
      "Period": 60,
      "Stat": "Average"
    },
    "ReturnData": false
  },
  {
    "Id": "m2",
    "MetricStat": {
      "Metric": {
        "Namespace": "AWS/RDS",
        "MetricName": "CPUUtilization",
        "Dimensions": [
          {
            "Name": "DBClusterIdentifier",
            "Value": "production-cluster-a"
          },
          {
            "Name": "Role",
            "Value": "READER"
          }
        ]
      },
      "Period": 60,
      "Stat": "Average"
    },
    "ReturnData": false
  }
]

レスポンスはこんな感じ。これを jq とかで Values の値を取ってきてごにょごにょすればいろいろできそう。

{
    "MetricDataResults": [
        {
            "Id": "m0",
            "Label": "AverageCPUUtilization",
            "Timestamps": [
                "2021-12-24T00:00:00+00:00"
            ],
            "Values": [
                4.780129741529112
            ],
            "StatusCode": "Complete"
        }
    ],
    "Messages": []
}

その他

date コマンドで現在日時を取得する。

$ date -u +"%Y-%m-%dT%H:%M:%SZ"

現在日時から10分前の日時を取得する。

$ date -u +"%Y-%m-%dT%H:%M:%SZ" -d "10 min ago"

現場からは以上です。

CloudFront のキャッシュを Lambda から invalidation する

調べる機会があったのでメモ。

前提

Lambda 関数はこんな感じで作成。IAM ロールは別途用意。

サンプルコード

今回は、invalidation するオブジェクトパスをパラメータで指定できるようにする。パラメータ未指定の場合はデフォルトのパス (/*) で invalidation を実行する。

CallerReference は冪等性を担保する仕組みのようで呼び出しごとに一意の値を渡せばよさそう。(今回は UUID とする)

import boto3
import uuid

client = boto3.client('cloudfront')

def lambda_handler(event, context):
    distribution_id = '{distribution_id}'
    items = event['items'] if 'items' in event else ['/*']
    resp = client.create_invalidation(
        DistributionId = distribution_id,
        InvalidationBatch = {
            'Paths': {
                'Quantity': len(items),
                'Items': items
            },
            'CallerReference': str(uuid.uuid4())
        }
    )
    print('create_invalidation success:')
    print(resp)

オブジェクトパスを指定して invalidation を実行する

オブジェクトパスをパラメータに指定して Lambda を テスト実行 する。

{
    "items": [
        "/path/to/foo",
        "/path/to/bar"
    ]
}

定期的に invalidation を実行する

設定 > トリガー で EventBridge (CloudWatch Events) のトリガーを設定する。ルールタイプをスケジュール式にして rate(10 minutes) とすると10分間隔で Lambda が起動する。(cron 形式でも指定可)

(補足1)

IAM ロールにはこんな感じの IAM ポリシーをアタッチする。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudfront:CreateInvalidation"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

(補足2)

AWS CLI で CloudFront のキャッシュを invalidation する。

$ aws cloudfront create-invalidation --distribution-id {distribution_id} --paths "/path/to/foo" "/path/to/bar"

現場からは以上です。

イミュータブルでゆこう に参加した #現場から学ぶモデル駆動設計

イミュータブルでゆこう に参加しました。簡単に所感をまとめます。

modeling-how-to-learn.connpass.com

所感

イベントとリソースを区別したりイベント (コト) に注目するというのは、頭では分かっていても実際に設計するときにちゃんと実践できるだろうか...。どうしてもヒトやモノというか、UI やテーブル設計に意識が向きがちなので、このあたりを転換できるようにしたいです。

最近、イベントデータを集約する処理でレイテンシが悪化して問題になる事例があった (というか自分が作り込んだ...) ので、スナップショットを取る方法は試してみたいところ。

以下、メモから抜粋。

イミュータブルデータモデルの極意

www.slideshare.net

  • 場合分けを形式知になっていない
  • data, inform, information
    • データは事実の集合
    • データだけでは意味をなさない
    • 選択/加工して知識を取り出す > information
  • data > event, resource
  • event (コト)
    • 日時属性を持つ
    • 時系列の並びが重要 (非対称性)
    • 例) 会員
  • resource (モノ)
    • 日時属性は持たない
    • ライフサイクルに伴って属性が変化する
    • 属性が変化しても identity は変わらない
    • 例) 会員登録
  • イベントによってリソースが生成/更新/削除される
  • イベントを永続化するかどうかは業務設計による
  • イベント-リソース
    • イベントの7W3Hがリソースと関連を持つ
  • リソース-リソース
    • 親子関係/依存関係
    • ライフサイクルが同じかどうか
    • 依存/非依存は業務の制約次第
  • イベント-イベント
    • 時系列, 並びが変わると関係が変わる
    • イベントデータを更新しないように設計する
    • 例) 注文-請求
      • 注文イベント生成前に請求イベントは存在しない
      • 注文イベントに請求の属性は持たせない
  • サブタイプ
    • 共通の性質をスーパータイプに持たせる
    • 差分の性質をサブタイプに持たせる
    • リソースのサブタイプは区分で分類する (区分をスーパータイプに持たせる)
  • イベントが親子構造を持つケース
    • 親子のイベントをまとめてイベントとする
    • 子のイベントに後続のイベントが関連付くこともある
    • 子のイベントが日時属性を持っていなくてもイベントとして扱う
  • 一連のイベントのステータス
    • ロングタームイベント
  • リソース同士を関連付けるイベント
    • リソース間の関連と、それに関するイベントは区別する
    • 例) 所属, 配属イベント
  • どのイベントを記録として残すか
    • リソースの生成/変更/削除 > 変更履歴
    • イベント発生から次のイベントが生成される > 業務フロー
    • 非依存のリソースの関連付け/解消 > 業務イベント, 操作履歴
  • お金を生むもの, 記録がないとお金を失うリスクがあるもの
  • イベントは変更してはならない (事実が失われてしまうので)

あとで読む

ドメイン駆動設計とイミュータブルデータモデルの素敵な関係

  • 必ずイミュータブルにする
  • 閉じた操作
    • 引数の型と戻り値の型はそのクラスの型に閉じる
  • withメソッド (setterの代わり)
    • 不変なオブジェクトを元に別インスタンスを作成して返す
    • 部分的な変更
  • イベントリポジトリ/集約ファクトリ
    • 事実の記録と集約の構築は非対称
    • 集約は永続化しない
    • 集約を変更する際は先に事実を記録してから集約インスタンスを生成する
    • 同じ事実からは同じ結果/集約が返る (一貫性)

ドメインイベントの観点から再考するソフトウェア設計

  • ドメインイベントは過去に起きたドメイン上の出来事
  • Event Sourcing に限らない
  • ドメインイベント = 事実
  • 事実 = 事態 + 成立
  • コトに注目する
    • コトはヒト/モノ同士を関連付ける
    • 時間軸に沿って前後関係を持つ
  • ヒト/モノから分析しない
  • コトを手掛かりに分析する
  • Event Sourcing
    • イベントからステートを作り出す
    • applyEvent
  • イベントは追記のみ (更新はしない)
    • ロックがないのでスケールしやすい
  • 非同期連携しやすい
  • 大量イベントをリプレイするとレイテンシが悪化する
    • 一定のタイミングでスナップショットを取る
  • Event Storming
    • コトからモノを探索する
    • イベントはコマンドが受理されたときに生成される
  • イベントのデータストアは RDB である必要はない
    • NoSQL とか...
  • Akka-ES モデル
    • 集約アクター内のオブジェクトグラフがデータソースとなる
    • シャーディング

その他

  • デフォルトは不変
  • 可変は局所的にする
  • イミュータブルはテストしやすい
  • ビジネスロジックでは戻り値を返さないことはない
  • void を使うのは外部の書き込みくらい
  • NOT NULL, 追記のみ (UPDATEしない)

ふりかえり

転職して1年。転職3ヶ月以降の期間をふりかえってみたり。

kntmr.hatenablog.com

やったこと

前半は主にマーケ寄りの施策を担当し、いくつかの新機能をリリース。

夏頃にエンジニアチームの体制が再編成されて、toB 寄りの開発チームのリーダーを担当。とある開発プロジェクトで PM として仕様を決めたり設計/開発をしつつ、チームメンバーのタスクをサポートしている。チーム参画直後に担当したプロジェクトでは、リーダーとしての立ち回りに悩みつつ、仕様検討や設計/開発をやっていたのでそこそこ心身疲弊した...。

あと、問い合わせ対応や打ち合わせ、チームメンバーのタスクのレビューなどが増えて、集中して作業できる時間があまり取れないのが悩みどころ。特に問い合わせ対応に関しては、担当する事業領域が変わったことでドメイン知識が足りず苦労している。

一応、チームはスクラムの体制になっており、自分はスクラムマスターのような役割を担わないといけないかもしれないが、スクラムイベントがちゃんと回せていない気がする...。もともと稼働していたチームに自分が合流した形になるので、最初は「郷に入っては郷に従え」みたいなスタンスでやってきたが、一度立ち止まって改めてチームビルディングしたいところ。バックログにも古くて優先度がよく分からないタスクが溜まってるし、ちゃんとリファインメントしたい。

これからどうする

  • スクラムイベントを効果的にやりたい
    • 逆にできないものは無理してやらない判断をしたい
    • 手段が目的にならないように
  • 1on1 を効果的にやりたい
    • 雑談力が欲しい
  • メトリクス監視を習慣にしたい
  • SRE 力を身に付けたい
  • 仕事以外のインプットを増やしたい
  • 副業したい

最近、日頃の疲れからか、本を読んだりコードを書いたり情報収集する時間が減っている気がする。こういうのは無理矢理にでも習慣にした方がよいと思うのでそろそろ活動再開しないと...。現場からは以上です。