お題とコンテキスト
プラットフォームは、マルチテナントサービスに対してブール値、文字列、構造化されたフィーチャーフラグを提供します。ルールはテナント、ユーザー、リージョン、アプリケーションのバージョンに依存する場合があります。リリース後、一部のインスタンスは即座に更新されますが、他のインスタンスでは数分の遅延が発生することがあり、機密コンテキストがログに記録されてはなりません。コントロールプレーン、データプレーン、評価コンテキスト、キャッシュ、リリース、障害処理、および監査ループを設計してください。
面接官がテストしていること
- コントロールプレーンでの配信とデータプレーンでの評価を分離し、バージョンと整合性の目標を定義すること。
- 評価コンテキストを正しくマージ、オーバーライド、伝播、保護すること。
- リクエストごとのライブ RPC を発生させずに、キャッシュの無効化、オフラインスナップショット、デフォルト値、ロールバックを設計すること。
- 個人データを漏洩することなく、露出(exposure)、エラー、変更、ルール一致のテレメトリを監査可能にすること。
最初に明確にすべき質問
- 評価は強力な整合性(strong consistency)が必要ですか、それとも古いデータ(stale data)でも許容されますか?許容される最大の遅延時間はどれくらいですか?
- テナント、ユーザー、デバイス、リージョン、バージョンのルールの優先順位はどうなっていますか?
- コントロールプレーンが利用できない場合、サービスはどのくらいの期間稼働し続ける必要があり、誰がデフォルト値を承認しますか?
- どのコンテキストフィールドが個人データに該当し、どのフィールドが露出ログへの記録を許可されていますか?
- 多言語 SDK とローカルでのオフライン評価が必須ですか、それともリモート評価機能でも許容されますか?
30秒の回答フレームワーク
コントロールプレーンがルールの検証、不変バージョンの配信、承認、およびロールバックを担当します。データプレーンの SDK は、低レイテンシとオフライン運用のためにバージョン管理されたローカルスナップショットを評価します。コンテキストは、明示的なオーバーライド順序と最小限のフィールドを用いて、グローバル、トランザクション、呼び出しスコープをマージします。インスタンスは、ストリーミングとポーリングを組み合わせて更新を受信し、TTL とバージョンの単調増加チェックを行います。障害発生時は、型付けされたデフォルト値または直近の正常値(last-known-good value)を使用し、データの古さを公開します。重要なフラグはフェイルクローズ(fail-closed)にすることも可能です。監査イベントにはバージョンと匿名化キーを含め、生の属性は絶対に含めません。
ステップごとの詳細な回答
ステップ 1: オブジェクトとリリースステートマシンの定義
フラグには、型、デフォルト値、ルール、バリアント、環境、バージョン、アクティベーション時間が含まれます。配信は、ドラフト、検証、承認、カナリア、完了、またはロールバックを経て進み、各変更によって不変のバージョンが作成されます。コンパイルエラー、型の不一致、またはデフォルト値の欠落は、すべてのサービスに対するランタイムエラーになる前に配信がブロックされます。
ステップ 2: 評価コンテキストの設計
アプリケーション、ホスト、リージョン、テナント、ユーザー属性を構造化コンテキストとして表現します。重複キーの明示的な優先順序を指定した仕様に従い、グローバル、トランザクション、呼び出しコンテキストをマージします。SDK が任意のテストステートやスレッドステートを暗黙的に読み取るべきではありません。コンテキストが SDK、トランスポート、またはログに入る前に、フィールドの許可リスト(allowlist)と墨消し(リダクション)を適用します。
ステップ 3: ローカル評価またはリモート評価の選択
低レイテンシとコントロールプレーンの短時間障害への耐性を重視する場合、分散されたルールスナップショットからのローカル評価が適しています。リモート評価は複雑なロジックを一元化しますが、すべての呼び出しにネットワークと可用性の依存関係が追加されます。ハイブリッド構成では、単純な評価を SDK 内で行い、複雑なルールにはプロバイダーを使用しつつ、型付けされた値、理由、バージョン、メタデータを返すことができます。
ステップ 4: キャッシュと整合性の目標の設定
スナップショットキャッシュは環境、フラグセット、バージョンをキーとし、チェックサムと有効期限で保護します。ポーリングによって、失われた更新通知を補完します。インスタンスは最後に信頼されたスナップショットから起動し、非同期で最新状態に追いつきます。「決して過去に戻らない(単調増加)」、最大遅延秒数、ロールバック伝播時間を SLO として定義します。
ステップ 5: 障害処理と安全なデフォルト値
評価エラーが発生した場合は、理由、エラーコード、ソースとともに、型互換性のあるデフォルト値または直近の正常値を返します。決済、認可、削除に関するフラグは、危険なデフォルト値を暗黙的に採用すべきではありません。ブロックするか、承認されたフェイルクローズポリシーを使用できます。不明なフラグ、安全でない型変換、ルールのタイムアウトが呼び出し元全体に波及しないようガードします。
ステップ 6: 監査と露出テレメトリの設計
コントロールプレーンは、誰がいつ配信、承認、カナリア実行、またはロールバックしたかを記録します。データプレーンは、フラグキー、バージョン、結果、ルールブランチ、SDK バージョン、および匿名のサブジェクトハッシュを記録し、生のメールアドレス、IP、完全なコンテキストは決して記録しません。サンプリング、保持、アクセスをテナントごとに分離し、結果をメトリクスと結合してカナリアの影響を検出します。
ステップ 7: ロールバックとマイグレーションの検証
固定されたコンテキストを各ルールバージョンに対してリプレイし、言語 SDK 間で結果を比較します。通知の喪失、キャッシュの破損、コントロールプレーンの停止、クロックスキュー、部分的なロールバック、プロバイダーのアップグレードを想定した訓練を行います。ロールバックは履歴を書き換えるのではなく新しいバージョンを作成します。完了後にインスタンスごとのバージョン分布とビジネスメトリクスを比較します。
質の高い回答例
コントロールプレーンが型チェック、コンパイル、承認、バージョン管理、カナリアを担当し、SDK は低レイテンシとオフライン運用のためにチェックサム付きローカルスナップショットを評価します。コンテキストは、固定されたオーバーライド順序と機密フィールドの許可リストを用いて、グローバル、トランザクション、呼び出しスコープをマージします。インスタンスは通知とポーリングを併用し、最大遅延時間とバージョンの単調増加ルールを適用します。エラー時は型付けされたデフォルト値または直近の正常値を返し、重要なフラグにはフェイルクローズのオプションを用意します。監査では配信とロールバックのチェーンを保持し、露出ログにはバージョン、結果、匿名キーのみを含め、ロールバックは SDK リプレイと障害訓練によって検証された新しいバージョンとして処理します。
よくある間違い
- 評価ごとにコントロールプレーンを同期的に呼び出し、ビジネスの可用性をコントロールプレーンと結合させてしまうこと。
- バージョンなしで値をキャッシュし、ロールバックや古さの理由を説明できなくなること。
- コンテキストのマージ順序を言語 SDK の実装依存にしてしまい、サービス間で異なる結果が生じること。
- 完全なユーザー属性やメールアドレスをログ出力すること。
- ロールバック時に古い設定を上書きしてしまい、監査履歴やリプレイ履歴を失うこと。
フォローアップの質問と回答
フォローアップ 1: 設定サービスがダウンしていても評価を継続できますか?
最後に信頼されたスナップショットを使用し、そのバージョン、経過時間、ソースを公開します。最大許容遅延時間のしきい値を超えた場合は、無期限に黙って実行し続けるのではなく、リスクベースのデフォルト値を選択するか、処理をブロックするか、人間の介入を求めます。
フォローアップ 2: 言語 SDK 間の一貫性をどのように維持しますか?
正規化、型、コンテキストマージ、エラー理由を仕様化し、バージョン管理された言語間の入力/出力テストベクトルを提供します。SDK が同じプロトコルとライフサイクルを共有しながら、プロバイダーが複雑なルールを一元的に実行できます。
フォローアップ 3: カナリアのパーセンテージを安定させるにはどうすればよいですか?
明示的なハッシュアルゴリズムを使用して、安定した匿名サブジェクトキーをバケット化します。ルールバージョンが固定されていれば、同じサブジェクトに対してすべてのインスタンスで同じバリアントが提供されます。アルゴリズムやソルトを変更する場合は、マイグレーションの影響を文書化した新しいバージョンを作成します。
フォローアップ 4: なぜフック(hooks)を使用するのですか?
フックは評価前にコンテキストを追加したり、評価後に値を検証したり、テレメトリを発行したりできますが、明示的な順序付け、タイムアウト、エラー処理が必要です。フラグの型を暗黙的に変更したり、監査をバイパスしたりしてはなりません。
フォローアップ 5: フラグを安全に削除するにはどうすればよいですか?
コード参照、評価トラフィック、デフォルトブランチを特定し、固定値のバージョンを配信して観察した上で、ルールと SDK メタデータを削除します。古いインスタンスが未知の型を受け取らないように、過去のバージョンとマイグレーション記録を保持します。