Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ Mesh v2 uses environment variables for configuration, allowing different setting
| Variable | Development | Production | Description |
|----------|-------------|------------|-------------|
| `MESH_SECRET_KEY` | `dev-secret-key-for-testing` | (set in GitHub Secrets) | Secret key for domain validation |
| `MESH_HOST_HEARTBEAT_INTERVAL_SECONDS` | `15` | `30` | Host heartbeat interval in seconds |
| `MESH_HOST_HEARTBEAT_TTL_SECONDS` | `60` | `150` | Host group TTL in seconds (5× interval) |
| `MESH_HOST_HEARTBEAT_INTERVAL_SECONDS` | `15` | `60` | Host heartbeat interval in seconds |
| `MESH_HOST_HEARTBEAT_TTL_SECONDS` | `60` | `150` | Host group TTL in seconds |
| `MESH_MEMBER_HEARTBEAT_INTERVAL_SECONDS` | `15` | `120` | Member heartbeat interval in seconds |
| `MESH_MEMBER_HEARTBEAT_TTL_SECONDS` | `60` | `600` | Member node TTL in seconds (5× interval) |
| `MESH_MAX_CONNECTION_TIME_MINUTES` | `10` | `50` | Maximum connection time for a group (minutes) |
| `MESH_MEMBER_HEARTBEAT_TTL_SECONDS` | `60` | `600` | Member node TTL in seconds |
| `MESH_MAX_CONNECTION_TIME_MINUTES` | `5` | `25` | Maximum connection time for a group (minutes) |

### Setup for Local Development

Expand All @@ -118,7 +118,7 @@ npx cdk deploy --context stage=stg

**Command Line Override**:
```bash
MESH_HOST_HEARTBEAT_INTERVAL_SECONDS=30 \
MESH_HOST_HEARTBEAT_INTERVAL_SECONDS=60 \
MESH_MEMBER_HEARTBEAT_INTERVAL_SECONDS=120 \
npx cdk deploy --context stage=prod
```
Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ mutation RenewHeartbeat($groupId: ID!, $domain: String!, $hostId: ID!) {

**重要**: この mutation はホストのみが実行できます。非ホストが実行すると `Unauthorized` エラーが返されます。

**ハートビート間隔**: 環境変数 `MESH_HOST_HEARTBEAT_INTERVAL_SECONDS` で設定(開発環境: 15秒、本番環境: 30秒
**ハートビート間隔**: 環境変数 `MESH_HOST_HEARTBEAT_INTERVAL_SECONDS` で設定(開発環境: 15秒、本番環境: 60秒

---

Expand Down
11 changes: 6 additions & 5 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ cp .env.example .env
| 変数名 | 開発環境推奨値 | 本番環境推奨値 | 説明 |
|--------|--------------|--------------|------|
| `MESH_SECRET_KEY` | `dev-secret-key-for-testing` | (GitHub Secretsで設定) | ドメイン検証用の秘密鍵 |
| `MESH_HOST_HEARTBEAT_INTERVAL_SECONDS` | `15` | `30` | ホストのハートビート送信間隔(秒) |
| `MESH_HOST_HEARTBEAT_INTERVAL_SECONDS` | `15` | `60` | ホストのハートビート送信間隔(秒) |
| `MESH_HOST_HEARTBEAT_TTL_SECONDS` | `60` | `150` | ホストグループの有効期限(秒) |
| `MESH_MEMBER_HEARTBEAT_INTERVAL_SECONDS` | `15` | `120` | メンバーのハートビート送信間隔(秒) |
| `MESH_MEMBER_HEARTBEAT_TTL_SECONDS` | `60` | `600` | メンバーノードの有効期限(秒) |
| `MESH_MAX_CONNECTION_TIME_SECONDS` | `300` | `3000` | グループの最大接続時間(秒) |
| `MESH_MAX_CONNECTION_TIME_SECONDS` | `300` | `1500` | グループの最大接続時間(秒) |

### 3.3 開発環境用の設定(stg)

Expand All @@ -101,11 +101,11 @@ MESH_MAX_CONNECTION_TIME_SECONDS=300
```bash
# 本番環境ではGitHub Secretsまたは環境変数で設定
MESH_SECRET_KEY=<本番用の秘密鍵>
MESH_HOST_HEARTBEAT_INTERVAL_SECONDS=30
MESH_HOST_HEARTBEAT_INTERVAL_SECONDS=60
MESH_HOST_HEARTBEAT_TTL_SECONDS=150
MESH_MEMBER_HEARTBEAT_INTERVAL_SECONDS=120
MESH_MEMBER_HEARTBEAT_TTL_SECONDS=600
MESH_MAX_CONNECTION_TIME_SECONDS=3000
MESH_MAX_CONNECTION_TIME_SECONDS=1500
```

**重要**:
Expand Down Expand Up @@ -175,10 +175,11 @@ npx cdk deploy
```bash
# 環境変数を直接指定してデプロイ
MESH_SECRET_KEY="<本番用秘密鍵>" \
MESH_HOST_HEARTBEAT_INTERVAL_SECONDS=30 \
MESH_HOST_HEARTBEAT_INTERVAL_SECONDS=60 \
MESH_HOST_HEARTBEAT_TTL_SECONDS=150 \
MESH_MEMBER_HEARTBEAT_INTERVAL_SECONDS=120 \
MESH_MEMBER_HEARTBEAT_TTL_SECONDS=600 \
MESH_MAX_CONNECTION_TIME_SECONDS=1500 \
npx cdk deploy --context stage=prod
```

Expand Down
2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Mesh v2 は環境変数を使用して設定を管理し、開発環境と本番
**本番環境(遅い間隔)**:
- コスト最適化(~70% のコスト削減)
- メンバーのハートビート 120 秒により、UX を維持しつつ API 呼び出しを削減
- ホストのハートビート 30 秒により、グループ解散の検出を迅速化
- ホストのハートビート 60 秒により、グループ解散の検出を迅速化
- TTL を間隔の 5 倍にすることで、ネットワークの一時的な問題に対応

### 環境変数の使用方法
Expand Down
10 changes: 8 additions & 2 deletions js/functions/checkExistingGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { util } from '@aws-appsync/utils';

export function request(ctx) {
const { hostId, domain } = ctx.args;
const nowEpoch = Math.floor(util.time.nowEpochMilliSeconds() / 1000);
const ttlSeconds = +(ctx.env.MESH_HOST_HEARTBEAT_TTL_SECONDS || '150');
const threshold = nowEpoch - ttlSeconds;
const nowISO = util.time.nowISO8601();

// 既存グループの検索
return {
Expand All @@ -17,9 +21,11 @@ export function request(ctx) {
})
},
filter: {
expression: 'hostId = :hostId',
expression: 'hostId = :hostId AND heartbeatAt > :threshold AND expiresAt > :now',
expressionValues: util.dynamodb.toMapValues({
':hostId': hostId
':hostId': hostId,
':threshold': threshold,
':now': nowISO
})
}
};
Expand Down
3 changes: 2 additions & 1 deletion js/functions/checkGroupExists.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export function request(ctx) {
export function response(ctx) {
const { groupId, domain } = ctx.args;
const nowEpoch = Math.floor(util.time.nowEpochMilliSeconds() / 1000);
const heartbeatThreshold = nowEpoch - 60; // heartbeat閾値: 60秒
const ttlSeconds = +(ctx.env.MESH_HOST_HEARTBEAT_TTL_SECONDS || '150');
const heartbeatThreshold = nowEpoch - ttlSeconds;

// グループが存在しない
if (!ctx.result) {
Expand Down
5 changes: 3 additions & 2 deletions js/functions/createGroupIfNotExists.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function request(ctx) {
}

// maxConnectionTimeSeconds のバリデーションと決定
const envMaxSeconds = +(ctx.env.MESH_MAX_CONNECTION_TIME_SECONDS || '3000');
const envMaxSeconds = +(ctx.env.MESH_MAX_CONNECTION_TIME_SECONDS || '1500');
let actualMaxSeconds = envMaxSeconds;

if (maxConnectionTimeSeconds !== undefined && maxConnectionTimeSeconds !== null) {
Expand All @@ -46,7 +46,8 @@ export function request(ctx) {
const now = util.time.nowISO8601();
const nowEpoch = Math.floor(util.time.nowEpochMilliSeconds() / 1000);
const expiresAt = util.time.epochMilliSecondsToISO8601(util.time.nowEpochMilliSeconds() + actualMaxSeconds * 1000);
const ttl = nowEpoch + 60; // 1分間
const ttlSeconds = +(ctx.env.MESH_HOST_HEARTBEAT_TTL_SECONDS || '150');
const ttl = nowEpoch + ttlSeconds;

return {
operation: 'PutItem',
Expand Down
9 changes: 6 additions & 3 deletions js/functions/findNodeMetadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ export function response(ctx) {
util.error(ctx.error.message, ctx.error.type);
}

if (!ctx.result) {
const result = ctx.result;
const nowEpoch = Math.floor(util.time.nowEpochMilliSeconds() / 1000);

if (!result || (result.ttl && result.ttl <= nowEpoch)) {
return null;
}

// stashに保存して次のFunctionへ
ctx.stash.nodeMetadata = ctx.result;
return ctx.result;
ctx.stash.nodeMetadata = result;
return result;
}
2 changes: 1 addition & 1 deletion js/functions/renewHeartbeatFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ export function response(ctx) {
groupId: groupId,
domain: domain,
expiresAt: group.expiresAt,
heartbeatIntervalSeconds: +(ctx.env.MESH_HOST_HEARTBEAT_INTERVAL_SECONDS || '30')
heartbeatIntervalSeconds: +(ctx.env.MESH_HOST_HEARTBEAT_INTERVAL_SECONDS || '60')
};
}
5 changes: 3 additions & 2 deletions js/resolvers/Mutation.createGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function request(ctx) {
}

// maxConnectionTimeSeconds のバリデーションと決定
const envMaxSeconds = +(ctx.env.MESH_MAX_CONNECTION_TIME_SECONDS || '3000');
const envMaxSeconds = +(ctx.env.MESH_MAX_CONNECTION_TIME_SECONDS || '1500');
let actualMaxSeconds = envMaxSeconds;

if (maxConnectionTimeSeconds !== undefined && maxConnectionTimeSeconds !== null) {
Expand All @@ -34,7 +34,8 @@ export function request(ctx) {
const now = util.time.nowISO8601();
const nowEpoch = Math.floor(util.time.nowEpochMilliSeconds() / 1000);
const expiresAt = util.time.epochMilliSecondsToISO8601(util.time.nowEpochMilliSeconds() + actualMaxSeconds * 1000);
const ttl = nowEpoch + 60; // 1分間
const ttlSeconds = +(ctx.env.MESH_HOST_HEARTBEAT_TTL_SECONDS || '150');
const ttl = nowEpoch + ttlSeconds;

return {
operation: 'PutItem',
Expand Down
5 changes: 5 additions & 0 deletions js/resolvers/Mutation.reportDataByNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export function request(ctx) {
value: item.value
}));

const ttlSeconds = +(ctx.env.MESH_MEMBER_HEARTBEAT_TTL_SECONDS || '600');
const nowEpoch = Math.floor(util.time.nowEpochMilliSeconds() / 1000);
const ttl = nowEpoch + ttlSeconds;

return {
operation: 'PutItem',
key: util.dynamodb.toMapValues({
Expand All @@ -25,6 +29,7 @@ export function request(ctx) {
domain: domain,
data: sensorDataList,
timestamp: now,
ttl: ttl,
// GSI用属性
gsi_pk: `NODE#${nodeId}`,
gsi_sk: 'STATUS'
Expand Down
10 changes: 10 additions & 0 deletions js/resolvers/Query.listGroupStatuses.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export function request(ctx) {
if (!domain || !groupId) {
util.error('groupId and domain are required', 'ValidationError');
}
const nowEpoch = Math.floor(util.time.nowEpochMilliSeconds() / 1000);

// DynamoDB Query: DOMAIN#${domain} 配下の GROUP#${groupId}#NODE#*#STATUS を取得
return {
Expand All @@ -20,6 +21,15 @@ export function request(ctx) {
':pk': `DOMAIN#${domain}`,
':sk_prefix': `GROUP#${groupId}#NODE#`
})
},
filter: {
expression: 'attribute_not_exists(#ttl) OR #ttl > :now',
expressionNames: {
'#ttl': 'ttl'
},
expressionValues: util.dynamodb.toMapValues({
':now': nowEpoch
})
}
};
}
Expand Down
3 changes: 2 additions & 1 deletion js/resolvers/Query.listGroupsByDomain.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { util } from '@aws-appsync/utils';
export function request(ctx) {
const { domain } = ctx.args;
const nowEpoch = Math.floor(util.time.nowEpochMilliSeconds() / 1000);
const threshold = nowEpoch - 60; // 1分前
const ttlSeconds = +(ctx.env.MESH_HOST_HEARTBEAT_TTL_SECONDS || '150');
const threshold = nowEpoch - ttlSeconds;

return {
operation: 'Query',
Expand Down
10 changes: 10 additions & 0 deletions js/resolvers/Query.listNodesInGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { util } from '@aws-appsync/utils';

export function request(ctx) {
const { groupId, domain } = ctx.args;
const nowEpoch = Math.floor(util.time.nowEpochMilliSeconds() / 1000);

return {
operation: 'Query',
Expand All @@ -14,6 +15,15 @@ export function request(ctx) {
':pk': `DOMAIN#${domain}`,
':sk_prefix': `GROUP#${groupId}#NODE#`
})
},
filter: {
expression: 'attribute_not_exists(#ttl) OR #ttl > :now',
expressionNames: {
'#ttl': 'ttl'
},
expressionValues: util.dynamodb.toMapValues({
':now': nowEpoch
})
}
};
}
Expand Down
6 changes: 3 additions & 3 deletions lib/mesh-v2-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class MeshV2Stack extends cdk.Stack {
});

// Environment variables defaults based on stage
const defaultMaxConnTimeSeconds = stage === 'prod' ? '3000' : '600';
const defaultMaxConnTimeSeconds = stage === 'prod' ? '1500' : '300';

// AppSync GraphQL API for Mesh v2
this.api = new appsync.GraphqlApi(this, 'MeshV2Api', {
Expand All @@ -87,7 +87,7 @@ export class MeshV2Stack extends cdk.Stack {
},
environmentVariables: {
TABLE_NAME: this.table.tableName,
MESH_HOST_HEARTBEAT_INTERVAL_SECONDS: process.env.MESH_HOST_HEARTBEAT_INTERVAL_SECONDS || '30',
MESH_HOST_HEARTBEAT_INTERVAL_SECONDS: process.env.MESH_HOST_HEARTBEAT_INTERVAL_SECONDS || '60',
MESH_HOST_HEARTBEAT_TTL_SECONDS: process.env.MESH_HOST_HEARTBEAT_TTL_SECONDS || '150',
MESH_MEMBER_HEARTBEAT_INTERVAL_SECONDS: process.env.MESH_MEMBER_HEARTBEAT_INTERVAL_SECONDS || '120',
MESH_MEMBER_HEARTBEAT_TTL_SECONDS: process.env.MESH_MEMBER_HEARTBEAT_TTL_SECONDS || '600',
Expand Down Expand Up @@ -367,7 +367,7 @@ export class MeshV2Stack extends cdk.Stack {
LC_ALL: 'en_US.UTF-8',
DYNAMODB_TABLE_NAME: this.table.tableName,
MESH_SECRET_KEY: process.env.MESH_SECRET_KEY || 'default-secret-key',
MESH_HOST_HEARTBEAT_INTERVAL_SECONDS: process.env.MESH_HOST_HEARTBEAT_INTERVAL_SECONDS || '30',
MESH_HOST_HEARTBEAT_INTERVAL_SECONDS: process.env.MESH_HOST_HEARTBEAT_INTERVAL_SECONDS || '60',
MESH_HOST_HEARTBEAT_TTL_SECONDS: process.env.MESH_HOST_HEARTBEAT_TTL_SECONDS || '150',
MESH_MEMBER_HEARTBEAT_INTERVAL_SECONDS: process.env.MESH_MEMBER_HEARTBEAT_INTERVAL_SECONDS || '120',
MESH_MEMBER_HEARTBEAT_TTL_SECONDS: process.env.MESH_MEMBER_HEARTBEAT_TTL_SECONDS || '600',
Expand Down
Loading