午前2時の悪夢:『人力ロールバック』の代償
Sentryからの通知でSlackの画面が真っ赤に染まりました。Grafanaのダッシュボードでは、CPU使用率のチャートが電柱のように垂直に跳ね上がっています。これは、先週新しい決済機能を「マージ」した直後に私が経験した恐ろしいシナリオです。
時計は午前2時を回っていました。プロモーション処理のロジックにバグがあり、システムがフリーズしてしまったのです。救い出す唯一の方法はロールバックでした。しかし、あいにく会社のCI/CDプロセスは、イメージのビルド、テストの実行、そしてKubernetesへのデプロイに丸15分かかります。システムが「仮死状態」にある15分間は、2,000件以上の注文がキャンセルされ、信頼が失墜することを意味していました。
Jenkinsのプログレスバーが進むのを待つ以外に何もできない、あのサーバーエラーを眺める無力感は、本当にトラウマになります。
問題の根本:デプロイとリリースの混同
私たちの多くが陥る根本的な間違いは、ビジネスロジックとデプロイプロセス(Deployment)を密結合させてしまっていることです。
機能を有効にしたいときはコードをデプロイし、バグがあって無効にしたいときはコードをロールバックする。この伝統的なやり方には、3つの大きなリスクが潜んでいます。
- 反応の遅さ: 本番環境が炎上しているときにCI/CDを待つのは苦行でしかありません。
- 一か八かの勝負: 5%の小規模なグループでテストする代わりに、100%のユーザーに機能を一気に開放せざるを得ません。
- コードの乱雑化:
if (env === 'production')のような条件分岐がキノコのように増殖し、コードベースが混乱に陥ります。
一般的な「応急処置」は効果的か?
多くのエンジニアは、環境変数(Environment Variables)を使うことをまず考えるでしょう。環境変数を修正してPodを再起動する。この方法はコードのデプロイよりは早いですが、依然としてサーバーの再起動が必要であり、数秒間ユーザーの接続が途切れる原因になります。
また、データベースに設定を保存する方法を選ぶ人もいます。ユーザーのリクエストごとにDBをチェックするのです。この方法は柔軟ですが、キャッシュメカニズムがないとデータベースに負荷をかけ、システムのレイテンシを増加させてしまいます。
Unleash – デプロイとリリースを分離するソリューション
その「手痛い教訓」から、私はフィーチャーフラグ(Feature Flags)システムを本格的に導入することに決めました。LaunchDarkly(小規模チームで月額約75ドルと高価)やFlagsmithを検討した結果、オープンソース版のUnleashを選びました。
Unleashを使えば、いつでもコードを本番環境にプッシュできますが、新機能は「スイッチ」の後ろに隠れて静かに待機します。準備が整い確信が持てた時に、UnleashのUI上でスイッチを切り替えるだけで完了です。コードを一行も触ることなく、すべての変更が即座に反映されます。
Dockerを使用して5分でUnleashサーバーを構築する
データの完全な制御とコスト削減のため、Docker Composeを使用してセルフホストする方法を選びました。これは技術チームにとって最も効率的な方法です。
# docker-compose.yml
version: "3.9"
services:
db:
image: postgres:15
environment:
POSTGRES_DB: unleash
POSTGRES_PASSWORD: password
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres", "-d", "unleash"]
interval: 2s
timeout: 5s
retries: 10
unleash:
image: unleashorg/unleash-server
ports:
- "4242:4242"
environment:
DATABASE_URL: postgres://postgres:password@db/unleash
DATABASE_SSL: "false"
DATABASE_MAX_CONNECTIONS: 20
UNLEASH_SECRET: your-secure-secret
depends_on:
db:
condition: service_healthy
docker-compose up -d コマンドを実行し、localhost:4242 にアクセスします。ユーザー名 admin、パスワード unleash42 でログインすれば、フラグの管理を開始できます。
Node.jsアプリケーションとの連携:レイテンシほぼゼロ
ロジックをハードコーディングする代わりに、Unleash Clientでラップします。このライブラリは非常にスマートで、フラグを取得してアプリケーションのメモリ内にキャッシュしてくれます。
ライブラリのインストールは非常に簡単です:
npm install unleash-client
実際のコードでの設定例:
const { initialize, isEnabled } = require('unleash-client');
const unleash = initialize({
url: 'http://your-unleash-api/api/',
appName: 'payment-service',
customHeaders: { Authorization: 'YOUR_API_TOKEN' },
});
async function processPayment(order) {
// メモリ内の 'new-payment-gateway' フラグをチェック
if (isEnabled('new-payment-gateway')) {
return useNewGateway(order);
}
return useOldGateway(order);
}
Node.jsアプリケーションとの連携では、キャッシュから直接フラグをチェックするため、レイテンシは1ms未満です。万が一Unleashサーバーに障害が発生しても、クライアントは自動的に最後のキャッシュ値を使用するか、デフォルト値にフォールバックするため、システムが停止することはありません。
技術的負債を避けるための実践的な経験
フィーチャーフラグは強力なツールですが、乱用するとコードがカオスになります。
1. 「宴のあとの片付け」ルール
機能が全ユーザーに対して100%安定して動作するようになったら、すぐにそのフラグを削除しましょう。ずっと前にリリースされた機能のための if-else 文がコード内に溢れないようにしてください。
2. 段階的なロールアウト戦略 (Gradual Rollout)
全員に対して有効にするのではなく、まずは userId に基づいて10%のユーザーでテストします。メトリクスが安定していれば、25%、50%、そして100%へと徐々に増やしていきます。
ヒント:複雑なJSON戦略の設定を素早くテストしたいときは、toolcraft.app/ja/tools/developer/json-formatter を使ってUnleash APIからのレスポンスデータを確認し、正確にフォーマットするのに役立てています。
3. 目的に応じたフラグの命名
test-flag-1 のような無意味な名前は避けましょう。[action]-[feature]-[version] という形式を使います。例えば enable-stripe-v3-migration とすれば、チームの誰もがその目的を一目で理解できます。
結びに代えて
Unleashを導入してから、私はぐっすり眠れるようになりました。何か問題が起きても、スマホを取り出してダッシュボードにアクセスし、OFFボタンを押すだけです。1秒足らずで、すべてのユーザーが安全なバージョンに戻されます。
プロジェクトが拡大し、デプロイ頻度が高まっているなら、今すぐフィーチャーフラグを導入しましょう。夜中にサーバーに張り付いて後悔するまで待つ必要はありません。

