Skip to content

meshV2拡張機能のAWS AppSyncコスト追跡ログ実装 #498

@takaokouji

Description

@takaokouji

概要

meshV2拡張機能のAWS AppSyncコストを追跡するため、切断後に予想コストをログに記録する機能を実装します。

関連ドキュメント

コスト計算式

ホストの場合(1秒あたり)

const costPerSecond = 
  // Heartbeat
  (1/15) * 0.000004 +
  // REPORT_DATA (8回/秒と仮定、実際は変更検出による)
  8 * 0.000004 +
  // FIRE_EVENTS (2回/秒と仮定、実際はイベント発火頻度による)
  2 * 0.000004 +
  // LIST_GROUP_STATUSES (5分ごと)
  (1/300) * 0.000004 +
  // ON_DATA_UPDATE受信
  (otherNodeCount * 8) * 0.000002 +
  // ON_BATCH_EVENT受信
  (otherNodeCount * 2) * 0.000002 +
  // Subscription接続 (3種類)
  (1/60) * 3 * (0.08/1000000);

// 他ノード1台の場合: 約 $0.00008693/秒

メンバーの場合(1秒あたり)

const costPerSecond = 
  // Heartbeat
  (1/120) * 0.000004 +
  // REPORT_DATA (8回/秒と仮定)
  8 * 0.000004 +
  // FIRE_EVENTS (2回/秒と仮定)
  2 * 0.000004 +
  // LIST_GROUP_STATUSES (5分ごと)
  (1/300) * 0.000004 +
  // ON_DATA_UPDATE受信
  (otherNodeCount * 8) * 0.000002 +
  // ON_BATCH_EVENT受信
  (otherNodeCount * 2) * 0.000002 +
  // Subscription接続 (3種類)
  (1/60) * 3 * (0.08/1000000);

// 他ノード1台の場合: 約 $0.00008360/秒

実装箇所

ファイル: gui/scratch-vm/src/extensions/scratch3_mesh_v2/mesh-service.js

1. コスト追跡用の変数追加(constructorに追加)

constructor (blocks, meshId, domain) {
    // ... 既存のコード ...
    
    // Cost tracking
    this.costTracking = {
        connectionStartTime: null,
        queryCount: 0,           // LIST_GROUPS_BY_DOMAIN, LIST_GROUP_STATUSES
        mutationCount: 0,        // CREATE_DOMAIN, CREATE_GROUP, JOIN_GROUP, etc.
        heartbeatCount: 0,       // RENEW_HEARTBEAT, SEND_MEMBER_HEARTBEAT
        reportDataCount: 0,      // REPORT_DATA
        fireEventsCount: 0,      // FIRE_EVENTS
        dataUpdateReceived: 0,   // ON_DATA_UPDATE
        batchEventReceived: 0,   // ON_BATCH_EVENT
        dissolveReceived: 0      // ON_GROUP_DISSOLVE
    };
}

2. 接続開始時刻の記録

// createGroup() メソッド内、成功時に追加
this.costTracking.connectionStartTime = Date.now();

// joinGroup() メソッド内、成功時に追加
this.costTracking.connectionStartTime = Date.now();

3. 各GraphQL操作のカウント

// 各Query実行後に追加
this.costTracking.queryCount++;

// 各Mutation実行後(種別に応じて)
this.costTracking.mutationCount++;
// renewHeartbeat()とsendMemberHeartbeat()では
this.costTracking.heartbeatCount++;
// _reportData()では
this.costTracking.reportDataCount++;
// fireEventsBatch()では
this.costTracking.fireEventsCount++;

// handleDataUpdate()内
this.costTracking.dataUpdateReceived++;

// handleBatchEvent()内
this.costTracking.batchEventReceived++;

// ON_GROUP_DISSOLVE subscriptionのnextハンドラ内
this.costTracking.dissolveReceived++;

4. 切断時のコスト計算とログ出力(cleanup()メソッドに追加)

cleanup () {
    // コスト計算とログ出力
    if (this.costTracking.connectionStartTime) {
        const connectionDurationSeconds = (Date.now() - this.costTracking.connectionStartTime) / 1000;
        const connectionDurationMinutes = connectionDurationSeconds / 60;
        
        // Query/Mutation costs
        const queryCost = this.costTracking.queryCount * 0.000004;
        const mutationCost = this.costTracking.mutationCount * 0.000004;
        
        // Subscription message costs
        const dataUpdateCost = this.costTracking.dataUpdateReceived * 0.000002;
        const batchEventCost = this.costTracking.batchEventReceived * 0.000002;
        const dissolveCost = this.costTracking.dissolveReceived * 0.000002;
        
        // Subscription connection cost (3 subscriptions)
        const connectionCost = (connectionDurationMinutes / 1000000) * 3 * 0.08;
        
        const totalCost = queryCost + mutationCost + dataUpdateCost + batchEventCost + dissolveCost + connectionCost;
        
        log.info(`Mesh V2: Cost Summary for ${connectionDurationMinutes.toFixed(2)} minutes connection`);
        log.info(`  Role: ${this.isHost ? 'Host' : 'Member'}`);
        log.info(`  Queries: ${this.costTracking.queryCount} ops = $${queryCost.toFixed(8)}`);
        log.info(`  Mutations: ${this.costTracking.mutationCount} ops = $${mutationCost.toFixed(8)}`);
        log.info(`    - Heartbeats: ${this.costTracking.heartbeatCount}`);
        log.info(`    - REPORT_DATA: ${this.costTracking.reportDataCount}`);
        log.info(`    - FIRE_EVENTS: ${this.costTracking.fireEventsCount}`);
        log.info(`  Subscription Messages:`);
        log.info(`    - Data Updates: ${this.costTracking.dataUpdateReceived} msgs = $${dataUpdateCost.toFixed(8)}`);
        log.info(`    - Batch Events: ${this.costTracking.batchEventReceived} msgs = $${batchEventCost.toFixed(8)}`);
        log.info(`    - Dissolve: ${this.costTracking.dissolveReceived} msgs = $${dissolveCost.toFixed(8)}`);
        log.info(`  Subscription Connection: ${connectionDurationMinutes.toFixed(2)} min × 3 = $${connectionCost.toFixed(8)}`);
        log.info(`  TOTAL ESTIMATED COST: $${totalCost.toFixed(8)} (${(totalCost * 1000000).toFixed(2)} per million operations equivalent)`);
        log.info(`  Average cost per second: $${(totalCost / connectionDurationSeconds).toFixed(10)}`);
    }
    
    // 既存のクリーンアップ処理
    this.pendingBroadcasts = [];
    // ... 以下略 ...
}

実装例の出力イメージ

Mesh V2: Cost Summary for 60.00 minutes connection
  Role: Host
  Queries: 13 ops = $0.00005200
    - LIST_GROUPS_BY_DOMAIN: 1
    - LIST_GROUP_STATUSES: 12
  Mutations: 36,242 ops = $0.14496800
    - Heartbeats: 240
    - REPORT_DATA: 28,800
    - FIRE_EVENTS: 7,200
  Subscription Messages:
    - Data Updates: 28,800 msgs = $0.05760000
    - Batch Events: 7,200 msgs = $0.01440000
    - Dissolve: 1 msgs = $0.00000200
  Subscription Connection: 60.00 min × 3 = $0.00001440
  TOTAL ESTIMATED COST: $0.21703640 (217.04 per million operations equivalent)
  Average cost per second: $0.0000603435

テスト方法

  1. ホストとメンバーそれぞれで接続・切断を実行
  2. コンソールログでコストサマリーが出力されることを確認
  3. 計算値が妥当か検証(接続時間、操作回数など)

コスト分析結果のサマリー

1時間接続時の概算コスト

シナリオ 総ノード数 総コスト(USD/時)
ホスト1+メンバー1 2 $0.43
ホスト1+メンバー9 10 $7.93
10グループ×40ノード 400 $1,180.89

1秒あたりの概算コスト(接続中)

  • ホスト(他ノード1台): $0.00008693/秒
  • メンバー(他ノード1台): $0.00008360/秒

コスト内訳の特徴

  1. 最大のコスト要因: Subscription受信メッセージ(特にREPORT_DATAの受信)
  2. グループ規模の影響: 他ノード数に比例してコスト増加
  3. ハートビートの影響: 全体コストの約0.5-1%程度(軽微)

優先度

Medium - 開発環境でのコスト把握と本番環境でのコスト監視のため

関連Issue


🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions