RORK LABEN
EXPO — Expo Agent がベータで公開されました。ブラウザ上でプロジェクトやリポジトリを直接扱いながら、プロンプトからアプリを生成・変更できますNATIVE — Expo Agent の出力は iOS・Android・web 向けの、実際に配布できるネイティブアプリですRN — React Native チームは新規プロジェクトについて Expo を公式に推奨しています。AI によるコード生成では、判断の余地を狭められる点が理由に挙げられますMAX — Rork Max は native Swift を出力し、Claude Code と Opus 4.6 の組み合わせで動作します。標準の Rork は React Native(Expo)でクロスプラットフォームのアプリを生成しますCREDIT — Rork は無料で始められ、有料プランは月額 $25 からです。クレジットの消費は速いため、本格的に作る前提なら費用を見積もっておく必要がありますSEPT — App Store の申請時回答と Android の開発者認証は、いずれも2026年9月からです。配布経路の棚卸しは早めに済ませておく類の変更ですEXPO — Expo Agent がベータで公開されました。ブラウザ上でプロジェクトやリポジトリを直接扱いながら、プロンプトからアプリを生成・変更できますNATIVE — Expo Agent の出力は iOS・Android・web 向けの、実際に配布できるネイティブアプリですRN — React Native チームは新規プロジェクトについて Expo を公式に推奨しています。AI によるコード生成では、判断の余地を狭められる点が理由に挙げられますMAX — Rork Max は native Swift を出力し、Claude Code と Opus 4.6 の組み合わせで動作します。標準の Rork は React Native(Expo)でクロスプラットフォームのアプリを生成しますCREDIT — Rork は無料で始められ、有料プランは月額 $25 からです。クレジットの消費は速いため、本格的に作る前提なら費用を見積もっておく必要がありますSEPT — App Store の申請時回答と Android の開発者認証は、いずれも2026年9月からです。配布経路の棚卸しは早めに済ませておく類の変更です
記事一覧/開発ツール
開発ツール/2026-08-08上級

復旧させたのに 429 が止まらない — リトライのジッター設計を2,000端末で測り直しました

バックエンド復旧後も 429 が続いた原因を、2,000端末の離散シミュレータで測り直しました。指数バックオフ単体・上限値・±10%ジッター・Retry-After の遵守が、それぞれ何秒の遅れを生むかの実測値です。

リトライバックオフExpo160React Native221信頼性設計

プレミアム記事

個人開発のアプリが使っているバックエンドを、4分ほど止めた朝がありました。設定を1行変えるだけのつもりが、デプロイに失敗して落ちたままになった、という単純な話です。

慌てて戻して、ログを見ながらほっとしたのも束の間でした。復旧しているのに、429 が止まりません。

サーバは動いている。処理能力も戻っている。それでも数分のあいだ、返るのはエラーばかり。手元の端末で開き直しても同じでした。

原因は自分のリトライ実装にありました。しかも、教科書どおりに書いたつもりの部分が、いちばん効いていました。

腹落ちさせるには測るしかありません。手元で 2,000 端末ぶんの再接続を回せる小さなシミュレータを書いて、実装ごとに何秒の差が出るかを並べてみました。

何が起きていたのか

障害が起きると、その時間帯にアプリを開いていた端末が全部まとめて失敗します。失敗した瞬間がほぼ揃っているので、その後のリトライも揃います。

復旧した瞬間、揃った端末が一斉に戻ってくる。処理能力を超えるので 429 を返す。429 を受けた端末が、また揃ったタイミングで戻ってくる。

サーバは復旧しているのに、自分のクライアントが自分のサーバを塞ぎ続けている状態でした。

ここまでは、よく知られた thundering herd の説明そのものです。私が分かっていなかったのは、「では何をどれだけ入れれば解けるのか」という定量の部分でした。

測定台を作る

実サービスで実験するわけにはいかないので、離散時間のシミュレータを書きました。100ms を1tick として、端末の送信・サーバの処理・リトライの再スケジュールだけを回します。

条件は手元の構成に寄せています。端末2,000台、復旧は30秒時点、復旧後のサーバ処理能力は 120 req/sec。乱数は seed 固定の mulberry32 で、5つの seed の平均を取りました。

// herd.mjs — node herd.mjs で実行できます
const N = 2000;               // 同時に失敗した端末数
const RECOVER_MS = 30000;     // サーバ復旧時刻
const CAPACITY_PER_SEC = 120; // 復旧後の処理能力
const TICK = 100;             // 1tick = 100ms
const CAP_TICK = CAPACITY_PER_SEC * TICK / 1000;
const BASE = 1000;            // 基準待機 1s
 
// seed 固定の疑似乱数(結果を再現できるようにするため)
function mulberry32(a) {
  return function () {
    a |= 0; a = a + 0x6D2B79F5 | 0;
    let t = Math.imul(a ^ a >>> 15, 1 | a);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}
 
function run(nextDelay, seed) {
  const rand = mulberry32(seed);
  const next = new Float64Array(N);   // 次に送信する時刻
  const att  = new Int32Array(N);     // 試行回数
  const prev = new Float64Array(N);   // 直前の待機時間(decorrelated 用)
  const done = new Uint8Array(N);
  for (let i = 0; i < N; i++) { next[i] = rand() * 2000; prev[i] = BASE; }
 
  let sentAfterRecovery = 0, ok = 0, peak = 0, t99 = -1;
 
  for (let t = 0; t < 1200000; t += TICK) {
    const arrivals = [];
    for (let i = 0; i < N; i++) if (!done[i] && next[i] <= t) arrivals.push(i);
 
    const up = t >= RECOVER_MS;
    if (up) {
      sentAfterRecovery += arrivals.length;
      const qps = arrivals.length * 1000 / TICK;
      if (qps > peak) peak = qps;
    }
 
    // 復旧前は全滅、復旧後も処理能力を超えた分は 429 相当で弾かれます
    let budget = up ? CAP_TICK : 0;
    for (const i of arrivals) {
      if (budget >= 1) { budget--; done[i] = 1; ok++; }
      else {
        att[i]++;
        const d = nextDelay(att[i], prev[i], rand);
        prev[i] = d;
        next[i] = t + d;
      }
    }
    if (t99 < 0 && ok >= N * 0.99) t99 = t;
    if (ok === N) break;
  }
  return { sentAfterRecovery, ok, peak: Math.round(peak), t99 };
}

計測するのは3つに絞りました。復旧後にサーバへ届いた総リクエスト数、復旧後のピーク QPS、そして 99% の端末が通り終えるまでの時刻です。

平均応答時間を見ても意味がありません。この種の障害で読者が体感するのは、いちばん遅い層がいつ戻るかだからです。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
指数バックオフをジッター無しで入れると、固定5秒間隔より99%完了が7倍遅くなる実測値
「Retry-After をそのまま守る」実装が最も遅かった理由と、下限として扱う書き換え方
decorrelated jitter と端末固定オフセットを組み合わせた、そのまま使える fetch ラッパー
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-08-04
名前が20文字ちょうどでも保存できない — 文字数の数え方を1か所に決めるまでの実測
絵文字や合成文字が入ると、クライアントとサーバーで文字数が食い違います。4つの数え方をNode v22で実測し、切り詰め2,000件の破損率75.6%・UTF-8往復での置換文字化36.1%という結果から、カウントの正本を1か所に置く設計へ至るまでをまとめました。
開発ツール2026-07-30
Renovate が触ってよい依存と、触ってはいけない依存 — Expo SDK 57 の123パッケージを機械的に切り分ける
Rork 生成アプリに依存の自動更新を入れると、Expo が版を管理している123パッケージまで巻き込まれます。2026年7月30日時点の実測では、うち6件が npm の最新版へメジャー更新される差でした。除外リストを手で書かず SDK から機械生成する手順をまとめます。
開発ツール2026-07-28
prebuild --clean で消える手入れを、上げる前に数える — Expo SDK 57 移行の棚卸し
Expo SDK 57 への移行前に、ios/ へ加えた手入れが prebuild の再生成でいくつ失われるかを数える手順をまとめました。素の差分649行のうち意味を持つのは3行。ノイズを正規化して実質的な変更だけを抽出するスクリプトと、config plugin への移し替えを実測つきで共有します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →