![]() |
![]() |
🟢 ずんだもん:
めたん、ぼくサーバレスとか、正直よくわからんド初心者なのだ!
🟣 めたん:
うんうん。知ってるわよ。
🟢 ずんだもん:
でも「Lambda → Lambda を同期でつなぐのは避けるべき」というこのQiita記事を読んだのだ。
そしたら「えっ、じゃあ従来のシェルスクリプトで長時間バッチ回してるのはなんでOKなのだ?」って素朴な疑問が湧いたのだ。
🟣 めたん:
なるほどね。
じゃあ今回は、その元ポストをきっかけに、
-
サーバレス → サーバレス 関数同士の同期がなぜ避けるべきと言われるのか
-
それと従来シェルの長時間バッチの何が違うのか
を調べて、会話形式でまとめてみたわ。
「なんもわからん状態」から「ちょっと見えてきたかも」くらいまでは一緒に行けるはずよ。
1. 元ネタ:なぜ Lambda → Lambda 同期は避けるべきなのだ?
🟢 ずんだもん:
まずさ、元のQiita記事では「Lambda同士を同期呼び出しでつなぐ構成は基本的に避ける」って言ってたのだ。
なんでダメって言われるのだ?
🟣 めたん:
ざっくり、理由は3つに分解できるわ。
-
コストが2重になる可能性
-
Lambda A が Lambda B を同期で呼び出すと、AはBが終わるまで待機する。
-
この“待ち時間”も実行時間として課金される。
-
B側も処理時間分課金されるので、1リクエストで実質“2つ分”のLambda時間を消費することになる。
-
-
同時実行数クォータを2つ食う
-
Lambdaにはリージョンごとの同時実行数制限がある。
-
A→Bを同期でつなぐと、1リクエストでAとBの2インスタンスを同時に占有する。
-
スパイクやバッチで大量実行が走ると、クォータを無駄に食べてスロットリングの原因になりやすい。
-
-
タイムアウト・リトライ設計がややこしくなる
-
呼び出し元と呼び出し先で、
-
タイムアウト値
-
リトライポリシー
を意識して揃えないと、 -
処理自体は成功しているのに、呼び出し元だけがタイムアウトで失敗扱い
-
同じ処理が二重実行される
みたいな事故が起きやすい。
-
-
🟢 ずんだもん:
なるほどなのだ。
「Lambda A が Lambda B を同期呼び出し」という構成だと、
-
お金も二重のリスク
-
同時実行枠も二重のリスク
-
障害時の設計もややこしい
っていう“三重苦”になりがちってことなのだ。
🟣 めたん:
そういうこと。だから元記事では、
-
単純に渡して終わりなら 非同期呼び出し
-
呼び出し量やリトライ制御をしっかりやりたいなら SQS 経由
みたいな構成をおすすめしていた、というわけね。
2. じゃあこの人は何を作ろうとしていたのだ?(推測)
![]() |
![]() |
🟢 ずんだもん:
ところで、そのQiita記事の人、実際どんな処理を作ろうとしてたのだ?
🟣 めたん:
あくまで推測だけど、バッチ処理の中に複数ステップがあって、それぞれを別Lambdaで実装したケースかもしれないわね。
例えば、典型的なサーバレスバッチだとこんな感じ。
-
Step1: 対象データの抽出
-
Step2: 対象ごとの処理(大量・並列)
-
Step3: 集計や後処理、通知
🟣 めたん:
最初の案としては、
-
Step1 が終わったら同期で Step2 Lambda を呼ぶ
-
Step2 が終わったら同期で Step3 Lambda を呼ぶ
みたいな「同期チェーン」を考えたんじゃないかしら。
でも、
-
コスト
-
同時実行数
-
タイムアウト/リトライ設計
の観点で「これはしんどいな」となって、
非同期や SQSベースの疎結合構成に寄せていった、という流れに見えるわ。
🟢 ずんだもん:
「処理がでっかいから仕方なく分割」じゃなくて、
「バッチの役割・ステップ単位で意図的に分割」って感じなのだな。
3. 「1機能1関数」と関数分割の考え方
🟢 ずんだもん:
ぼく、関数を作るとき「1機能1関数」って考えがちなのだ。
この人も、やっぱりそうやって分割したのだ?
🟣 めたん:
「1機能1関数」って言葉、どの粒度を“機能”と呼ぶかで結構変わるのよね。
ざっくり2パターンあるわ。
-
ビジネスユースケース単位
- 例: 「データ更新ジョブ全体」「レポート生成ジョブ」
-
そのユースケースを構成するステップ/責務単位
- 例: 「対象抽出」「更新プラン計算」「更新実行」「通知送信」
🟣 めたん:
Lambda界隈だと、後者、つまり「ステップ単位・責務単位での関数分割」がよく出てくる。
-
ステップごとにスケールのさせ方を変えられる
-
ステップごとにエラーハンドリングやリトライを変えられる
-
ステップごとに再実行しやすい
といった理由からね。
🟢 ずんだもん:
なるほどなのだ。
「1機能」を「1ユースケース」じゃなくて「1ステップ・1責務」くらいに細かく取るのが、Lambdaだとフィットしやすいわけなのだ。
4. 従来シェルスクリプトの「サーバー内フェーズ分割」はOKなのか?
![]() |
![]() |
🟢 ずんだもん:
で、ここから本題なのだ。
サーバー上ローカルで回す従来方式のシェルスクリプトで、
-
フェーズ01
-
フェーズ02
-
フェーズ03
みたいに分けて長時間バッチを回してるのは、設計としてOKなのだ?
🟣 めたん:
結論から言うと、むしろ良いわ。
サーバー内バッチ(従来シェル)の前提
-
1つのプロセスが「長めに連続で動く」長命バッチ前提
-
フェーズ 01 / 02 / 03… に分けて進めるのは、バッチ設計として王道パターン
メリット
-
フェーズ単位でテストしやすい(「02だけ検証」みたいなことができる)
-
フェーズごとの再実行・スキップ戦略を立てやすい
-
ログをフェーズ単位で整理できる
🟣 めたん:
元Qiita記事が問題視しているのは、
「別Lambda同士を同期でつないで、クラウド側リソースを二重消費する構成」であって、「サーバー内でフェーズ分割」そのものじゃないの。
サーバー内フェーズ分割は、保守性・テスト性の向上という意味で、むしろプラス側のパターンよ。
🟢 ずんだもん:
つまり、従来シェルバッチのフェーズ分割と、
Lambda → Lambda 同期呼び出しアンチパターンは、
そもそも土俵が違うってことなのだ。
5. シェルバッチとLambdaでベストプラクティスが変わる根本理由
🟢 ずんだもん:
じゃあ、なんで従来シェルバッチとLambdaで、
設計のベストプラクティスがこんなに変わってしまうのだ?
🟣 めたん:
端的に言うと、実行環境の前提が違いすぎるからよ。
5-1. 短命プロセス vs 長命バッチ
-
🔵 Lambda / Functions
-
1回の実行時間に上限(例: Lambdaは最大15分)
-
イベントごとに“短命な実行環境”が自動起動→処理→終了
-
いつ別インスタンスに切り替わってもおかしくない前提
-
-
🟢 従来シェルバッチ
-
サーバー上で1プロセスが長時間連続して動いてもよい前提
-
OSやジョブスケジューラの都合を除けば、「15分で強制終了」みたいな制限はない
-
🟣 めたん:
だからLambda側では、長時間やりたい処理ほど、
-
細切れにしてステップごとに区切る
-
各ステップの間で外部状態(DB / Queue / ファイル)を更新しておく
という設計を強く意識する必要がある。
一方、サーバー内バッチはそこまで強くは縛られないわ。
5-2. ステートレス前提かどうか
- 🔵 サーバレス関数(Lambda / Functions)
基本的に「ステートレス」前提。
- インメモリの状態を信頼しすぎると、インスタンス切替やスケールアウトで破綻する。
-
状態はDB / キュー / ストレージなど外部に置き、
関数は「イベントを受けて外部状態を読み書きして終わる」のが理想。
- 🟢 サーバー内シェルバッチ
プロセス中にオブジェクト・変数を持ち続けてもよい。
- フェーズをまたいだ状態保持も、設計次第で十分現実的。
🟣 めたん:
短命+ステートレスという前提があるからこそ、
-
Lambda → Lambda 同期でつなぐより
-
イベント+外部状態で疎結合にする
方がしっくり来るのよ。
6. もし「長命プロセス」をあえてサーバレスでやるなら?
![]() |
![]() |
🟢 ずんだもん:
ここまで聞くと、「長時間かかる処理はシェルでやっとけばいいのだ」って気持ちになるのだ。
でも、あえてサーバレスで長めの処理をやりたいときはどうすればいいのだ?
🟣 めたん:
いい質問ね。
「長命プロセスをサーバレスで実現したい」ときに出てくるのが、
-
AWS なら Step Functions + Lambda
-
Azure なら Durable Functions
みたいな “ワークフロー系サービス” よ。
6-1. AWS 編:Step Functionsで長いフローをつなぐ
🟣 めたん:
まず AWS からいきましょうか。
Lambdaで長い処理フローを組みたいとき、
「Lambda A → Lambda B を同期で直列接続」はアンチパターン寄り、という話をしたわよね。
そこで登場するのが Step Functions というワークフローサービス。
-
各ステップをLambda(または他サービス呼び出し)として実装
-
Step Functionsのステートマシン定義(JSON/YAML)で
-
実行順序
-
条件分岐
-
リトライ・エラー処理
を記述する
-
こうすると、
-
❌ 「Lambda A が Lambda B を同期で直接呼び続ける」のではなく
-
✅ 「Step Functions が A→B→C… と順番に呼び分ける」
形になるから、
-
コスト二重になる可能性
-
同時実行枠二重
-
タイムアウト地獄
みたいな“同期チェーン三重苦”を避けながら、長い処理フローを表現できるわ。
🟢 ずんだもん:
つまり、「長命ジョブをそのままLambdaに押し込む」のではなくて、
長い処理をステップに分解して、Step Functions に実行を任せる のがAWS流ってことなのだ。
🟣 めたん:
そういうこと。
Step Functions は「長く続くのはワークフロー(状態)であって、Lambda実行は1回1回は短い」という思想なのよ。
6-2. Azure 編:Durable Functionsで「長いバッチ」を分解する
🟢 ずんだもん:
Azure側だとどうなるのだ?
🟣 めたん:
Azureの場合は、同じポジションにいるのが Durable Functions ね。
例えば、従来シェルバッチ的な処理を Azure Functions だけでやるとしたら、ざっくりこんな構成になるわ。
-
オーケストレーター関数(ワークフローの進行役)
-
フェーズ01〜03を「ステップ」として順番に呼び分ける
-
どこまで進んだか、何件処理済みかをDurable Functionsの状態として保持する
-
-
アクティビティ関数(実際の仕事をする小さい関数)
-
データ一覧取得(ページング対応)
-
フィルタリングや中間データ作成(CSV相当)
-
更新指示(API呼び出し、閾値超えたらバックオフ)
-
結果確認とリトライ
-
🟢 ずんだもん:
つまり**、「1本の長いシェルスクリプト」を、**
-
進行役のオーケストレーター
-
細かく分けた作業係アクティビティ
に分けて、Durable Functions に「長時間の状態管理と再開」を任せる感じなのだ?
🟣 めたん:
そうそう。
-
長時間待機が必要なときは「スリープ」じゃなくて
Wait(タイマー) -
大量処理は一気にやらず「チャンクごと」に分割
-
途中で落ちても、オーケストレーションの状態から再開
というスタイルになるわ。
「長命プロセスを無理に1回の実行でやる」のではなく、
“短命な実行を何回もつなげて長く見せる” のがDurable Functionsの考え方ね。
6-3. どんなときにサーバレス長命ワークフローを選ぶべき?
🟢 ずんだもん:
じゃあ、どういうときにサーバレスで長いフローをやる意味があるのだ?
🟣 めたん:
ざっくりこんな感じね。
Step Functions / Durable Functions を選ぶ意味があるケース
-
実行環境をクラウド側に寄せたい(オンプレサーバーに依存したくない)
-
実行頻度や負荷が読めず、オートスケールの恩恵を受けたい
-
ステップごとにリトライ/エラー通知/再実行を細かく制御したい
-
将来、イベントトリガー(Webhook / EventBridge / Event Grid)に寄せていきたい
逆に、
-
実行環境が固定(既にオンプレサーバーがある)
-
処理の性質が「夜間に数時間まったり流すだけ」
-
チームとしてサーバレス基盤の運用コストを増やしたくない
みたいなときは、
今の従来シェル+フェーズ分割のやり方をきれいに保つ方が、現実的で堅い選択 だと思うわ。
🟢 ずんだもん:
サーバレスで長命プロセスをやるっていうより、
「長い仕事をちっちゃい仕事に分解して、Step Functions や Durable Functions にスケジューリングしてもらう」
って発想に変えるのがポイントなのだ。
🟣 めたん:
そういうこと。
「プロセスを長生きさせる」のではなくて、
「ワークフローを長生きさせて、プロセスは短命で回す」 のがサーバレス流の長命処理ね。
7. 今回の学びをずんだもん的に整理
🟢 ずんだもん:
-
従来シェルの長時間バッチは、
-
1プロセス長命前提
-
内部フェーズ分割が正義
-
-
サーバレスで同じことをやりたいなら、
-
AWSなら Step Functions + Lambda
-
Azureなら Durable Functions
みたいなワークフロー系で“長さ”と“状態”を持たせる
-
ってところまで分かったのだ!
🟣 めたん:
あとは実際に触ってみて、
「どこまで小さく割るといい感じか」
「どこでステートを外に出すか」
を試行錯誤してみるといいわね。
🟢 ずんだもん:
というわけで、今回の学びをずんだもん的にまとめると、こうなのだ!
-
Lambda → Lambda の同期呼び出しは、
-
コスト
-
同時実行クォータ
-
タイムアウト・リトライ設計
の3点から、基本的に避けた方がよい。
-
-
代わりに、
-
非同期呼び出し
-
SQSなどのキューを挟んだイベント駆動・疎結合アーキテクチャ
がベストプラクティスになりやすい。
-
-
一方、従来シェルスクリプトのような
サーバー内でフェーズを分割する長時間バッチは、-
長命プロセス前提
-
フェーズ単位テスト・再実行がしやすい
という意味で、むしろ良い設計。
-
-
ベストプラクティスが変わる根本原因は、
- 実行時間制限(短命プロセス)
- ステートレス前提
- 自動スケール+同時実行クォータ
といった「実行環境の前提条件」の違いであり、
お金の話はその結果として表に出ているだけ、なのだ。
🟣 めたん:
「サーバレスは設計が難しい」って話じゃなくて、
「環境の前提が違うから、最適解も変わる」って話だったわけね。
従来型バッチ処理とサーバレスをごっちゃにすると、設計指針が混ざってモヤモヤしがちだけど、だいぶ整理できたんじゃない?
ちょっと気が早い花見の下見後の打ち上げ会にて(ファミレス)
![]() |
![]() |
🟢 ずんだもん:
めたん、Lambdaを同期でつなぎまくるとどうなるか知ってるのだ?
🟣 めたん:
過負荷による呼び出しエラーが出て、夜中のバッチが朝まで終わらないとか?
🟢 ずんだもん:
そうなのだ。ぼくの経験では、朝イチで「なんで終わってないんですか?」って聞かれて、
「同期でつないだ心が折れて、今は非同期で立ち直り中です」
って返すことになるのだ。
🟣 めたん:
なんかうまいこと言った感じだわね(笑)。でも、気持ちだけでも最初からステートレスにしておきたいわね…。
🟢 ずんだもん:
そうなのだ。 しかも、残業つづきで夜中の3時に
「Waiting for your response…」 って静かに佇んでいたのだ……。
ログが優しいのがまたつらいのだ。
🟣 めたん:
しっかり連休とってQueue挟めばよかったのにね。
仕事も処理も、間にキューを置いた方が平和なのよ。
🟢 ずんだもん:
そうなのだ!だから仕事も設計も、適度に疎結合でいくのだ。
でも、ぼくにとって花見のお酒は強結合なのだ!
🟣 めたん:
あなた見た目的にお酒飲んじゃいけないキャラでしょ!(笑)
リンク集
利用キャラクター
- ずんだもん:VOICEVOX:ずんだもん
- つむぎ:PIXIV:りょうごさん
- めたん:PIXIV:りょうごさん
本記事で使用しているキャラクター画像の著作権は、それぞれの権利者に帰属します。
非商用目的での利用に基づき掲載しています。


