並行処理・非同期処理における信頼性設計の落とし穴
リソース後始末の try/finally は core 処理だけでなく setup も覆う外側に置く
4か月前
非同期 worker などで確保したリソース(lock、一時状態、markAsFailed 等の終端処理)を、core 処理関数(例: processTask)の finally だけで解放すると、その手前の setup(例: buildUserSession = ユーザーセッション構築、認可、テナント解決)で例外が出たときに解放されずリークする。
setup 段階でも throw は起きる(ユーザーが非 ACTIVE 化・テナント削除・一時的な DB エラーなど、タスク作成〜処理開始の間の状態変化)。後始末は setup を含む外側の try の catch/finally で担保し、解放操作は冪等(例: deleteByTenantIdAndJobId を何度呼んでも安全)にする。
これは「処理主体がロック所有権を再確認する」話とは別軸で、finally スコープが処理本体だけを覆い setup を覆っていない構造的な穴。cleanup の対象範囲を『確保した瞬間から』にそろえる。
使われない期限/heartbeat カラムは恒久ブロックの罠になる
4か月前
排他ロックのテーブルに expires_at / heartbeat_at カラムと index を足したのに、実際には誰も読み書きしていない状態(作成時は expiresAt=null、worker は heartbeat 更新も期限切れ奪取もしない、回収 cron も無い、検索クエリが期限を見ない)は危険。
一度でも worker 異常終了などで lock が stale 化すると、期限を見ない検索(findByTenantId 等)が古いロックを生きていると誤認し続け、そのテナント/資源が恒久的にブロックされる。
期限・heartbeat の仕組みを入れるなら、カラムだけでなく『書き込み(作成時 expiresAt 設定)・更新(処理中 heartbeat)・期限切れの奪取/回収(cron か作成時チェック)』まで実配線する。当面入れないなら『stale ロックは手動対応』を設計ドキュメントに明記する。dead schema(宣言だけで配線されないスキーマ)を放置しない。
HTTP 経路と worker 経路で共有する service は worker 側でトランザクション境界が消える
4か月前
同一の service メソッド(例: bulkUpdateItem)を HTTP 経路と非同期 worker 経路の両方から呼ぶとき、トランザクション境界の有無が経路で非対称になりやすい。HTTP 経路は Controller に付いた TransactionInterceptor 等のフレームワーク機構で原子的に包まれるが、worker 経路(SQS 受信 → processor → service)にはその機構が効かず、非トランザクションのまま複数の DB 書き込みが走る。
結果、途中失敗で partial write が残り、さらに task=FAILED にしてキューメッセージを即削除すると再実行もできず不整合が残留する。
worker 経路では DB 書き込み部分を明示的にトランザクション(executeInTransaction 等)で包み、commit 後に冪等な後処理(検索インデックス反映・集計・履歴記録)を回す設計へ寄せる。interceptor / middleware 由来の暗黙のトランザクションは別の呼び出し経路には効かない、という一般則として持つ。
非同期ジョブに認可を引き継ぐ時、権限主体を部分復元するとフェイルオープンで権限昇格になる――実行時に完全再構築する
4か月前
一括更新などをキュー worker で後追い実行する設計で、実行時の認可コンテキスト(権限主体:UserSession 等)を「作成時に payload へ権限スナップショットを保存して復元する」方式は危険。復元した権限主体が一部の権限フィールド(groupPermission / itemPermissions / roleType など)を欠くと、権限ヘルパーが「未設定=全許可(全レコード変更可)」とフェイルオープンし、作成時に通ったユーザーが worker 側で昇格した権限で実行できてしまう。対策は snapshot 方式を取り下げ、worker 側で task.userId / adminId から user / tenant / role の各 repository を引いて現在の完全な権限主体を再構築し、既存のサービスに渡す。加えて、一括操作の作成時に機能権限(MANAGE 等)と item 権限だけを検証し、指定された各レコードの更新可否(レコードアクセス)を検証しないと、同一テナント内の本来触れないレコードを指定して更新できる。認可は操作対象の各レコードのアクセス可否まで確定させる。権限ヘルパーがフェイルオープン設計の場合は、部分的な権限オブジェクトを渡すこと自体が昇格に直結すると意識する。