mybook

面接対策:システムデザインラウンド — 顧客デプロイを前提とした設計

「システムデザインの面接対策って、やっぱり Twitter を設計できるようにしておけばいいですか?」

Arclight AI のミーティングルームで、入社1年目のユウキがそう聞いた。翌週に控えた FDE チームの採用面接でインタビュアーを務めることになったソウタは、面接官側の準備として候補者が何を聞かれるかを整理していた。その横で、ユウキが自分の転職面接のことを考え始めたのだ。

ソウタは首を横に振った。

「FDE のシステムデザイン面接は、SWE のそれとは根本的に違う。Twitter のタイムラインをどうスケールさせるかじゃない。顧客の環境にどうデプロイするかが問われるんだ」

カイが会議室に入ってきた。

「いい質問だな、ユウキ。SWE 面接のシステムデザインは自社プロダクトの設計だ。でも FDE 面接では、顧客がすでに持っている環境に、自社プロダクトをどう組み込むかが焦点になる。制約が全然違うんだよ」

INFO

FDE のシステムデザイン面接で最も重要なのは「顧客の制約を最初に聞き出す力」です。技術的に正しい設計でも、顧客のセキュリティポリシーやコンプライアンス要件を無視した設計は即不合格になります。


FDE システムデザイン面接の特徴

SWE 面接との違い

ソウタはホワイトボードに比較表を書き出した。

観点SWE システムデザインFDE システムデザイン
設計対象自社プロダクト全体顧客環境への統合
主な制約スケーラビリティセキュリティ・コンプライアンス
データ自社で管理顧客のデータ主権
インフラ自由に選択可能顧客の既存インフラに制約
運用SRE チームが担当顧客の運用チームと協業
成功基準QPS・レイテンシ顧客の業務が止まらないこと

評価軸

FDE システムデザイン面接では、以下の5つの軸で評価される。

  1. マルチテナント設計 — テナント分離の戦略と実装
  2. セキュリティ・コンプライアンス — SOC2、GDPR、HIPAA への対応
  3. 移行戦略 — ゼロダウンタイムでの切り替え計画
  4. 運用設計 — 監視、アラート、障害復旧
  5. 顧客コミュニケーション — 技術的判断を非技術者に説明する能力

時間配分(60分)

Loading diagram...
フェーズ時間やること
要件整理15分機能要件、非機能要件、顧客制約のヒアリング
高レベル設計15分コンポーネント図、データフロー
深掘り設計20分面接官が選んだ領域を詳細に設計
運用設計10分監視、障害対応、ロールバック戦略

WARNING

最初の15分で顧客の制約を聞き出さないまま設計に入ると、後半で「実はその顧客は金融機関で、パブリッククラウドに顧客データを置けません」と言われて設計がひっくり返ります。必ず最初に確認しましょう。


頻出テーマ 6 問

問題1: マルチテナント SaaS のエンタープライズ顧客対応

問題文: あなたの会社はマルチテナント SaaS を提供しています。大手金融機関の顧客から「他のテナントとデータを完全に分離してほしい」と要求されました。既存のマルチテナントアーキテクチャを維持しつつ、この顧客にシングルテナント分離を提供する設計をしてください。

テナント分離戦略の比較

ソウタがユウキに説明した3つの分離レベルがある。

戦略分離レベルコスト運用負荷適用シーン
行レベル分離一般テナント
スキーマ分離準エンタープライズ
DB インスタンス分離金融・医療

アーキテクチャ

Loading diagram...

Rails でのテナントルーティング実装

# app/middleware/tenant_router.rb
class TenantRouter
  ISOLATED_TENANTS = Rails.application.config.isolated_tenants
 
  def initialize(app)
    @app = app
  end
 
  def call(env)
    request = ActionDispatch::Request.new(env)
    tenant_id = extract_tenant_id(request)
 
    if ISOLATED_TENANTS.include?(tenant_id)
      # 専用DBに接続を切り替え
      ActiveRecord::Base.connected_to(
        role: :writing,
        shard: :"tenant_#{tenant_id}"
      ) do
        @app.call(env)
      end
    else
      # 共有DBで行レベル分離
      Current.tenant_id = tenant_id
      @app.call(env)
    end
  end
 
  private
 
  def extract_tenant_id(request)
    request.headers["X-Tenant-ID"] ||
      request.subdomain.presence ||
      raise(TenantNotFoundError)
  end
end

AWS 構成のポイント

  • 専用テナントは VPC Peering で Arclight AI の管理 VPC と接続
  • RDS for PostgreSQL の専用インスタンスを顧客専用サブネットに配置
  • AWS KMS で顧客ごとの暗号化キーを管理(CMK: Customer Managed Key)
  • Security Group で共有テナントのネットワークと完全に分離

INFO

面接では「なぜスキーマ分離ではなくインスタンス分離を選んだのか」を必ず聞かれます。金融機関の場合、監査要件で「物理的に異なるインスタンス」が求められることが多い点を説明しましょう。


問題2: レガシーシステムからのデータ移行パイプライン

問題文: 顧客は20年稼働しているオンプレミスの基幹システムを使っています。このシステムのデータをあなたの SaaS に移行する設計をしてください。移行中もシステムは稼働し続ける必要があります。

移行戦略の全体像

Loading diagram...

ETL vs ELT の選択

「レガシー移行では ETL を推奨する」とソウタは言った。「20年前のシステムにはデータの不整合が大量にある。ロード前にクレンジングしないと新システムが汚染される。ELT はクラウド DWH への分析基盤構築向きだ」

CDC(Change Data Capture)を使ったゼロダウンタイム移行

# app/services/migration/cdc_processor.rb
module Migration
  class CdcProcessor
    def initialize(tenant:, source_config:)
      @tenant = tenant
      @source_config = source_config
      @kinesis = Aws::Kinesis::Client.new
    end
 
    def process_change_event(event)
      case event[:operation]
      when "INSERT"
        upsert_record(event[:after])
      when "UPDATE"
        upsert_record(event[:after])
      when "DELETE"
        soft_delete_record(event[:before])
      end
 
      record_checkpoint(event[:lsn])
    end
 
    private
 
    def upsert_record(data)
      transformed = DataTransformer.transform(
        data,
        source_schema: @source_config[:schema],
        target_schema: :current
      )
 
      target_model = transformed[:model].constantize
      target_model.upsert(
        transformed[:attributes],
        unique_by: transformed[:unique_key]
      )
    end
 
    def record_checkpoint(lsn)
      MigrationCheckpoint.upsert(
        { tenant_id: @tenant.id, lsn: lsn, recorded_at: Time.current },
        unique_by: :tenant_id
      )
    end
  end
end

データ整合性の検証

移行後のデータ検証は3段階で行う。件数検証(レコード数一致)→ サンプル検証(ランダム1%を突合)→ ハッシュ検証(テーブル全体の MD5 比較)。

WARNING

面接で「ビッグバン移行でいいのでは?」と聞かれたら、それはトラップです。FDE の面接では必ず「顧客の業務を止めない移行」を提案してください。ビッグバン移行は週末の停止が必要で、金融機関では月末処理やバッチ処理と衝突するリスクがあります。


問題3: グローバル顧客向けのマルチリージョンデプロイ

問題文: ヨーロッパに本社を置く顧客が GDPR に準拠した形であなたの SaaS を利用したいと言っています。日本にも拠点があり、個人情報保護法への対応も必要です。マルチリージョンアーキテクチャを設計してください。

アーキテクチャ

Loading diagram...

データレジデンシー戦略

「GDPR で一番大事なのは、EU 市民のデータが EU 域外に出ないことだ」とカイが補足した。

データ種別EU リージョンAP リージョン同期方式
個人情報プライマリ保存しない
設定データプライマリリードレプリカAurora Global DB
分析データ匿名化して保存匿名化して保存S3 Cross-Region
監査ログプライマリプライマリ(各自)同期しない

Rails でのリージョンルーティング

# app/middleware/region_router.rb
class RegionRouter
  REGION_MAP = {
    "eu" => { database: :eu_primary, s3_bucket: "arclight-eu" },
    "ap" => { database: :ap_primary, s3_bucket: "arclight-ap" }
  }.freeze
 
  def initialize(app)
    @app = app
  end
 
  def call(env)
    request = ActionDispatch::Request.new(env)
    region = determine_region(request)
    config = REGION_MAP.fetch(region)
 
    ActiveRecord::Base.connected_to(
      role: :writing,
      shard: config[:database]
    ) do
      Current.region = region
      Current.s3_bucket = config[:s3_bucket]
      @app.call(env)
    end
  end
 
  private
 
  def determine_region(request)
    # 顧客のデータレジデンシー設定を優先
    tenant = Tenant.find_by(
      subdomain: request.subdomain
    )
    tenant&.data_residency_region || "ap"
  end
end

AWS 構成

  • Route 53 Geolocation Routing でユーザーの地理的位置に基づくルーティング
  • Aurora Global Database でリージョン間の低レイテンシレプリケーション(1秒未満)
  • S3 Object Lock で GDPR の「忘れられる権利」対応時の監査証跡保持
  • CloudFront のキャッシュポリシーで個人情報を含むレスポンスはキャッシュしない

INFO

面接で「全データを EU に集約すればシンプルでは?」と聞かれたら、レイテンシのトレードオフを説明しましょう。日本の拠点ユーザーが EU にアクセスすると RTT が 250ms 以上になり、UX が大幅に低下します。設定データのリードレプリカを AP に置くことで、読み取りレイテンシを 10ms 以下に抑えられます。


問題4: リアルタイム AI 推論パイプラインの顧客環境デプロイ

問題文: 顧客が「AI の推論は自社 VPC 内で完結させたい。データが外部に出るのは許可できない」と言っています。リアルタイムの AI 推論エンドポイントを顧客の VPC 内にデプロイする設計をしてください。

アーキテクチャ

Loading diagram...

「ポイントは、推論は顧客 VPC 内で完結するが、モデルの更新は Arclight 側から制御できること」とソウタは言った。

コールドスタート対策

対策レイテンシ改善コスト
Provisioned ConcurrencyP99: 50ms以下
ウォームプールP99: 200ms以下
モデル最適化(ONNX)全体20%改善なし
レスポンスキャッシュヒット時0ms

モデルバージョニングと A/B テスト

# app/services/ai/inference_router.rb
module AI
  class InferenceRouter
    def initialize(tenant:)
      @tenant = tenant
      @sagemaker = Aws::SageMakerRuntime::Client.new(
        endpoint_url: tenant.sagemaker_endpoint_url
      )
    end
 
    def predict(input)
      variant = select_variant
      response = @sagemaker.invoke_endpoint(
        endpoint_name: @tenant.endpoint_name,
        body: input.to_json,
        content_type: "application/json",
        target_variant: variant.name
      )
 
      result = JSON.parse(response.body.read)
      record_prediction(variant, input, result)
      result
    end
 
    private
 
    def select_variant
      variants = ModelVariant.where(
        tenant: @tenant,
        status: :active
      )
 
      # トラフィック分配に基づいて選択
      roll = SecureRandom.random_number(100)
      cumulative = 0
      variants.find do |v|
        cumulative += v.traffic_percentage
        roll < cumulative
      end
    end
 
    def record_prediction(variant, input, result)
      PredictionLog.create!(tenant: @tenant, variant: variant,
        input_hash: Digest::SHA256.hexdigest(input.to_json),
        latency_ms: result["latency_ms"], model_version: variant.model_version)
    end
  end
end
  • VPC Endpoint Service で Arclight の SageMaker エンドポイントを顧客 VPC に公開
  • PrivateLink により、トラフィックは AWS バックボーンを経由し、インターネットに出ない
  • S3 VPC Endpoint でモデルアーティファクトのダウンロードもプライベート接続
  • CloudWatch Metrics は Arclight の管理アカウントにクロスアカウント共有

WARNING

「SageMaker のマルチモデルエンドポイントを使えばコスト削減できるのでは?」と聞かれたら、セキュリティ上の懸念を述べましょう。マルチモデルエンドポイントでは異なるテナントのモデルが同一インスタンスに載るため、金融機関の顧客には受け入れられないことが多いです。


問題5: 顧客向け監査ログ・コンプライアンスシステム

問題文: SOC2 と ISO27001 の認証を取得している顧客に、改ざん不可能な監査ログシステムを提供してください。ログは7年間保持し、リアルタイム検索と長期アーカイブの両方に対応する必要があります。

アーキテクチャ

Loading diagram...

改ざん防止の仕組み

監査ログで最も重要なのは 改ざん不可能性(Immutability)だ。

# app/models/audit_log.rb
class AuditLog < ApplicationRecord
  # 更新・削除を禁止
  def readonly?
    persisted?
  end
 
  before_destroy { raise ActiveRecord::ReadOnlyRecord }
 
  before_create :generate_integrity_hash
  before_create :set_sequence_number
 
  scope :for_tenant, ->(tenant) { where(tenant_id: tenant.id) }
  scope :in_period, ->(from, to) { where(occurred_at: from..to) }
 
  private
 
  def generate_integrity_hash
    previous = AuditLog.where(tenant_id: tenant_id)
                       .order(sequence_number: :desc)
                       .first
 
    payload = {
      tenant_id: tenant_id,
      action: action,
      actor_id: actor_id,
      resource: resource_type,
      resource_id: resource_id,
      changes: changes_json,
      occurred_at: occurred_at.iso8601,
      previous_hash: previous&.integrity_hash || "GENESIS"
    }
 
    self.integrity_hash = OpenSSL::Digest::SHA256.hexdigest(
      payload.to_json
    )
  end
 
  def set_sequence_number
    last_seq = AuditLog.where(tenant_id: tenant_id)
                       .maximum(:sequence_number) || 0
    self.sequence_number = last_seq + 1
  end
end

保持期間とストレージ階層

0-30日は OpenSearch(ミリ秒応答)、31-365日は S3 Standard(秒応答)、1-7年は S3 Glacier(時間応答)に自動階層化する。S3 Object Lock(Governance モード)で管理者でも削除不可とし、Athena で長期アーカイブも SQL 検索可能にする。テナントごとに KMS CMK で暗号化を分離する。

INFO

面接で「ブロックチェーンを使って改ざん防止すべきでは?」と聞かれることがあります。ハッシュチェーン(前のログのハッシュを次のログに含める)で十分な改ざん検知が可能であり、ブロックチェーンのような分散合意は不要です。オーバーエンジニアリングを避ける判断力も評価されます。


問題6: 顧客の既存認証基盤との統合

問題文: エンタープライズ顧客が「自社の Active Directory でシングルサインオン(SSO)したい。ユーザーのプロビジョニングも自動化してほしい」と要求しています。SAML 2.0 / OIDC 対応の SSO と SCIM によるユーザー同期を設計してください。

認証フロー

Loading diagram...

「SSO 統合で一番ハマるのは、顧客ごとに IdP の設定が微妙に違うこと」とカイが言った。「SAML のクレーム名が違ったり、グループの属性名が違ったり。テンプレで対応できると思ったら痛い目を見る」

SAML / OIDC プロトコルの比較

項目SAML 2.0OIDC
トークン形式XMLJWT
主な利用者エンタープライズモダンアプリ
実装複雑度
モバイル対応困難良好
FDE での遭遇率非常に高い高い

Rails での OmniAuth 統合

# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  # テナントごとに動的に SAML 設定を読み込む
  provider :saml,
    setup: ->(env) {
      request = Rack::Request.new(env)
      tenant = Tenant.find_by!(
        subdomain: request.subdomain
      )
      sso = tenant.sso_configuration
 
      env["omniauth.strategy"].options.merge!(
        issuer: "https://#{tenant.subdomain}.arclight.ai",
        idp_sso_service_url: sso.idp_sso_url,
        idp_cert_fingerprint: sso.idp_cert_fingerprint,
        name_identifier_format:
          "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
        attribute_statements: {
          email: [sso.email_attribute || "email"],
          name: [sso.name_attribute || "displayName"],
          groups: [sso.groups_attribute || "memberOf"]
        }
      )
    }
end

JIT プロビジョニングとグループマッピング

# app/services/sso/jit_provisioner.rb
module SSO
  class JitProvisioner
    def initialize(tenant:)
      @tenant = tenant
    end
 
    def provision(auth_hash)
      user = find_or_create_user(auth_hash)
      sync_group_memberships(user, auth_hash)
      sync_permissions(user)
      user
    end
 
    private
 
    def find_or_create_user(auth_hash)
      email = auth_hash.info.email.downcase
      user = @tenant.users.find_by(email: email)
 
      if user
        update_user_attributes(user, auth_hash)
      else
        create_user(auth_hash)
      end
    end
 
    def create_user(auth_hash)
      @tenant.users.create!(
        email: auth_hash.info.email.downcase,
        name: auth_hash.info.name,
        sso_uid: auth_hash.uid,
        sso_provider: "saml",
        password: SecureRandom.hex(32) # SSO専用なので不使用
      )
    end
 
    def sync_group_memberships(user, auth_hash)
      idp_groups = auth_hash.extra.raw_info
                            .attributes["groups"] || []
 
      mappings = @tenant.group_mappings
      mapped_roles = mappings.select { |m|
        idp_groups.include?(m.idp_group_name)
      }.map(&:app_role)
 
      user.update!(roles: mapped_roles)
    end
 
    def sync_permissions(user)
      permissions = user.roles.flat_map { |role|
        RolePermission.where(role: role).pluck(:permission) }.uniq
      user.update!(permissions: permissions)
    end
  end
end

SCIM によるユーザー自動同期

SCIM(System for Cross-domain Identity Management)は、IdP 側でのユーザー変更を自動的に SaaS に反映する仕組みだ。作成(POST)、属性更新(PATCH)、無効化(PATCH active=false)の3操作をサポートし、JIT プロビジョニングと組み合わせることで、ユーザーライフサイクル全体を自動化できる。

WARNING

面接で SSO の設計をするとき、「パスワードログインも残すべきか」は頻出の深掘り質問です。答えは「テナント管理者に選択させる」です。SSO 強制モードと SSO + パスワード併用モードを設定可能にし、金融機関では SSO 強制を推奨します。ただし、SSO 障害時のブレークグラス(緊急アクセス)手順も必ず設計に含めましょう。


システムデザイン面接のフレームワーク「DEPLOY」

6つの問題を見てきたユウキに、ソウタはすべての問題に共通するフレームワークを伝えた。

「どんな問題が出ても、この DEPLOY フレームワークに沿って進めればいい」

Loading diagram...

D: Define requirements(要件定義)

最初の15分で以下を明確にする。

  • 機能要件: 何を実現するのか
  • 非機能要件: レイテンシ、スループット、可用性
  • コンプライアンス要件: GDPR、SOC2、HIPAA、PCI DSS
  • 顧客の既存環境: どのクラウド?オンプレ?ネットワーク構成は?

「面接官に質問をしない候補者は、ここで大きく減点される」とカイが言った。

E: Estimate scale(規模見積もり)

具体的な数字で議論する。DAU・同時接続数、初期/日次/年間データ量、QPS・ピーク倍率、レイテンシ要件(P50/P95/P99)を押さえる。

P: Propose high-level design(高レベル設計)

コンポーネント図を描く。主要コンポーネントの特定、データフローの方向、外部サービスとの境界、テナント分離の境界線を示す。この段階では詳細に入りすぎない。

L: Layer in details(詳細設計)

面接官が深掘りしたい領域に焦点を当てる。データモデル設計、API 設計、キャッシュ戦略、エラーハンドリングなど。

O: Operationalize(運用設計)

FDE 面接ではここが特に重要。監視(SLI/SLO)、アラート(誰にどう通知)、障害対応(ランブック、エスカレーション)、ロールバック(デプロイ失敗時の復旧)を設計する。

Y: Your customer's constraints(顧客制約)

DEPLOY フレームワークの最後にして最も FDE らしいステップ。顧客のセキュリティポリシー、変更管理プロセス(CAB 承認)、メンテナンスウィンドウ、ステークホルダー(CISO、コンプライアンス担当)を考慮する。

INFO

DEPLOY フレームワークは直線的に進むものではありません。特に「Y: 顧客制約」は最初の「D: 要件定義」の段階から意識すべきです。フレームワークの最後に置いているのは、設計全体を顧客制約の視点でレビューする最終チェックとしての意味があります。


面接官が見ているポイント TOP 5

ソウタは最後に、自分が面接官をするときの評価基準をユウキに教えた。

1. 顧客制約を最初に確認するか

「設計に入る前に、顧客の業界、規制環境、既存インフラを確認する候補者は強い。逆に、いきなりアーキテクチャを描き始める候補者は、どんなに技術力が高くても FDE には向いていない」

2. セキュリティ・コンプライアンスを自発的に言及するか

「面接官が聞く前に、データの暗号化、アクセス制御、監査ログに言及できるか。FDE にとってセキュリティは後付けではなく、設計の出発点だ」

3. 移行戦略を含めた設計か

「新システムの設計だけでなく、既存システムからの移行パスを含めて設計できるか。ビッグバン移行ではなく、段階的な移行を提案できるか」

4. 運用のことを考えているか

「デプロイして終わりではない。障害が起きたとき誰が対応するのか、ログをどう見るのか、ロールバックはどうするのか。顧客の運用チームのスキルレベルも考慮に入れた設計ができるか」

5. トレードオフを明確に説明できるか

「すべての選択にはトレードオフがある。コストとセキュリティ、パフォーマンスと一貫性、シンプルさとスケーラビリティ。正解を出すことよりも、トレードオフを明確に説明して、顧客の状況に応じた判断ができるかが重要だ」


章のまとめ

ユウキはノートを閉じて、深く息を吐いた。

「SWE の面接対策とは全然違いますね。技術力だけじゃなくて、顧客の立場で考える力が問われるんだ」

ソウタはうなずいた。「その通り。FDE のシステムデザイン面接で問われているのは、技術的に最適な設計ではなく、顧客にとって最適な設計ができるかだ」

カイが締めくくった。「DEPLOY フレームワークを体に染み込ませろ。特に D と Y だ。要件定義で顧客の制約を聞き出し、最後に顧客の視点で設計をレビューする。この習慣がついていれば、どんな問題が出ても対応できる」

ユウキは立ち上がった。面接まであと2週間。DEPLOY フレームワークを使って、6つの頻出テーマを何度も設計し直す日々が始まる。だが、もう漠然とした不安はなかった。何を聞かれても、まず「顧客の制約は何か」と問うことから始めればいい。

INFO

次章では、FDE 面接のもう一つの山場「ビヘイビア面接ラウンド」を攻略します。顧客との修羅場をどう語るか、STAR フレームワークの FDE 版を学びましょう。