AWS AppSync GraphQL本番運用Vol1|JSリゾルバ・Subscription・Merged API

1. AppSync概要とGraphQL本番設計の文脈

AppSync全体アーキテクチャ — クライアントからAppSync、各データソースへのフロー
図1: AppSync全体アーキテクチャ — クライアント→AppSync→データソースのフロー
📌 本連載について(AppSync GraphQL 本番運用シリーズ)

  • 本連載は、AWS AppSyncでGraphQL APIを本番運用するための設計・実装を、AppSync単体の深掘りとして扱います。
  • Vol1では、スキーマ駆動設計・JSリゾルバ・リアルタイムSubscription・Merged API・認証モード・キャッシングという土台を整理します。

1-1. AppSyncが解く課題とGraphQL vs REST

AWS AppSyncは、GraphQL APIをサーバーレスで提供するAWSのマネージドサービスです。開発者はAPIサーバーやWebSocketサーバーを自前で管理することなく、スキーマ定義とリゾルバの実装に集中できます。

REST APIでは、リソースごとにエンドポイントが分かれており、クライアントが必要なデータを揃えるには複数回のHTTPリクエストを要することがあります(アンダーフェッチ)。逆に1つのエンドポイントが不要なフィールドも返してしまう場合もあります(オーバーフェッチ)。GraphQLはクライアントが必要なフィールドだけをクエリで指定できる単一エンドポイントのAPIスタイルであり、これらの問題を構造的に解決します。

観点RESTGraphQL(AppSync)
エンドポイントリソースごとに複数単一エンドポイント
データ取得サーバーが返す構造に依存クライアントが必要なフィールドを指定
型付けOpenAPI等で別途定義スキーマ(SDL)がAPIの型契約
リアルタイムWebSocket別実装が必要Subscriptionとして組み込み
N+1問題APIレイヤーの工夫が必要パイプライン/バッチリゾルバで対応

GraphQLの単一エンドポイント設計はAPIバージョニングの管理も簡素化します。フィールドの追加は後方互換を保ったまま行え、不要になったフィールドは@deprecatedディレクティブで明示しながら段階的に廃止できます(§2-3参照)。

AppSyncがマネージドで提供する機能

AppSyncはAPIサーバーの管理作業を大幅に削減します。以下の機能はAppSyncが内部で処理するため、自前実装が不要です。

  • 自動スケーリング: トラフィックに応じてコンピュートリソースを自動調整する(容量計画不要)
  • WebSocketサーバー管理: Subscriptionのための永続接続管理・コネクション追跡をマネージドで提供
  • CloudWatch統合: リクエスト数・レイテンシ・4XX/5XXエラーがデフォルトでCloudWatchメトリクスに記録される
  • AWS WAF統合: AppSync APIをAWS WAFのWebACLに関連付けてアプリケーション層の保護を追加できる
  • プライベートAPI: VPC内のリソースからのみアクセス可能なプライベートエンドポイントの構成をサポート(2023年GA)

これらの特性はRESTful APIをAPI GatewayまたはALB+コンテナで構築する場合と比較したときのAppSyncの優位点です。一方で、AppSyncはGraphQL専用のサービスであるため、シンプルなCRUDのみ必要な用途ではAPI GatewayのHTTP APIのほうが低コストになる場合があります。

1-2. AppSyncのサーバーレス統合アーキテクチャ

AppSyncのアーキテクチャは、GraphQL APIレイヤー(AppSync自身)とデータストア層の分離に基づいています。図1に示すように、クライアントはHTTPS(Query/Mutation)またはWebSocket(Subscription)でAppSyncエンドポイントに接続し、AppSyncが各フィールドに紐付いたリゾルバを通じてデータソースを呼び出します。

データソースの種類

データソース用途
Amazon DynamoDBNoSQLテーブルへの直接CRUD
AWS Lambdaカスタムロジック・外部サービス連携
Amazon Aurora(RDS Data API)RDBへのSQL実行(Aurora Serverless v1/v2対応)
Amazon OpenSearch Service全文検索・複合クエリ
HTTP エンドポイント任意のRESTful APIへの呼び出し
Amazon EventBridgeイベント発行(イベント駆動統合)
NONE(データソースなし)Subscriptionのローカルリゾルバ用

リゾルバはデータソースに紐付いており、GraphQLスキーマのフィールドごとに1つのリゾルバを設定します。リゾルバはリクエストマッピング(AppSyncからデータソースへのリクエスト変換)とレスポンスマッピング(データソースのレスポンスからGraphQL型への変換)の2フェーズで動作します。リゾルバのランタイムはVTL(Velocity Template Language)またはAPPSYNC_JS(JavaScript)から選択できます(詳細は§3)。

リクエストライフサイクル

AppSyncがクライアントのGraphQLリクエストを処理する流れを以下に示します。

  1. クライアントがHTTPS/WebSocketでAppSyncエンドポイントにリクエストを送信
  2. AppSyncが設定された認証モードでリクエストを検証(§6参照)
  3. GraphQLオペレーション(Query/Mutation/Subscription)を解析し、各フィールドに紐付いたリゾルバを特定
  4. 各リゾルバがリクエストマッピングを実行し、データソースへのリクエストに変換
  5. データソース(DynamoDB等)がレスポンスを返す
  6. リゾルバがレスポンスマッピングを実行し、GraphQL型に変換
  7. キャッシュが有効な場合はレスポンスをキャッシュし(§7参照)、クライアントに返す

Subscriptionの場合は、Mutationが完了した時点でWebSocket接続中のサブスクライバーに非同期でpushされます。

AppSync Events(補足)

AppSync Eventsは、GraphQL Subscriptionとは独立した軽量なWebSocket pub/subサービスです。チャンネルベースのメッセージ配信を低レイテンシで実現し、東京リージョンでは2025年3月にGA(一般提供)となりました。本連載はGraphQL APIの設計・本番運用を主題とするため、AppSync EventsはVol2の補足として取り上げます。

1-3. 読者像とこの記事のゴール

本記事の想定読者は、AWSのサーバーレス環境でGraphQL APIを構築・運用したいバックエンドエンジニア・フルスタックエンジニア・アーキテクトです。REST APIの設計経験があり、GraphQLへの移行・併用を検討している方や、すでにAppSyncを使っているが設計判断を体系化したい方を主対象とします。

Vol1を読み終えると、以下の設計判断軸を実務に持ち帰ることができます。

  • スキーマ駆動設計: SDLを型契約として定義し、フロントエンドとバックエンドの並行開発を可能にする
  • JSリゾルバ(APPSYNC_JS): unitリゾルバとパイプラインリゾルバの使い分け、制約の把握
  • リアルタイムSubscription: WebSocket接続の設計とスケール考慮
  • Merged API: マイクロサービス分割に対応したスキーマ統合パターン
  • 認証・認可: 5つの認証モードとフィールド単位の認可設計
  • キャッシングとコスト最適化: per-resolverキャッシュと課金モデルの把握

▶ 関連: AWS App Integration Vol1(SQS×SNS×EventBridge×API GW×AppSync 全体像)


2. GraphQLスキーマ駆動設計

2-1. SDLによる型定義

GraphQL SDLはスキーマを宣言的に記述するための言語仕様です。AppSyncではSDLでAPIの型定義を行い、このスキーマがクライアントとサーバー間のAPI契約として機能します。

基本型(type)

type Todo {
  id: ID!
  title: String!
  completed: Boolean!
  owner: String
  createdAt: AWSDateTime
  updatedAt: AWSDateTime
}

!付きフィールドはnon-null(必須)を意味します。フィールドに!がない場合はnullableであり、スキーマ進化時の後方互換確保において重要な設計判断です(§2-3参照)。

入力型(input)

Mutation引数には専用のinput型を定義します。QueryとMutationで同一型を共有するとスキーマの結合度が上がるため、作成・更新・削除それぞれに個別のinput型を持たせるのがベストプラクティスです。

input CreateTodoInput {
  title: String!
  completed: Boolean
}

input UpdateTodoInput {
  id: ID!
  title: String
  completed: Boolean
}

Enum・Interface・Union

enum TodoStatus {
  ACTIVE
  COMPLETED
  ARCHIVED
}

interface Node {
  id: ID!
}

type Todo implements Node {
  id: ID!
  title: String!
  status: TodoStatus!
}

union SearchResult = Todo | User

Interfaceは共通フィールドを持つ型の抽象化に使い、Unionは異なる型を1つのフィールドで返す場合に使います。

AppSync組み込みスカラー

AppSyncはGraphQL標準スカラー(StringIntFloatBooleanID)に加え、以下の組み込みスカラーを提供します。

スカラー用途
AWSDateYYYY-MM-DD形式の日付
AWSTimehh:mm:ss.sss形式の時刻
AWSDateTimeISO 8601形式の日時
AWSTimestampUnixエポック秒(整数)
AWSEmailメールアドレス
AWSJSONJSON文字列(任意の構造体を格納)
AWSURLURL
AWSPhone電話番号
AWSIPAddressIPv4/IPv6アドレス

AWSJSONは構造が可変なペイロードを格納するために使われますが、スキーマの型安全性が失われるため多用は避けます。

カスタムスカラー

scalar BigInt
scalar Decimal

カスタムスカラーのバリデーションはリゾルバ側で実装します。AppSync組み込みスカラーで要件を満たせる場合はそちらを優先します。

2-2. Query / Mutation / Subscription の設計パターン

GraphQLスキーマのルート型はQuery・Mutation・Subscriptionの3種類です。

type Query {
  getTodo(id: ID!): Todo
  listTodos(
 filter: ModelTodoFilterInput
 limit: Int
 nextToken: String
  ): TodoConnection
}

type Mutation {
  createTodo(input: CreateTodoInput!): Todo
  updateTodo(input: UpdateTodoInput!): Todo
  deleteTodo(input: DeleteTodoInput!): Todo
}

type Subscription {
  onCreateTodo(owner: String): Todo
 @aws_subscribe(mutations: ["createTodo"])
}

命名規則

操作パターン
単一取得get{Type}getTodo
一覧取得list{Type}slistTodos
作成create{Type}createTodo
更新update{Type}updateTodo
削除delete{Type}deleteTodo
購読on{Mutation}onCreateTodo

一貫した命名はクライアントSDK(AWS Amplify等)の自動生成との親和性を高めます。

ページネーション(Connection Pattern)

AppSyncでは、一覧取得にCursor-basedページネーション(Connection Pattern)を採用します。nextTokenで次ページのカーソルを受け取り、次回クエリの引数に渡すことでページ送りを実現します。

type TodoConnection {
  items: [Todo]
  nextToken: String
}

DynamoDBデータソースの場合、AppSyncはDynamoDB ExclusiveStartKeyを自動的にnextTokenとしてBase64エンコードします。クライアント側でnextTokenをデコードする必要はありません。

エラー設計

GraphQLのレスポンスはHTTPステータス200で返却され、エラーはerrorsフィールドに含まれます。AppSyncではutil.error(message, errorType)(APPSYNC_JS)でフィールドレベルのエラーを返せます。一部フィールドの取得に成功しつつ別フィールドでエラーが発生するという部分的な成功も表現可能であり、REST APIのHTTPエラーコード設計とは異なる点を把握しておきます。

Subscriptionと@aws_subscribeディレクティブ

AppSyncのSubscriptionはMutationにトリガーされます。@aws_subscribeディレクティブでトリガーとなるMutationを指定し、同一Subscriptionに複数のMutationを指定することも可能です。

type Subscription {
  onTodoChanged(owner: String): Todo
 @aws_subscribe(mutations: ["createTodo", "updateTodo", "deleteTodo"])
}

2-3. スキーマ設計のベストプラクティス

スキーマ進化と後方互換設計

GraphQLスキーマは後方互換を保ちながら進化させることが基本原則です。

  • フィールドの追加: 既存クライアントに影響しないため自由に追加できる
  • フィールドの削除: @deprecatedディレクティブで非推奨を明示し、クライアント移行後に削除する
  • non-null化(!追加): 既存のStringString!にするとクライアントへの破壊的変更になるため原則避ける
  • 型の変更: 原則禁止。新フィールドを追加して古いフィールドをdeprecateする方針をとる
type Todo {
  id: ID!
  title: String!
  # 旧フィールド(廃止予定)
  dueDate: String @deprecated(reason: "Use dueDateISO instead")
  # 新フィールド(AWSDate型で型安全化)
  dueDateISO: AWSDate
}

フィールド粒度の設計

フィールドを細かくしすぎるとスキーマが複雑化し、粗くしすぎると型安全性が失われます。判断基準として、クライアントが実際にまとめて取得するフィールドセットを単位に型を設計します。構造が可変なデータにAWSJSONを使うと型情報が失われるため、構造が安定しているデータは個別フィールドで定義します。

N+1問題とパイプライン/バッチリゾルバ

GraphQLでは親フィールドのリゾルバが子フィールドのリストを返し、子フィールドごとにリゾルバが個別に呼び出される構造(N+1問題)が発生しやすいです。AppSyncでは以下の手段で対処します。

  • パイプラインリゾルバ: 複数のAppSync Functionを連鎖させ、1リクエスト内で複数データソースに順次アクセスする(§3で詳述)
  • DynamoDB BatchGetItem: リゾルバからDynamoDB BatchGetItemを呼び出して複数レコードを一括取得する
  • Lambdaデータソース + DataLoaderパターン: Lambdaデータソースで複数キーをまとめてフェッチするバッチ処理を実装する

スキーマ分割とMerged API

大規模なAPIでは、単一スキーマをチームをまたいで管理するとコンフリクトが増加します。AppSyncのMerged APIは、複数のsource API(それぞれ独立したGraphQL API)のスキーマを単一エンドポイントに統合する機能です。ドメイン(例: 注文・商品・ユーザー)ごとにsource APIを分割し、Merged APIで統合することで、チーム間の独立性とAPIの一貫性を両立します。スキーマ設計の段階でMerged API統合を見越した型名の衝突回避を検討しておくことが重要です(§5で詳述)。


3. JSリゾルバとパイプラインリゾルバ実践

JSリゾルバとパイプラインリゾルバの構造比較
図2: JSリゾルバ(unit)とパイプラインリゾルバの構造比較

3-1. APPSYNC_JS runtime(重要な前提)

APPSYNC_JS は AppSync のリゾルバで使用する JavaScript ランタイム(ECMAScript 2022 準拠サブセット)。2022年末のGA以降、unitリゾルバとパイプラインリゾルバの両方をJSで記述できる

✅ 重要な前提: JSリゾルバはunit/pipeline両対応

  • 旧ドキュメントやブログ記事に「JSリゾルバはパイプラインリゾルバのみ対応」という記述が見られるが、これは初期プレビュー時の制限
  • 現行のAPPSYNC_JS(GAバージョン)はunitリゾルバでも全面利用できる
  • 公式ドキュメント: “JavaScript resolvers work with unit and pipeline resolvers”

ランタイムの基本構成はリゾルバタイプによらず共通:

request ハンドラ — データソースへのリクエストオブジェクトを生成して返す。
response ハンドラ — データソースのレスポンスを変換してGraphQLレスポンスとして返す。

// unitリゾルバ(DynamoDB GetItem)の最小構成
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  return {
 operation: 'GetItem',
 key: util.dynamodb.toMapValues({ id: ctx.args.id }),
  };
}

export function response(ctx) {
  if (ctx.error) {
 util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result;
}

@aws-appsync/utils 型ライブラリが提供する主要ヘルパー:

カテゴリ代表関数用途
DynamoDButil.dynamodb.toMapValues() / dynamodb.get() / dynamodb.put()マーシャリング・操作オブジェクト生成
エラーutil.error(msg, type, data)リゾルバ実行を中断してエラーを返す
ユーティリティutil.autoId() / util.time.nowISO8601()UUID生成・タイムスタンプ
HTTPutil.http.copyHeaders()HTTPデータソース向けヘッダーコピー

3-2. JSランタイムの制約

APPSYNC_JS はNode.jsやブラウザのV8エンジンとは異なる組み込みランタイム。パフォーマンス保証と決定論的実行のため、以下の構文・APIが使用不可となっている(公式ドキュメント「Resolver evaluation」に明記)。

制約理由代替手段
try/catch 不可ランタイムがエラーを ctx.error に集約ctx.error チェック + util.error()
while / do-while ループ不可無限ループ防止Array.reduce / Array.map
async/await / Promise 不可ランタイムが非同期を吸収し同期的に完結非同期処理はランタイム側が担当
任意のnpmパッケージ不可サンドボックス制約@aws-appsync/utils のみ利用可

エラーハンドリングのパターン:

export function response(ctx) {
  // try/catch は使えない → ctx.error で判定
  if (ctx.error) {
 util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result;
}

ループが必要な場合は Array メソッドで代替:

// while は使えない → Array.reduce で代替
const total = items.reduce((acc, item) => acc + item.price, 0);

// 条件付きフィルタ
const activeUsers = users.filter(u => u.status === 'ACTIVE');

util.error() を呼び出すとリゾルバはその時点で中断し、GraphQL レスポンスの errors フィールドにエラーが返る。errorType を指定するとクライアント側でエラー種別を識別できる。

3-3. パイプラインリゾルバ(function chain)

パイプラインリゾルバは、複数のAppSync Function(以下「Function」)を直列に実行するリゾルバタイプ。各Functionは独立して再利用可能なユニットとして設計でき、複数のリゾルバで共有できる。

実行フロー(図2参照):

  1. before マッピング — パイプライン開始前のリクエスト変換(オプション)
  2. Function 1 → Function 2 → … → Function N — 各FunctionがDynamoDB/Lambda/HTTP等のデータソースに接続して順次実行
  3. after マッピング — パイプライン全体のレスポンス変換(オプション)

Function 間のデータ共有には ctx.stash を使用する。ctx.stash はパイプライン全体で共有されるマップで、任意のデータを後続Functionに引き渡せる:

// Function 1: 認証チェック → ユーザー情報をstashに保存
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  return {
 operation: 'GetItem',
 key: util.dynamodb.toMapValues({ id: ctx.identity.sub }),
  };
}

export function response(ctx) {
  if (!ctx.result) {
 util.error('User not found', 'UnauthorizedException');
  }
  ctx.stash.user = ctx.result; // 後続FunctionにUserオブジェクトを渡す
  return ctx.result;
}
// Function 2: stash に保存されたユーザー情報を使ってデータ取得
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  const { user } = ctx.stash; // Function 1 が保存した値を参照
  return {
 operation: 'Query',
 query: {
expression: 'ownerId = :uid',
expressionValues: util.dynamodb.toMapValues({ ':uid': user.id }),
 },
  };
}

export function response(ctx) {
  return ctx.result.items;
}

ctx.prev.result は直前の Function の戻り値(response ハンドラの返却値)を参照できる。ctx.stash との使い分け:

変数スコープ主な用途
ctx.stashパイプライン全体Function 間の任意データ共有(認証結果・集計値等)
ctx.prev.result直前の Function の出力のみ直前の Function が返した値をそのまま利用する場合

Unit resolver vs Pipeline resolver の使い分け

シナリオ推奨タイプ
単一データソースへのシンプルな CRUDUnit resolver
認証チェック → データ取得 → 後処理のような多段ロジックPipeline resolver
複数データソースにまたがるアグリゲーションPipeline resolver
Function を複数 API・リゾルバで再利用したいPipeline resolver

3-4. VTLからJSへの移行(正確な現状)

AWS は新規開発において APPSYNC_JS(JSリゾルバ)を推奨しているが、VTL(Velocity Template Language)は現行サポートが継続しており、公式の廃止(EOL)日付は発表されていない

⚠️ よくある誤解: VTL廃止は誤り — 継続利用可能

  • AWSの公式スタンス: 「新規開発にはAPPSYNC_JSを推奨」 — これは推奨であり強制廃止ではない
  • 公式ドキュメントにVTLのEOL日付は記載されていない(2024年時点)
  • 「VTLは廃止された」「VTLは近く廃止される」という断定記述は公式根拠なしであり誤り
  • 既存のVTLリゾルバは移行必須ではなく、現行環境でそのまま動作する

VTL から JS への移行を進める場合の手順:

1. AWS コンソールの移行支援機能を利用

AppSync コンソールのリゾルバ編集画面から「Convert to JS resolver」を選択すると、VTL マッピングテンプレートを JS ハンドラ形式に変換できる。複雑なテンプレートは手動調整が必要な場合もあるため、変換後の動作確認は必ず行う。

2. 関数の対応マッピング

VTL の $util.* 関数群は @aws-appsync/utilsutil.* に対応する関数が提供されている:

VTLJS(@aws-appsync/utils)
$util.dynamodb.toMapValues()util.dynamodb.toMapValues()
$util.error()util.error()
$util.autoId()util.autoId()
$util.time.nowISO8601()util.time.nowISO8601()

3. リゾルバテストで動作確認

AppSync コンソールのリゾルバ編集画面でテストペイロードを入力してハンドラ単体の動作確認が可能。移行後は必ずユニットテストを実施する。

移行の判断基準として、VTL で問題なく動作している既存リゾルバを無理に移行する必要はない。新規作成するリゾルバや、VTL の記述が複雑で保守性が低下しているリゾルバから段階的に移行するアプローチが現実的。


4. リアルタイムsubscription設計

リアルタイムsubscription WebSocket接続フロー
図3: リアルタイムSubscription — WebSocket接続フロー

4-1. GraphQL SubscriptionとマネージドWebSocket(重要な前提)

AppSync SubscriptionはGraphQL subscriptionオペレーションを通じてリアルタイムデータをクライアントへプッシュする仕組みである。クライアントはWebSocket接続を確立したうえでsubscriptionを登録し、対象のMutationが発生するとAppSyncがデータをプッシュする。接続・サブスクライバー管理・Mutationトリガの検知はすべてAppSyncが担い、開発者は接続管理のコードを書かずに済む。

⚠️ よくある誤解: AppSync SubscriptionのWebSocketはAPI Gateway WebSocket APIとは別実装

AppSync SubscriptionのリアルタイムエンドポイントはAppSyncが完全に管理するマネージドWebSocketである。API Gateway WebSocket APIとは独立した別のサービス実装であり、設定方法・エンドポイント・料金体系・制限値がすべて異なる。

  • API Gateway WebSocket API: 独立したWebSocket APIとして別途プロビジョニングする。$connect/$disconnect/$defaultルートを定義し、接続状態の管理はDynamoDBなど外部ストアで自己実装する。
  • AppSync SubscriptionのWebSocket: GraphQL APIエンドポイントに組み込み済み。AppSyncがサブスクライバー管理・Mutationとの紐付け・フィルタリングを自動処理する。接続先はGraphQL APIとは別の専用realtimeエンドポイント(wss://<id>.appsync-realtime-api.<region>.amazonaws.com/graphql)になる。

同じWebSocketプロトコル(RFC 6455)を土台にしているが、利用方法・クォータ・課金体系は完全に独立している点に注意。

WebSocket接続フロー(AppSync固有の手順)

AppSync SubscriptionはHTTPS(wss://)のWebSocketで動作し、以下の手順で接続を確立する。

  1. 接続確立: クライアントがrealtime-endpointに対してWebSocket接続を開始する。ヘッダーに認証情報(API Key、IAM Signature V4、Cognito IDトークン等)とhost情報をBase64エンコードして付与する。
  2. connection_init送信: WebSocket接続が開いたら、クライアントは{"type": "connection_init"}メッセージを送信する。
  3. connection_ack受信: AppSyncが認証・接続を承認すると{"type": "connection_ack"}が返る。このタイミングでkeepalive間隔も通知される。
  4. start(subscribe)送信: subscriptionオペレーション本体(GraphQL query文字列と変数)と認証情報を含む{"type": "start", ...}メッセージを送信し、サブスクリプション登録を完了する。
  5. start_ack受信: AppSyncがサブスクリプションを受け付けると{"type": "start_ack"}が返る。
  6. data受信: 対象のMutationが発生するたびに{"type": "data", "payload": {...}}がプッシュされる。
  7. stop / connection_close: クライアントが購読を止める場合は{"type": "stop"}を送信し、接続を閉じる。

AppSync Clientライブラリ(AWS Amplify)を利用すると、上記の手順・再接続ロジック・署名処理がライブラリ側で自動処理されるため、アプリケーションコードから低レベルのWebSocketプロトコルを意識せずに済む。

4-2. Mutationトリガとフィルタリング

@aws_subscribeディレクティブによるMutation紐付け

Subscriptionフィールドには@aws_subscribeディレクティブを付与し、トリガとなるMutationを指定する。指定したMutationが実行されると、AppSyncがサブスクライブ中のクライアントに自動でデータを配信する。

type Subscription {
  onCreatePost(author: String): Post
 @aws_subscribe(mutations: ["createPost"])
  onUpdatePost(postId: ID!): Post
 @aws_subscribe(mutations: ["updatePost"])
  onDeletePost: Post
 @aws_subscribe(mutations: ["deletePost"])
}

サブスクリプションリゾルバのnull返却パターン

Subscriptionリゾルバは通常のQuery/Mutationリゾルバとは役割が異なる。サブスクリプション登録時のリゾルバ(requestハンドラ)はデータソースへの問い合わせを行わず、util.subscriptions.subscribe()でフィルタ条件を返すだけである。responseハンドラはnullを返すのが基本パターンになる。

// subscriptionリゾルバ(JSランタイム)のnull返却パターン
export function request(ctx) {
  // フィルタなしでsubscribeする場合はシンプルに返す
  return util.subscriptions.subscribe();
}

export function response(ctx) {
  // Subscriptionリゾルバのresponseはnullを返す
  return null;
}

実際のデータプッシュはAppSyncが自動処理するため、リゾルバはフィルタ条件の定義に専念する設計になっている。

$util.subscriptions.filter()による拡張フィルタリング

AppSync JSリゾルバではutil.subscriptions.filter()を使ってサブスクリプションイベントのフィルタリング式を定義できる。クライアントが設定した購読条件に合致するMutationデータのみをプッシュする仕組みである(enhanced subscription filters)。

export function request(ctx) {
  // Subscriptionの引数(ctx.args)を使ったフィルタリング
  const filter = util.subscriptions.filter({
 author: { eq: ctx.args.author },
 status: { in: ["PUBLISHED", "UPDATED"] }
  });
  return util.subscriptions.subscribe(filter);
}

export function response(ctx) {
  return null;
}

フィルタリング式は複合条件(and / or / not)もサポートしており、スキーマの引数として受け取った値を使って購読対象を絞り込める。$util.subscriptions.filter()が使えない場合の代替として、スキーマ引数でのシンプルなフィルタリング(subscriptionフィールドの引数一致)も利用可能だが、複雑な条件はJSリゾルバの拡張フィルタリングが適している。

📌 Subscriptionリゾルバ設計の要点

  • @aws_subscribe(mutations: ["mutationName"])でMutationとSubscriptionを紐付ける
  • リゾルバのrequestハンドラはutil.subscriptions.subscribe(filter?)を返す
  • responseハンドラはnullを返す(null返却パターン)
  • 複雑なフィルタリングはutil.subscriptions.filter()で定義する

4-3. スケールと運用考慮

同時接続数とクォータ

AppSync Subscriptionの同時接続数にはデフォルトのサービスクォータが設定されている。クォータ値はAWSコンソールのService Quotasでアカウント・リージョンごとに確認でき、上限緩和申請も可能である。大規模なリアルタイムアプリケーションを設計する場合は、想定ピーク接続数を事前に見積もり、必要であれば増枠申請を検討すること。

接続にはkeepaliveが必要であり、AppSyncは接続確立時に推奨keepalive間隔をconnection_ackメッセージで通知する。クライアント側でkeepaliveを実装しないと非アクティブ接続として切断される場合がある。Amplify Clientを使用するとkeepalive処理は自動で行われる。

課金体系(Realtime ConnectionとRealtime Update)

AppSync Subscriptionの利用には2種類の課金が発生する。

課金項目対象
Realtime ConnectionWebSocket接続時間(分単位)の課金
Realtime Updateサブスクライブクライアントへのメッセージ配信数(4KB単位)の課金

接続時間課金はWebSocketが開いている間ずっと発生するため、同時接続数が多いほどコストが増加する。メッセージ配信課金はMutationが発生してプッシュされたデータ量に比例する。具体的な単価はリージョンや時期によって変動するため、AWSの公式AppSyncプライシングページで最新の単価を確認したうえでコスト試算を行うこと。

試算の観点として以下を押さえておく。
– 1日あたりの平均同時接続数 × 接続時間(分)= Realtime Connection費用の見積もり基準
– 1日あたりの平均Mutation発生数 × サブスクライバー数 × 平均メッセージサイズ = Realtime Update費用の見積もり基準
– キャッシュ(§7-1参照)はQueryコストの削減には効くが、Subscriptionの課金には影響しない

Amplify Clientとの統合パターン

AWS AmplifyのAppSyncクライアントライブラリ(Amplify v6 / Amplify Gen2)を使うと、以下の処理が自動化される。

  • WebSocket接続の確立・切断
  • 認証情報(IAM Signature V4、Cognito IDトークン等)の自動付与
  • keepalive送信と非アクティブタイムアウト後の自動再接続
  • ネットワーク復帰時の自動再サブスクライブ

Amplify Gen2(TypeScript)では、スキーマ定義からsubscriptionのクライアントコードが自動生成されるため、手書きのWebSocketコードを最小限に抑えられる。Amplifyを使わない場合はAWS AppSync JavaScript SDKを利用する方法もあり、サポートされているWebSocketプロトコルの仕様に従って独自クライアントを実装することも可能である。

接続エラーハンドリングと再接続戦略

本番環境では接続切断・ネットワーク中断が発生する前提で再接続ロジックを設計する必要がある。AppSyncは接続が切れた場合でもサーバーサイドのサブスクリプション登録を自動的に維持しないため、クライアント側で再接続後にstartメッセージを再送してsubscriptionを再登録する必要がある。

Amplify Clientを使用している場合は再接続・再サブスクライブが自動で行われる。独自実装の場合は以下の考慮が必要である。

  • WebSocketのonclose/onerrorイベントを検知して再接続シーケンスを開始する
  • 指数バックオフを実装し、短時間での連続再接続によるサービス負荷を避ける
  • 再接続後にconnection_initstartの手順を再実行し、subscriptionを再登録する
  • Mutationに対してsubsciprion欠損が許容されない場合は、再接続後に最新データをQueryで取得して状態を補完するパターン(楽観的UI更新)を組み合わせる

Subscription認可とスキーマディレクティブ

AppSyncはMulti-auth modeをサポートするため、Subscriptionフィールドにも認証モードを指定できる。スキーマディレクティブ(@aws_api_key / @aws_iam / @aws_cognito_user_pools / @aws_oidc / @aws_lambda)をSubscriptionフィールドに付与することで、フィールド単位の認可制御が可能である(§6-2で詳述)。

type Subscription {
  onCreatePost(author: String): Post
 @aws_subscribe(mutations: ["createPost"])
 @aws_cognito_user_pools  # CUPユーザーのみ購読可
  onAdminEvent: AdminEvent
 @aws_subscribe(mutations: ["publishAdminEvent"])
 @aws_iam  # IAM認証のみ(バックエンドサービス向け)
}

CloudWatchメトリクスによる監視

AppSync SubscriptionはCloudWatchで以下のメトリクスを監視できる(§7-2と連携)。

メトリクス監視観点
ConnectSuccess / ConnectClientError / ConnectServerErrorWebSocket接続成功率・エラー率
SubscribeSuccess / SubscribeClientError / SubscribeServerErrorサブスクリプション登録の成功・失敗率
PublishDataMessageSuccess / PublishDataMessageErrorMutationトリガ後のデータ配信成功・失敗率
ActiveConnection現時点のアクティブなWebSocket接続数(クォータ監視に利用)

ActiveConnectionを定期的に監視しておくことで、サービスクォータへの接近をアラートとして検知できる。本番運用ではCloudWatchアラームでActiveConnectionが上限の80%を超えた時点で通知する設定を推奨する。

📌 §4 リアルタイムSubscription設計 まとめ

  • AppSync SubscriptionのWebSocketはマネージド実装 — API Gateway WebSocket APIとは別サービス・別設定・別課金
  • WebSocket接続フローはAppSync固有(connection_init → connection_ack → start → start_ack → data)
  • Subscriptionリゾルバはutil.subscriptions.subscribe()を返し、responseはnull(null返却パターン)
  • 拡張フィルタリングはutil.subscriptions.filter()でJSリゾルバに実装
  • 課金は「Realtime Connection(接続時間)」と「Realtime Update(メッセージ配信数)」の2本立て
  • 本番運用ではAmplify Clientの自動再接続 + CloudWatchのActiveConnection監視が基本セット

5. Merged APIと分割スキーマ統合

Merged APIのスキーマ統合構成
図4: Merged API — source APIのスキーマ統合構成

5-1. Merged APIの仕組みとマイクロサービス分割

Merged APIは複数のGraphQL API(source API)のスキーマを単一のGraphQLエンドポイントに統合するAppSyncの機能です。各source APIは独立したGraphQL APIとして存在し、それぞれが独自のリゾルバ・データソース・認証設定を保持します。Merged APIはこれらのスキーマを結合して公開し、クライアントからは単一エンドポイントとして見えます。

マイクロサービス分割パターン

source APIの分割には主に次の2パターンがあります。

  • ドメイン別分割: 注文・在庫・ユーザーなどビジネスドメインごとにsource APIを作成します。各チームがそれぞれのsource APIを所有・管理し、Merged APIで統合します。チーム間の依存関係を最小化しつつ、クライアントには統合されたGraphQLスキーマを提供できます。
  • マイクロサービス別分割: 既存のマイクロサービスごとにsource APIを対応付けます。各マイクロサービスに対してsource APIが窓口となり、Merged APIが唯一のGraphQL統合ポイントとなります。

スキーママージの仕組みとconflict解決

Merged APIがsource APIのスキーマを統合する際、型名の衝突(conflict)が発生する場合があります。AppSyncはMergeTypeポリシーとして以下を選択できます。

ポリシー挙動
MERGE_TYPES同名の型が複数のsource APIに存在する場合、フィールドを統合した単一の型として扱う。型定義の互換性が求められる。
RAISE_ERROR同名の型が複数のsource APIに存在する場合、Merged APIの作成/更新時にエラーを返す。厳密なスキーマ境界管理に適している。

スキーマ統合の具体的なイメージ

例えば、注文サービス(Order API)と在庫サービス(Inventory API)をそれぞれsource APIとして作成し、Merged APIで統合するケースを考えます。

Order APIのスキーマ(一部):

type Query {
  getOrder(orderId: ID!): Order
  listOrders(userId: ID!): [Order]
}

type Order {
  orderId: ID!
  userId: ID!
  items: [OrderItem]
  status: OrderStatus!
}

Inventory APIのスキーマ(一部):

type Query {
  getItem(itemId: ID!): Item
  checkStock(itemId: ID!): StockInfo
}

type Item {
  itemId: ID!
  name: String!
  price: Float!
}

Merged APIはこれら2つのスキーマを統合し、クライアントには単一のGraphQLエンドポイントから getOrder / listOrders / getItem / checkStock がすべて利用可能な状態になります。

Merged APIの主な制限と注意点

  • 型名衝突: source API間で同一名の型が存在し、ポリシーがRAISE_ERRORの場合は統合に失敗します。型命名をドメイン別prefix(例: Order_*, Inventory_*)で管理するのが有効です。
  • ネストされたMerged API非対応: Merged APIをさらに別のMerged APIのsource APIとして使用することはできません。Merged APIは常に最上位の統合レイヤーである必要があります。
  • source API数の上限: 1つのMerged APIに関連付けられるsource API数にはソフトリミットがあります(AWS Service Quotasで確認)。
  • Subscription: Merged APIのSubscriptionは各source APIのSubscriptionリゾルバに委譲されます。クライアントはMerged APIエンドポイントでSubscribeできます。
  • source APIの認証モード整合性: Merged API配下のsource APIとMerged API自体の認証モード設定が整合している必要があります。source APIで有効でない認証モードをMerged APIが使用しようとすると、リクエストが拒否されます。

5-2. 実行ロールと権限モデル(重要な前提)

⚠️ 重要な前提: Merged APIのIAM execution roleは必須

「Merged APIはsource APIを束ねるだけで権限設定は不要」という誤解が多いですが、これは誤りです。Merged APIにはIAM execution roleの設定が必須であり、このロールがないとMerged APIからsource APIへのリゾルバ呼び出しは機能しません。

Merged APIがsource APIのリゾルバを実行するとき、AppSyncサービスプリンシパル(appsync.amazonaws.com)がMerged APIに設定されたIAM execution roleをassumeします。このロールを通じてsource APIリゾルバへのアクセス権限が付与されます。

IAM execution roleに必要な権限の粒度

source APIへのアクセスには appsync:SourceGraphQL アクションが必要です。対象リソース(ARN)の粒度はフィールドの種別によって異なります。

フィールド種別必要なリソースARN
top-levelフィールド(Query/Mutation/Subscriptionの直下)各フィールドのARN(例: arn:aws:appsync:region:account:apis/sourceApiId/types/Query/fields/getOrder
non-top-levelフィールド(ネストされた型のフィールド)source API ARN全体(例: arn:aws:appsync:region:account:apis/sourceApiId

IAMポリシーの例(top-levelフィールドARNとsource API ARNを両方含める):

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": "appsync:SourceGraphQL",
"Resource": [
  "arn:aws:appsync:ap-northeast-1:123456789012:apis/ORDER_API_ID/types/Query/fields/*",
  "arn:aws:appsync:ap-northeast-1:123456789012:apis/ORDER_API_ID/types/Mutation/fields/*",
  "arn:aws:appsync:ap-northeast-1:123456789012:apis/ORDER_API_ID"
]
 }
  ]
}

trust policyの設定(confused deputy対策)

IAM execution roleのtrust policyではAppSyncサービスプリンシパルにassumeを許可します。aws:SourceAccountaws:SourceArnの条件を追加することで、特定のMerged APIからのみassumeを許可し、confused deputy問題を防ぎます。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Principal": { "Service": "appsync.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
  "StringEquals": {
 "aws:SourceAccount": "123456789012"
  },
  "ArnLike": {
 "aws:SourceArn": "arn:aws:appsync:ap-northeast-1:123456789012:apis/MERGED_API_ID"
  }
}
 }
  ]
}

よくある失敗パターン

  • execution roleを設定せずにMerged APIを作成 → source APIリゾルバが実行されずエラー
  • top-levelフィールドARNを列挙せずにsource API ARNのみを付与 → Query/Mutation top-levelフィールドの解決が失敗するケースあり
  • non-top-levelフィールドへのsource API ARN付与を忘れる → ネストされた型の解決が失敗

5-3. クロスアカウント構成(RAM共有)

source APIが別のAWSアカウントに存在する場合、Merged APIはそのsource APIに直接アクセスできません。クロスアカウント統合にはAWS RAM(Resource Access Manager)を使用します。

RAM共有の手順

  1. source API側のアカウントでRAM共有を作成: source APIのARNをRAMリソースシェアに追加し、Merged APIが存在するアカウント(またはAWS Organization)と共有します。
  2. Merged API側のアカウントでRAM招待を承認: リソース共有の招待を承認することで、Merged APIのアカウントからsource APIが参照可能になります。
  3. Merged APIにsource APIを関連付け: RAM共有済みのsource APIのARNを使ってMerged APIのsource APIリストに追加します。

クロスアカウント構成の注意点

  • RAM共有が完了していない状態でsource APIをMerged APIに関連付けようとすると、APIが見つからないエラーになります。RAM共有の承認完了を確認してから関連付けを行います。
  • クロスアカウント構成でもMerged APIのIAM execution roleに appsync:SourceGraphQL 権限が必要です。クロスアカウントのリソースARNも権限の対象に含めます。
  • source APIのオーナーアカウントとMerged APIのオーナーアカウントを分離することで、チームや組織単位での独立したデプロイサイクルが実現できます。これがMerged API×RAM共有を組み合わせた大規模マイクロサービス統合の主なユースケースです。

AWS Organizations連携によるRAM共有

AWS Organizationsを使用している場合、個別のアカウントIDを指定せずにOrganizational Unit(OU)またはOrganization全体を対象にRAM共有を設定できます。これにより、Organization内の複数アカウントに対して一括でsource APIを共有でき、新規アカウントが追加された場合も自動で共有対象に含まれます。Organizations連携のRAM共有を使用するには、Organizations管理アカウントでRAMとOrganizationsの統合設定(aws ram enable-sharing-with-aws-organization)が有効になっている必要があります。

CDK(Cloud Development Kit)によるMerged API構成例

AWS CDKを使ってMerged APIを定義する場合、aws-cdk-lib/aws-appsync モジュールの GraphqlApi を使用し、sourceApiAssociations でsource APIとの関連付けを設定します。

import * as appsync from 'aws-cdk-lib/aws-appsync';
import * as iam from 'aws-cdk-lib/aws-iam';

// IAM execution role
const mergedApiRole = new iam.Role(stack, 'MergedApiRole', {
  assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'),
});
mergedApiRole.addToPolicy(new iam.PolicyStatement({
  actions: ['appsync:SourceGraphQL'],
  resources: [
 `${orderApi.arn}/types/Query/fields/*`,
 `${orderApi.arn}/types/Mutation/fields/*`,
 orderApi.arn,
  ],
}));

// Merged API
const mergedApi = new appsync.GraphqlApi(stack, 'MergedApi', {
  name: 'my-merged-api',
  definition: appsync.Definition.fromSourceApis({
 sourceApis: [
{
  sourceApi: orderApi,
  mergeType: appsync.MergeType.MANUAL_MERGE,
},
 ],
 mergedApiExecutionRole: mergedApiRole,
  }),
});

MANUAL_MERGE を指定すると、スキーマの再マージはコンソール/APIの明示的な操作でのみ実行されます。AUTO_MERGE を指定すると、source APIのスキーマが変更されるたびに自動でMerged APIのスキーマが更新されます。本番環境では意図しないスキーマ変更を防ぐため MANUAL_MERGE を推奨するケースが多いです。

📌 Merged API 設計チェックリスト

  • ✅ Merged APIにIAM execution roleを設定したか
  • ✅ execution roleに appsync:SourceGraphQL を付与し、top-levelフィールドARNとsource API ARNの両方を対象にしたか
  • ✅ trust policyで aws:SourceAccount / aws:SourceArn 条件を付与したか(confused deputy対策)
  • ✅ クロスアカウントの場合、RAM共有承認済みのsource APIを使用しているか
  • ✅ 型名衝突のリスクを評価し、MergeTypeポリシー(MERGE_TYPES / RAISE_ERROR)を選択したか
  • ✅ Merged APIをさらに別のMerged APIのsource APIとして使用していないか(非対応)

6. 認証モードと認可設計

認証モード別フロー(Lambda authorizer重点)
図5: 認証モード別フロー(Lambda authorizer重点)

6-1. 5つの認証モード

AppSyncは以下5つの認証モードをサポートしており、API単位でプライマリモードを1つ選択します。

API_KEY
APIキーをリクエストヘッダー(x-api-key)で渡す最もシンプルな認証方式です。有効期限は最大365日で、期限切れ前の手動ローテーションが必要です。開発・テスト・パブリックデータの読み取り専用APIに向いており、機密データを扱う本番環境には不向きです。

AWS_IAM
AWS SigV4署名を使ったサービス間認証方式です。IAMロールまたはユーザーにAppSyncリソースへのアクセスポリシーを付与します。Lambda・EC2・ECS等のAWSサービスからのAPIコール、およびCognito Identity Poolと組み合わせた未認証(ゲスト)アクセスの認可に適しています。

AMAZON_COGNITO_USER_POOLS
Cognito User Poolsが発行するJWT(IDトークンまたはアクセストークン)をAuthorizationヘッダーで渡す方式です。ユーザー認証の標準パターンとして最も広く採用されています。CognitoグループとスキーマのGROUPS directiveを組み合わせることでロールベースアクセス制御(RBAC)を実装できます。

OPENID_CONNECT
Auth0・Okta等のサードパーティIdPが発行するOIDCトークンを使った認証方式です。既存のIdP基盤をそのまま活用したい場合に利用します。AppSync側でiss(発行者)とaud(オーディエンス)クレームの検証設定を行います。

AWS_LAMBDA(Lambda authorizer)
カスタム認可ロジックをLambda関数で実装する方式です。上記4方式では対応できない独自認証要件(APIキー+IPアドレス制限の複合条件等)に適しています。TTLキャッシュ(デフォルト300秒、最大3600秒)を設定することでLambda呼び出し頻度とコストを削減できます。

6-2. 多重認証モードとフィールド単位認可

AppSyncでは1つのAPIに対してプライマリ認証モードに加えて追加認証モードを設定できます(合計最大5モードの組み合わせが可能)。これにより、例えばパブリックデータはAPI_KEYで、認証済みユーザーのデータはCognitoで、管理者操作はIAMで、というように用途別に認証方式を使い分けることができます。

スキーマのdirectiveを使うことで、型・フィールドレベルで認証モード単位の認可を制御できます:

directive対象認証モード
@aws_api_keyAPI_KEY認証のみ許可
@aws_iamAWS_IAM認証のみ許可
@aws_cognito_user_poolsCognito User Pools認証のみ許可(cognito_groups引数でグループ制限も可能)
@aws_oidcOpenID Connect認証のみ許可
@aws_lambdaLambda authorizer認証のみ許可
@aws_authCognito User Poolsのグループベース認可(groups引数で許可グループを列挙)

directiveをフィールドに付与しない場合はAPIのデフォルト認証モードが適用されます。directiveを付与したフィールドは、そのdirectiveが指定する認証モードでしかアクセスできません。

6-3. Lambda authorizer設計

Lambda authorizerはAppSyncからInvokeされるLambda関数で、独自の認証・認可ロジックを実装します。

入力(AppSync → Lambda)

AppSyncはLambda関数に以下の形式でイベントを渡します:
authorizationToken: クライアントが送信したトークン文字列(Authorizationヘッダーの値等)
requestContext: APIのARN・アカウントID・リクエストID・クエリ文字列等のコンテキスト情報

出力(Lambda → AppSync)

Lambda関数は以下のJSON形式でAppSyncに認可結果を返します:

{
  "isAuthorized": true,
  "resolverContext": { "userId": "u123", "role": "admin" },
  "deniedFields": ["Mutation.deleteUser"],
  "ttlOverride": 300
}
フィールド説明
isAuthorizedtrueの場合リクエストを許可、falseの場合拒否
resolverContextリゾルバの$ctx.identity.resolverContextに渡す追加コンテキスト。認可済みのユーザーIDやロール等を格納できる
deniedFields許可リクエスト内で特定フィールドのみ拒否する場合に完全ARNまたはショートハンド形式(TypeName.fieldName)で指定
ttlOverrideこのレスポンスのキャッシュTTL(秒)。0を指定するとキャッシュせずに毎リクエストLambdaを呼び出す

TTLキャッシュ中は同一トークンのリクエストに対してLambdaが呼ばれないため、レスポンスはステートレス(トークン自体の検証結果のみに基づく)であることが重要です。


7. キャッシング・監視・コスト最適化

キャッシング設計とコスト最適化パターン
図6: キャッシング設計とコスト最適化パターン

7-1. キャッシング設計

AppSyncのServer-Side Caching(SSC)はAPI単位で有効化するマネージドキャッシュ機能で、ElastiCache互換のインメモリキャッシュをバックエンドとして使用します。

キャッシュの粒度(2種類)

設定説明適した用途
Full request cachingクエリ全体をキャッシュ。同一クエリ文字列+変数+認証情報の組み合わせでキャッシュヒット読み取り専用APIでスループット最優先の場合
Per-resolver cachingリゾルバ単位でキャッシュを設定。フィールドごとに異なるTTLとキャッシュキーを設定可能クエリの一部フィールドだけキャッシュしたい場合

TTLとキャッシュキー

TTLは0〜3600秒の範囲で設定します(0はキャッシュ無効)。Per-resolverキャッシュのデフォルトキーは$context.arguments$context.identityの組み合わせです。追加のキャッシュキー(例: クエリ変数内の特定フィールド)を設定することで、キャッシュのヒット率と粒度を調整できます。

キャッシュ無効化(invalidation)

コンソール・CLIからAPIレベルでキャッシュを即時フラッシュできます。Mutationでデータを更新した際のキャッシュ一貫性は、適切なTTL設計(更新頻度に合わせた短めのTTL)またはMutation後の明示的なflushコマンド実行で担保します。

7-2. 監視(CloudWatch / X-Ray)

CloudWatchメトリクス

AppSyncはデフォルトでCloudWatchにメトリクスを送信します。本番環境で最低限監視すべき主要メトリクスは以下のとおりです:

メトリクス説明
4XXError4系クライアントエラー(認証失敗・不正クエリ等)のリクエスト数
5XXError5系サーバーエラー(リゾルバ障害・データソースタイムアウト等)のリクエスト数
Latencyリクエスト処理時間(ミリ秒)。p99等パーセンタイル監視を推奨
ConnectSuccessSubscriptionの接続成功数
ConnectClientErrorSubscriptionの接続クライアントエラー数
Requests総リクエスト数(APIへのクエリ・ミューテーション数)

5XXErrorLatencyにCloudWatchアラームを設定しておくことで、リゾルバ障害やデータソース応答遅延を早期に検出できます。

AWS X-Rayトレーシング

AppSyncはAWS X-Rayとのネイティブ統合をサポートしています。コンソールまたはAPIでX-Rayトレーシングを有効化すると、リゾルバ単位・AppSync function単位・データソース呼び出し単位のトレースが記録されます。パイプラインリゾルバでは各Functionのレイテンシが個別に可視化されるため、どのFunction・どのDynamoDB操作がボトルネックかをピンポイントで特定できます。

7-3. コスト最適化

AppSyncの課金は操作種別ごとに異なります。具体的な単価はリージョン・時期によって変動するため、コスト計画の際は必ずAWS AppSync公式料金ページで最新単価を確認してください。

課金の主要コンポーネント

課金項目課金の単位説明
クエリ・ミューテーションリクエスト数(4KB単位)Query/MutationのAPIへのリクエスト数に応じた従量課金
Subscription接続時間分単位クライアントがWebSocketで接続している時間に課金(Realtime Connection)
Subscriptionメッセージメッセージ配信数(4KB単位)サブスクライブクライアントへプッシュされたメッセージ数に課金(Realtime Update)
Server-Side Cachingインスタンス時間キャッシュを有効化した場合のインスタンスタイプ依存の時間課金

試算の観点

  • クエリ・ミューテーション: 1日あたりの平均API呼び出し数 × 30日 = 月間リクエスト数が試算基準。ペイロードが4KBを超える場合は4KB単位で切り上げ。
  • Subscription接続時間: 1日あたりの平均同時接続数 × 接続時間(分)× 30日 = 月間接続時間(分)。常時接続クライアントが多いほど接続時間課金が増加。
  • Subscriptionメッセージ: 1日あたりのMutation発生数 × 平均サブスクライバー数 × 30日 = 月間メッセージ配信数。
  • キャッシュ: インスタンスタイプごとの時間単価 × 月間稼働時間。リクエスト課金削減効果とのトレードオフで判断。

コスト削減の設計指針

  • per-resolverキャッシュの活用: 読み取り頻度が高く更新頻度が低いリゾルバにキャッシュを設定し、バックエンドへの呼び出し(DynamoDB読み取り等)とリクエスト数そのものを削減
  • Subscription接続管理: 画面非表示時に接続を切断するなど、不要な接続時間を最小化(接続時間課金の抑制)
  • クエリ粒度の設計: GraphQLのオーバーフェッチ防止(必要フィールドのみをクエリ)で4KB超過のリクエストを減らす
  • X-Rayによるボトルネック特定: 高レイテンシリゾルバの特定・最適化でレスポンスタイム短縮とキャッシュヒット率向上を両立

8. まとめと次回予告

8-1. 本記事のまとめ

✅ Vol1 習得チェックリスト

  • GraphQLスキーマ駆動設計: SDLで型・Query・Mutation・Subscriptionを定義し、スキーマをAPI契約として管理する。スキーマ進化はnullable設計と@deprecatedディレクティブで後方互換を保つ
  • APPSYNC_JS runtime(unit/pipeline両対応): JSリゾルバはunitリゾルバとパイプラインリゾルバの両方で利用可能。try/catchasync/awaitwhileループは使用不可という制約を踏まえてリゾルバを実装する
  • パイプラインリゾルバとctx.stash: 複数のAppSync functionをchainしてマルチステップ処理を実現。ctx.stashでfunction間のデータを受け渡す
  • SubscriptionのマネージドWebSocket: AppSync SubscriptionのリアルタイムエンドポイントはAppSyncマネージドのWebSocketであり、API Gateway WebSocket APIとは独立した別実装。接続フロー(connection_init → connection_ack → start → data)を理解する
  • Merged API(IAM execution role必須): 複数のsource APIスキーマを単一エンドポイントに統合。appsync:SourceGraphQLを持つIAM execution roleが必須。クロスアカウントはAWS RAM経由で共有してから関連付ける
  • 5つの認証モードと多重認証: API_KEY・AWS_IAM・Cognito User Pools・OIDC・Lambda authorizerを組み合わせ、スキーマdirective(@aws_api_key等)でフィールド単位の認可を実装する
  • キャッシング・監視・コスト: per-resolver/full-requestキャッシュでバックエンド負荷を削減。CloudWatchの5XXError/LatencyアラームとX-Rayトレーシングで本番監視。課金はQuery/Mutation・Subscription接続時間・メッセージ配信の3軸で試算する

8-2. 次回予告

Vol1では、AppSync GraphQL APIの本番設計に必要な土台(スキーマ・リゾルバ・Subscription・Merged API・認証・監視)を整理しました。

Vol2では、以下のテーマを扱う予定です:

  • AppSync Events(WebSocket pub/sub): GraphQL Subscriptionと異なるイベント駆動の新しいリアルタイム通信モード
  • AWS CDKによるIaC実装: AppSync API・リゾルバ・データソース・認証設定をCDK(TypeScript)でコード管理する実践パターン
  • パフォーマンスチューニング: N+1問題とbatch resolverの活用、キャッシュ戦略の最適化、X-Ray分析に基づくリゾルバ改善

▶ Vol2: AppSync Events・CDK実装・パフォーマンスチューニング・オフライン同期

関連記事

  • アプリ統合の全体像: SQS×SNS×EventBridge×API GW×AppSync のroundup

▶ 関連: AWS App Integration Vol1(アプリ統合サービスの全体像)