@exabugs/dynamodb-client 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +1 -0
- package/dist/client/FindCursor.d.ts +11 -6
- package/dist/client/FindCursor.d.ts.map +1 -1
- package/dist/client/FindCursor.js +30 -11
- package/dist/client/FindCursor.js.map +1 -1
- package/dist/integrations/react-admin/components/ReferenceManyToMany/ReferenceManyToManyField.d.ts.map +1 -1
- package/dist/integrations/react-admin/components/ReferenceManyToMany/ReferenceManyToManyField.js +5 -4
- package/dist/integrations/react-admin/components/ReferenceManyToMany/ReferenceManyToManyField.js.map +1 -1
- package/dist/integrations/react-admin/components/ReferenceManyToMany/ReferenceManyToManyInput.d.ts.map +1 -1
- package/dist/integrations/react-admin/components/ReferenceManyToMany/ReferenceManyToManyInput.js +5 -4
- package/dist/integrations/react-admin/components/ReferenceManyToMany/ReferenceManyToManyInput.js.map +1 -1
- package/dist/integrations/react-admin/components/ReferenceManyToMany/useGetListAll.d.ts +19 -0
- package/dist/integrations/react-admin/components/ReferenceManyToMany/useGetListAll.d.ts.map +1 -0
- package/dist/integrations/react-admin/components/ReferenceManyToMany/useGetListAll.js +36 -0
- package/dist/integrations/react-admin/components/ReferenceManyToMany/useGetListAll.js.map +1 -0
- package/dist/integrations/react-admin/dataProvider.d.ts +15 -1
- package/dist/integrations/react-admin/dataProvider.d.ts.map +1 -1
- package/dist/integrations/react-admin/dataProvider.js +84 -4
- package/dist/integrations/react-admin/dataProvider.js.map +1 -1
- package/dist/integrations/react-admin/index.d.ts +1 -0
- package/dist/integrations/react-admin/index.d.ts.map +1 -1
- package/dist/integrations/react-admin/index.js.map +1 -1
- package/dist/scripts/operations/bulkRecordRepair.js +2 -1
- package/dist/scripts/operations/bulkRecordRepair.js.map +1 -1
- package/dist/server/handler.cjs +359 -237
- package/dist/server/handler.cjs.map +4 -4
- package/dist/server/media-handler.cjs +2 -2
- package/dist/server/media-process-handler-dlq.cjs +331 -209
- package/dist/server/media-process-handler-dlq.cjs.map +4 -4
- package/dist/server/media-process-handler.cjs +330 -208
- package/dist/server/media-process-handler.cjs.map +4 -4
- package/dist/server/operations/find/idQuery.d.ts.map +1 -1
- package/dist/server/operations/find/idQuery.js +75 -43
- package/dist/server/operations/find/idQuery.js.map +1 -1
- package/dist/server/operations/find/shadowQuery.d.ts.map +1 -1
- package/dist/server/operations/find/shadowQuery.js +24 -8
- package/dist/server/operations/find/shadowQuery.js.map +1 -1
- package/dist/server/utils/scanWarning.d.ts +30 -0
- package/dist/server/utils/scanWarning.d.ts.map +1 -0
- package/dist/server/utils/scanWarning.js +63 -0
- package/dist/server/utils/scanWarning.js.map +1 -0
- package/dist/sharp-layer.zip +0 -0
- package/docs/README.md +286 -0
- package/docs/SCAN_WARNING.md +81 -0
- package/package.json +2 -1
- package/terraform/README.md +8 -6
- package/terraform/main.tf +40 -1
- package/terraform/outputs.tf +13 -0
- package/terraform/variables.tf +17 -0
package/docs/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# @exabugs/dynamodb-client
|
|
2
|
+
|
|
3
|
+
DynamoDB Single-Table設計向けのクライアントライブラリ。MongoDB風のAPIでDynamoDBを操作できます。
|
|
4
|
+
|
|
5
|
+
## 特徴
|
|
6
|
+
|
|
7
|
+
- **認証方式別クライアント**: IAM、Cognito、Token認証に最適化
|
|
8
|
+
- **型安全**: TypeScriptで完全に型付けされたAPI
|
|
9
|
+
- **軽量**: 認証方式ごとに必要な依存関係のみを含む
|
|
10
|
+
- **MongoDB風API**: 使いやすいCRUD操作
|
|
11
|
+
|
|
12
|
+
## インストール
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @exabugs/dynamodb-client
|
|
16
|
+
# または
|
|
17
|
+
pnpm add @exabugs/dynamodb-client
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## クイックスタート
|
|
21
|
+
|
|
22
|
+
### Cognito認証(ブラウザ/Node.js)
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { DynamoClient } from '@exabugs/dynamodb-client/client/cognito';
|
|
26
|
+
|
|
27
|
+
const client = new DynamoClient(FUNCTION_URL, {
|
|
28
|
+
auth: {
|
|
29
|
+
getToken: async () => {
|
|
30
|
+
// Cognitoトークンを取得
|
|
31
|
+
return token;
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await client.connect();
|
|
37
|
+
|
|
38
|
+
const db = client.db('mydb');
|
|
39
|
+
const articles = db.collection('articles');
|
|
40
|
+
|
|
41
|
+
// CRUD操作
|
|
42
|
+
const results = await articles
|
|
43
|
+
.find({ status: 'published' })
|
|
44
|
+
.sort({ publishedAt: 'desc' })
|
|
45
|
+
.limit(10)
|
|
46
|
+
.toArray();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### IAM認証(Lambda/Node.js)
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { DynamoClient } from '@exabugs/dynamodb-client/client/iam';
|
|
53
|
+
|
|
54
|
+
const client = new DynamoClient(FUNCTION_URL, {
|
|
55
|
+
auth: {
|
|
56
|
+
region: process.env.AWS_REGION!,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await client.connect();
|
|
61
|
+
|
|
62
|
+
const db = client.db('mydb');
|
|
63
|
+
const articles = db.collection('articles');
|
|
64
|
+
|
|
65
|
+
await articles.insertOne({
|
|
66
|
+
title: 'New Article',
|
|
67
|
+
status: 'draft',
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Token認証(固定トークン)
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { DynamoClient } from '@exabugs/dynamodb-client/client/token';
|
|
75
|
+
|
|
76
|
+
const client = new DynamoClient(FUNCTION_URL, {
|
|
77
|
+
auth: {
|
|
78
|
+
token: 'your-static-token',
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await client.connect();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Records Lambda(サーバー側実装)
|
|
86
|
+
|
|
87
|
+
このライブラリには、Records Lambda(サーバー側実装)も含まれています。
|
|
88
|
+
|
|
89
|
+
### デプロイ方法
|
|
90
|
+
|
|
91
|
+
Terraformモジュールを使用してデプロイします。
|
|
92
|
+
|
|
93
|
+
```hcl
|
|
94
|
+
module "lambda_records" {
|
|
95
|
+
source = "github.com/your-org/dynamodb-client//packages/core/terraform"
|
|
96
|
+
# または
|
|
97
|
+
# source = "../packages/core/terraform" # ローカル開発時
|
|
98
|
+
|
|
99
|
+
project_name = "my-project"
|
|
100
|
+
environment = "dev"
|
|
101
|
+
region = "us-east-1"
|
|
102
|
+
|
|
103
|
+
# DynamoDB設定
|
|
104
|
+
dynamodb_table_name = "my-table"
|
|
105
|
+
dynamodb_table_arn = "arn:aws:dynamodb:..."
|
|
106
|
+
|
|
107
|
+
# Cognito設定
|
|
108
|
+
cognito_user_pool_id = "us-east-1_xxxxx"
|
|
109
|
+
|
|
110
|
+
# シャドウ設定(base64エンコード)
|
|
111
|
+
shadow_config = base64encode(file("shadow.config.json"))
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### ビルド
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cd packages/core
|
|
119
|
+
pnpm build # クライアントとサーバーの両方をビルド
|
|
120
|
+
pnpm build:lambda # サーバーのみをビルド
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
詳細は[TerraformモジュールのREADME](./terraform/README.md)を参照してください。
|
|
124
|
+
|
|
125
|
+
## メンテナンススクリプト
|
|
126
|
+
|
|
127
|
+
### シャドーレコード修復
|
|
128
|
+
|
|
129
|
+
DynamoClient(IAM認証)を使用してシャドーレコードを修復するスクリプトです。
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Dry run(変更なし)
|
|
133
|
+
pnpm repair-shadows -- --resource articles --dry-run
|
|
134
|
+
|
|
135
|
+
# 単一レコードを修復
|
|
136
|
+
pnpm repair-shadows -- --resource articles --id <RECORD_ID> --repair
|
|
137
|
+
|
|
138
|
+
# 全レコードを修復
|
|
139
|
+
pnpm repair-shadows -- --resource articles --repair
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
環境変数:
|
|
143
|
+
|
|
144
|
+
- `ENV`: 環境(dev/stg/prd)、デフォルト: `dev`
|
|
145
|
+
- `AWS_REGION`: AWSリージョン、デフォルト: `us-east-1`
|
|
146
|
+
- `RECORDS_API_URL`: Records Lambda Function URL(省略時はAWS CLIで自動取得)
|
|
147
|
+
|
|
148
|
+
## ドキュメント
|
|
149
|
+
|
|
150
|
+
- [使用ガイド](./CLIENT_USAGE.md) - 認証方式別の詳細な使用方法
|
|
151
|
+
- [アーキテクチャ](./ARCHITECTURE.md) - 内部設計とコンポーネント構成
|
|
152
|
+
|
|
153
|
+
## API型定義
|
|
154
|
+
|
|
155
|
+
Records Lambda API の型定義は `@exabugs/dynamodb-client/types` からエクスポートされています。
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import type {
|
|
159
|
+
ApiErrorResponse, // 操作タイプ
|
|
160
|
+
ApiOperation, // リクエスト型
|
|
161
|
+
ApiRequest,
|
|
162
|
+
ApiResponse, // レスポンス型
|
|
163
|
+
ApiSuccessResponse,
|
|
164
|
+
BulkOperationResult,
|
|
165
|
+
DeleteOneParams,
|
|
166
|
+
FindManyParams,
|
|
167
|
+
FindOneParams,
|
|
168
|
+
FindParams, // データ型
|
|
169
|
+
FindResult,
|
|
170
|
+
InsertOneParams,
|
|
171
|
+
OperationError,
|
|
172
|
+
UpdateOneParams,
|
|
173
|
+
} from '@exabugs/dynamodb-client/types';
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## シャドウ管理機能
|
|
177
|
+
|
|
178
|
+
このライブラリには、DynamoDB Single-Table設計におけるシャドウレコード管理機能が含まれています。
|
|
179
|
+
|
|
180
|
+
### インストール
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pnpm add @exabugs/dynamodb-client
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 使用方法
|
|
187
|
+
|
|
188
|
+
#### シャドウSK生成
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { generateShadowSK } from '@exabugs/dynamodb-client/shadows';
|
|
192
|
+
|
|
193
|
+
// 文字列フィールド
|
|
194
|
+
const nameSK = generateShadowSK('name', 'Tech News', '01HZXY123', 'string');
|
|
195
|
+
// => 'name#Tech#News#id#01HZXY123'
|
|
196
|
+
|
|
197
|
+
// 数値フィールド
|
|
198
|
+
const prioritySK = generateShadowSK('priority', 123, '01HZXY123', 'number');
|
|
199
|
+
// => 'priority#00000000000000000123#id#01HZXY123'
|
|
200
|
+
|
|
201
|
+
// 日時フィールド
|
|
202
|
+
const createdAtSK = generateShadowSK(
|
|
203
|
+
'createdAt',
|
|
204
|
+
'2025-11-12T10:00:00.000Z',
|
|
205
|
+
'01HZXY123',
|
|
206
|
+
'datetime'
|
|
207
|
+
);
|
|
208
|
+
// => 'createdAt#2025-11-12T10:00:00.000Z#id#01HZXY123'
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### シャドウ差分計算
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { calculateShadowDiff } from '@exabugs/dynamodb-client/shadows';
|
|
215
|
+
|
|
216
|
+
const oldKeys = ['name#Old#Name#id#01HZXY123', 'priority#00000000000000000010#id#01HZXY123'];
|
|
217
|
+
|
|
218
|
+
const newKeys = ['name#New#Name#id#01HZXY123', 'priority#00000000000000000010#id#01HZXY123'];
|
|
219
|
+
|
|
220
|
+
const diff = calculateShadowDiff(oldKeys, newKeys);
|
|
221
|
+
// => {
|
|
222
|
+
// toDelete: ['name#Old#Name#id#01HZXY123'],
|
|
223
|
+
// toAdd: ['name#New#Name#id#01HZXY123']
|
|
224
|
+
// }
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### 設定管理
|
|
228
|
+
|
|
229
|
+
v0.3.x以降、shadow.config.jsonファイルは不要になりました。すべてのフィールドが自動的にシャドウ化されます。
|
|
230
|
+
|
|
231
|
+
環境変数で動作をカスタマイズできます:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// 環境変数(デフォルト値)
|
|
235
|
+
process.env.SHADOW_CREATED_AT_FIELD = 'createdAt'; // 作成日時フィールド名
|
|
236
|
+
process.env.SHADOW_UPDATED_AT_FIELD = 'updatedAt'; // 更新日時フィールド名
|
|
237
|
+
process.env.SHADOW_STRING_MAX_BYTES = '100'; // 文字列の最大バイト数
|
|
238
|
+
process.env.SHADOW_NUMBER_PADDING = '15'; // 数値のパディング桁数
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
シャドウレコードの生成例:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { generateShadowSK } from '@exabugs/dynamodb-client/shadows';
|
|
245
|
+
|
|
246
|
+
// 各フィールドタイプのシャドウSK生成
|
|
247
|
+
const nameSK = generateShadowSK('name', 'Tech News', '01HZXY123', 'string');
|
|
248
|
+
// => 'name#Tech#News#id#01HZXY123'
|
|
249
|
+
|
|
250
|
+
const prioritySK = generateShadowSK('priority', 123, '01HZXY123', 'number');
|
|
251
|
+
// => 'priority#00000000000000000123#id#01HZXY123'
|
|
252
|
+
|
|
253
|
+
const dateSK = generateShadowSK('createdAt', '2025-11-12T10:00:00.000Z', '01HZXY123', 'datetime');
|
|
254
|
+
// => 'createdAt#2025-11-12T10:00:00.000Z#id#01HZXY123'
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### エスケープルール
|
|
258
|
+
|
|
259
|
+
文字列値は以下のルールでエスケープされます:
|
|
260
|
+
|
|
261
|
+
- `#` → `##`
|
|
262
|
+
- スペース → `#`
|
|
263
|
+
|
|
264
|
+
例:
|
|
265
|
+
|
|
266
|
+
- `"Tech News"` → `"Tech#News"`
|
|
267
|
+
- `"AI#ML"` → `"AI##ML"`
|
|
268
|
+
|
|
269
|
+
## パッケージ構成
|
|
270
|
+
|
|
271
|
+
| エクスポート | 環境 | 用途 | バンドルサイズ |
|
|
272
|
+
| ----------------------------------------- | ------- | ------------------------------ | -------------- |
|
|
273
|
+
| `@exabugs/dynamodb-client/client/iam` | Node.js | IAM認証クライアント | ~500KB |
|
|
274
|
+
| `@exabugs/dynamodb-client/client/cognito` | 共通 | Cognito認証クライアント | ~50KB |
|
|
275
|
+
| `@exabugs/dynamodb-client/client/token` | 共通 | Token認証クライアント | ~50KB |
|
|
276
|
+
| `@exabugs/dynamodb-client/server` | Node.js | Records Lambda(サーバー実装) | ~1.2MB |
|
|
277
|
+
| `@exabugs/dynamodb-client/server/handler` | Node.js | Records Lambda ハンドラー | ~1.2MB |
|
|
278
|
+
| `@exabugs/dynamodb-client/shadows` | Node.js | シャドウ管理機能 | ~10KB |
|
|
279
|
+
| `@exabugs/dynamodb-client/types` | 共通 | API型定義 | - |
|
|
280
|
+
| `@exabugs/dynamodb-client/terraform` | - | Terraformモジュール | - |
|
|
281
|
+
|
|
282
|
+
## ライセンス
|
|
283
|
+
|
|
284
|
+
MIT License - 詳細は [LICENSE](LICENSE) ファイルを参照してください。
|
|
285
|
+
|
|
286
|
+
Copyright (c) 2024 exabugs
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# 疎な検索の警告(SPARSE_SCAN)と対応
|
|
2
|
+
|
|
3
|
+
## 方針
|
|
4
|
+
|
|
5
|
+
インデックス(シャドー)で絞り込めない条件の検索は、一致する件数に対して多くのレコードを読む必要がある。
|
|
6
|
+
**結果を正しく返すため、検索を途中で打ち切ることはしない**(途中までを「全件」「最終ページ」として返すと、
|
|
7
|
+
呼び出し側が結果を誤って扱い、データの欠落や関連の誤削除につながる)。
|
|
8
|
+
|
|
9
|
+
代わりに、1リクエストで読んだ件数が閾値を超えたとき**警告を記録**する。警告に気づいて、
|
|
10
|
+
インデックス・スキーマ・ロジックの見直しに着手することが目的。
|
|
11
|
+
|
|
12
|
+
## 何が警告されるか
|
|
13
|
+
|
|
14
|
+
Records Lambda が、次の経路で 1 リクエストあたりの読み取り件数を数える。
|
|
15
|
+
|
|
16
|
+
| `path` | どんな検索か |
|
|
17
|
+
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| `id-filtered-scan` | `sort: id`(react-admin の既定)に **id 以外のフィールドの絞り込み**を付けた検索。演算子(`$eq` を含む)によらず、id 順に全件を読んでメモリで絞る |
|
|
19
|
+
| `shadow-filter-first` | 並べ替えフィールドとは別のフィールドを `$eq` で先に絞り(`schema.cardinality` が perPage/2 を超える場合のみ)、残りの条件をメモリで絞る検索。全件をメモリに載せる |
|
|
20
|
+
| `shadow-full-scan` | 並べ替えフィールド以外への絞り込みで、`schema.cardinality` が未設定/小さい `$eq`、または `$contains` `$ne` `$in` `$regex` など(並べ替えフィールド自身へのこれらの演算子も含む)。並べ替えフィールドの全件をメモリに載せる |
|
|
21
|
+
|
|
22
|
+
閾値は環境変数 `SCAN_WARN_THRESHOLD`(既定 5000 件)。Terraform では `scan_warn_threshold`。
|
|
23
|
+
閾値に達した時点で 1 リクエストにつき 1 回、次の JSON を `warn` で出す(Lambda がタイムアウトしても残る)。
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"level": "warn",
|
|
28
|
+
"event": "SPARSE_SCAN",
|
|
29
|
+
"resource": "venues",
|
|
30
|
+
"path": "id-filtered-scan",
|
|
31
|
+
"sortField": "id",
|
|
32
|
+
"filters": ["name:$contains"],
|
|
33
|
+
"scanned": 5000,
|
|
34
|
+
"threshold": 5000,
|
|
35
|
+
"requestId": "…",
|
|
36
|
+
"actions": ["インデックス: …", "スキーマ: …", "ロジック: …"]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
クライアントの `toArrayAll()` も、100 ページ(既定。`warnPages` で変更)を超えるとコンソールに警告する。
|
|
41
|
+
こちらも取得は打ち切らない。`maxPages` を**明示した**場合のみ、超過時にエラーを投げる(黙って切り詰めない)。
|
|
42
|
+
|
|
43
|
+
## 警告に気づく
|
|
44
|
+
|
|
45
|
+
`terraform/` を使っている場合、`event = "SPARSE_SCAN"` のログをメトリクス
|
|
46
|
+
`<project>/<env>/Records` の `SparseScanCount` にし、CloudWatch アラーム
|
|
47
|
+
`<project>-<env>-records-sparse-scan` を作る。通知するには `scan_warning_alarm_actions` に
|
|
48
|
+
SNS トピックの ARN を渡す。Terraform を使わない場合は、同じ条件でメトリクスフィルタを作る。
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
{ $.event = "SPARSE_SCAN" }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 警告が出たら
|
|
55
|
+
|
|
56
|
+
ログの `resource` / `path` / `filters` / `sortField` から、どの検索かを特定して見直す。
|
|
57
|
+
|
|
58
|
+
1. **インデックスを見直す**
|
|
59
|
+
- 全フィールドは自動でシャドー(インデックス)化されている。効くかどうかは**並べ替えフィールドと演算子**で決まる
|
|
60
|
+
- **絞り込みに使うフィールドで並べ替える。** 並べ替えフィールドへの `$eq` `$gt` `$gte` `$lt` `$lte` `$starts` は、キー条件として直接読み取れる。`sort: id` とフィルタの組み合わせ(`id-filtered-scan`)は、どの演算子でも全件走査になるので避ける
|
|
61
|
+
- 並べ替えと別のフィールドを `$eq` で絞る場合は、`schema.cardinality` にそのフィールドの値の種類数を設定する(perPage/2 を超えると、そのフィールドで先に絞る filter-first が使われる)
|
|
62
|
+
- `$contains` `$ne` `$in` `$regex` などはインデックスを使えない。`$eq` / `$starts` / 範囲に置き換えられないか検討する
|
|
63
|
+
2. **スキーマを見直す**
|
|
64
|
+
- 絞り込み条件で値を分けられるなら、コレクションを分ける、または条件を値に持つ別フィールド(検索用の接頭辞付きの値など)を追加する(非正規化)
|
|
65
|
+
- 多対多の中間テーブルなど、関連が増え続ける構造は、片側を並べ替えフィールドにして絞れる形にする
|
|
66
|
+
3. **ロジックを見直す**
|
|
67
|
+
- その絞り込みを毎回行う必要があるか、条件を変えられないか、結果をキャッシュできないかを確認する
|
|
68
|
+
- 全件が必要な処理(バッチ・修復スクリプトなど)は、管理画面や API の通常経路ではなく、
|
|
69
|
+
`sort({ id: 'asc' }).toArrayAll()`(id 順のカーソル方式。全件で O(件数))を使うバッチとして分ける。
|
|
70
|
+
オフセット方式の経路(`shadow-*`)で `toArrayAll` を使うと、ページごとに全件を読み直すため読み取り量が件数の二乗になる
|
|
71
|
+
|
|
72
|
+
見直した後は、同じ検索で警告が出なくなったことをログで確認する。閾値は、
|
|
73
|
+
アプリの通常の最大データ量に合わせて調整してよい(上げて警告を黙らせるのではなく、見直しを優先する)。
|
|
74
|
+
|
|
75
|
+
## 補足
|
|
76
|
+
|
|
77
|
+
- 警告は `LOG_LEVEL` に関わらず出力される(`LOG_LEVEL=error` でも出る)
|
|
78
|
+
- `scan_warning_alarm_actions` が空のままだと、アラーム状態が記録されるだけで誰にも通知されない。
|
|
79
|
+
気づくためには SNS トピックなどの通知先を渡すこと
|
|
80
|
+
- アラームとメトリクスの作成には、デプロイ用ロールに `logs:PutMetricFilter` `cloudwatch:PutMetricAlarm`
|
|
81
|
+
`cloudwatch:TagResource` が必要
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exabugs/dynamodb-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "DynamoDB Single-Table Client SDK with MongoDB-like API, Shadow Records, and Lambda implementation for serverless applications",
|
|
5
5
|
"author": "exabugs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"bin",
|
|
39
39
|
"dist",
|
|
40
40
|
"terraform",
|
|
41
|
+
"docs/SCAN_WARNING.md",
|
|
41
42
|
"README.md",
|
|
42
43
|
"LICENSE",
|
|
43
44
|
"CHANGELOG.md"
|
package/terraform/README.md
CHANGED
|
@@ -82,12 +82,14 @@ module "lambda_records" {
|
|
|
82
82
|
|
|
83
83
|
### オプション変数
|
|
84
84
|
|
|
85
|
-
| 変数名
|
|
86
|
-
|
|
|
87
|
-
| `log_retention_days`
|
|
88
|
-
| `log_level`
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
85
|
+
| 変数名 | 型 | デフォルト値 | 説明 |
|
|
86
|
+
| ---------------------------- | ------------ | ------------ | ------------------------------------------------------------------------------------------------------ |
|
|
87
|
+
| `log_retention_days` | number | 7 | CloudWatch Logsの保持期間(日) |
|
|
88
|
+
| `log_level` | string | "info" | ログレベル(debug/info/warn/error) |
|
|
89
|
+
| `scan_warn_threshold` | number | 5000 | 疎な検索(SPARSE_SCAN)の警告を出す閾値(1リクエストで読む件数)。打ち切りはしない。docs/SCAN_WARNING.md |
|
|
90
|
+
| `scan_warning_alarm_actions` | list(string) | [] | SPARSE_SCAN アラームの通知先(SNS トピック ARN)。空だと誰にも通知されない |
|
|
91
|
+
| `lambda_timeout` | number | 900 | Lambda関数のタイムアウト(秒) |
|
|
92
|
+
| `lambda_memory` | number | 512 | Lambda関数のメモリサイズ(MB) |
|
|
91
93
|
|
|
92
94
|
## 出力
|
|
93
95
|
|
package/terraform/main.tf
CHANGED
|
@@ -80,6 +80,43 @@ resource "aws_cloudwatch_log_group" "lambda_records" {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
# 疎な検索(SPARSE_SCAN)の警告を検知する
|
|
84
|
+
# Lambda は、1リクエストで読んだ件数が SCAN_WARN_THRESHOLD を超えると event=SPARSE_SCAN の警告ログを出す。
|
|
85
|
+
# 結果を壊さないため検索は打ち切らない。警告に気づいて、インデックス・スキーマ・ロジックの
|
|
86
|
+
# 見直しに着手できるよう、メトリクスとアラームにする(対応手順: docs/SCAN_WARNING.md)
|
|
87
|
+
resource "aws_cloudwatch_log_metric_filter" "sparse_scan" {
|
|
88
|
+
name = "${var.project_name}-${var.environment}-records-sparse-scan"
|
|
89
|
+
log_group_name = aws_cloudwatch_log_group.lambda_records.name
|
|
90
|
+
pattern = "{ $.event = \"SPARSE_SCAN\" }"
|
|
91
|
+
|
|
92
|
+
metric_transformation {
|
|
93
|
+
name = "SparseScanCount"
|
|
94
|
+
namespace = "${var.project_name}/${var.environment}/Records"
|
|
95
|
+
value = "1"
|
|
96
|
+
default_value = "0"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
resource "aws_cloudwatch_metric_alarm" "sparse_scan" {
|
|
101
|
+
alarm_name = "${var.project_name}-${var.environment}-records-sparse-scan"
|
|
102
|
+
alarm_description = "疎な検索(SPARSE_SCAN)の警告が出ています。ログの actions に従って、インデックス・スキーマ・ロジックを見直してください(docs/SCAN_WARNING.md)"
|
|
103
|
+
namespace = aws_cloudwatch_log_metric_filter.sparse_scan.metric_transformation[0].namespace
|
|
104
|
+
metric_name = aws_cloudwatch_log_metric_filter.sparse_scan.metric_transformation[0].name
|
|
105
|
+
statistic = "Sum"
|
|
106
|
+
period = 300
|
|
107
|
+
evaluation_periods = 1
|
|
108
|
+
threshold = 1
|
|
109
|
+
comparison_operator = "GreaterThanOrEqualToThreshold"
|
|
110
|
+
treat_missing_data = "notBreaching"
|
|
111
|
+
|
|
112
|
+
alarm_actions = var.scan_warning_alarm_actions
|
|
113
|
+
ok_actions = var.scan_warning_alarm_actions
|
|
114
|
+
|
|
115
|
+
tags = {
|
|
116
|
+
Name = "${var.project_name}-${var.environment}-records-sparse-scan"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
83
120
|
# Lambda関数のZIPファイルを作成
|
|
84
121
|
data "archive_file" "lambda_records" {
|
|
85
122
|
type = "zip"
|
|
@@ -118,6 +155,8 @@ resource "aws_lambda_function" "records" {
|
|
|
118
155
|
COGNITO_USER_POOL_ID = var.cognito_user_pool_id
|
|
119
156
|
COGNITO_CLIENT_ID = var.cognito_client_id
|
|
120
157
|
LOG_LEVEL = var.log_level
|
|
158
|
+
# 疎な検索(SPARSE_SCAN)の警告閾値(1リクエストで読む件数)
|
|
159
|
+
SCAN_WARN_THRESHOLD = var.scan_warn_threshold
|
|
121
160
|
# シャドウ設定(環境変数ベース)
|
|
122
161
|
SHADOW_CREATED_AT_FIELD = var.shadow_created_at_field
|
|
123
162
|
SHADOW_UPDATED_AT_FIELD = var.shadow_updated_at_field
|
|
@@ -215,7 +254,7 @@ module "parameter_store" {
|
|
|
215
254
|
records_iam_function_url = aws_lambda_function_url.records_iam.function_url
|
|
216
255
|
# IAM URL は常に同じ apply で作るため true を明示する(未確定の値の有無で count を決めない)
|
|
217
256
|
create_records_iam_url_parameter = true
|
|
218
|
-
records_function_arn
|
|
257
|
+
records_function_arn = aws_lambda_function.records.arn
|
|
219
258
|
|
|
220
259
|
# Cognito設定
|
|
221
260
|
cognito_user_pool_id = var.cognito_user_pool_id
|
package/terraform/outputs.tf
CHANGED
|
@@ -59,3 +59,16 @@ output "iam_alias_arn" {
|
|
|
59
59
|
description = "AWS_IAM認証用エイリアスのARN。呼び出し元IAMロールのlambda:InvokeFunctionUrl権限のResourceに指定する"
|
|
60
60
|
value = aws_lambda_alias.iam.arn
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
output "sparse_scan_alarm_name" {
|
|
64
|
+
description = "疎な検索(SPARSE_SCAN)の CloudWatch アラーム名"
|
|
65
|
+
value = aws_cloudwatch_metric_alarm.sparse_scan.alarm_name
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
output "sparse_scan_metric" {
|
|
69
|
+
description = "疎な検索(SPARSE_SCAN)のメトリクス(namespace / name)"
|
|
70
|
+
value = {
|
|
71
|
+
namespace = aws_cloudwatch_log_metric_filter.sparse_scan.metric_transformation[0].namespace
|
|
72
|
+
name = aws_cloudwatch_log_metric_filter.sparse_scan.metric_transformation[0].name
|
|
73
|
+
}
|
|
74
|
+
}
|
package/terraform/variables.tf
CHANGED
|
@@ -55,6 +55,23 @@ variable "log_level" {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
variable "scan_warn_threshold" {
|
|
59
|
+
description = "疎な検索(SPARSE_SCAN)の警告を出す閾値(1リクエストで読むレコード件数)。超えても検索は打ち切らず、警告のみ記録する"
|
|
60
|
+
type = number
|
|
61
|
+
default = 5000
|
|
62
|
+
|
|
63
|
+
validation {
|
|
64
|
+
condition = var.scan_warn_threshold >= 1 && floor(var.scan_warn_threshold) == var.scan_warn_threshold
|
|
65
|
+
error_message = "scan_warn_threshold は 1 以上の整数である必要があります。"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
variable "scan_warning_alarm_actions" {
|
|
70
|
+
description = "SPARSE_SCAN アラームの通知先(SNS トピック ARN の配列)。空ならアラーム状態の記録のみ"
|
|
71
|
+
type = list(string)
|
|
72
|
+
default = []
|
|
73
|
+
}
|
|
74
|
+
|
|
58
75
|
# シャドウ設定(環境変数ベース)
|
|
59
76
|
variable "shadow_created_at_field" {
|
|
60
77
|
description = "作成日時フィールド名"
|