已分发 图表
结构化数据存储由DynamoDB支持,具有二级索引和丰富的查询能力.
何时使用 : 您需要通过多个字段查询,使用复合键,或者执行以排序为基础的范围查询. 对有关系,时间序列数据和需要多个索引的存取模式的实体有好处.
不使用时 : 如果您只需要单键检查, 请使用 KVStore。如果需要完整的 SQL(joins,聚合),则使用 Database。 。 。 .
设计与模拟等价细节 : 设计
API 密码
const table = new DistributedTable(scope, id, options)
类型推论( 重要 ): 执行 没有 通过一个单一的显式类型参数,例如
new DistributedTable<MyType>(...)。只做这样的针T并让密钥/索引的通用符号回到其广泛的默认值,这打破了密钥类型的推论——get()财务报告和审定财务报表query({ where })然后会要求 每个 字段,而不只是关键字段。 要么让所有一般人推断(new DistributedTable(scope, id, { schema, key, indexes })没有明确<...>),或通过所有三个通名. 请注意:as const一个人做 没有 修复它.
| 方法 | 回返 | 说明 |
|---|---|---|
get(key) | Promise<T | null> | 按主键获取单个项目 . |
put(item, options?) | Promise<void> | 存储一个物品。 覆盖, 除非设置条件 . |
delete(key, options?) | Promise<void> | 按主键删除项目 . |
query(options) | AsyncIterable<T> | 按索引或主键查询项目 . |
scan(options?) | AsyncIterable<T> | 完成所有项目。 精通大型数据集. |
getBatch(keys) | Promise<(T | null)[]> | 按键获取多个项目 . |
putBatch(items) | Promise<void> | 储存多件物品. |
deleteBatch(keys) | Promise<void> | 按键删除多个项目 . |
DistributedTable.fromExisting(tableName) | ExternalTableRef | 包好先前存在的DynamomDB表格. |
只有运行时间. 数据方法get, (中文(简体) ), put, (中文(简体) ), delete, (中文(简体) ), query, (中文(简体) ), scan, (中文(简体) ), getBatch, (中文(简体) ), putBatch, (中文(简体) ), deleteBatch(按请求时间运行) ApiNamespace 方法, RawRoute 处理器、工作处理器或运行时脚本, 没有 在您的顶级 aws-blocks/index.ts。顶级代码运行于 CDK 合成期间,其中块解决其基础设施构造,其数据方法为 守护:顶级调用 Error 读取 DistributedTable.<method>() cannot be called during CDK synth. Data methods run at request time — …。对于种子数据,请从处理器内部或单独的运行时脚本中进行。 在模块范围构造块是好的;只有方法调用才能移动到处理器中.
正在收集
scan()结果 : 喜欢query(), (中文(简体) ),scan()返回一个AsyncIterable- 与await Array.fromAsync(table.scan())或直线for await. 偏爱query()结束scan()(扫描仪读取每件物品).
选项
| 选项 | 类型 | 需求 | 说明 |
|---|---|---|---|
schema | StandardSchemaV1 | 对 | 运行时验证计划(佐德,瓦利博特,ArkType等). 类型 T 根据计划推断. |
key | TableKeyConfig<T> | 对 | 主密钥配置 : { partitionKey, sortKey? }中必须存在字段名称. |
indexes | Record<string, TableKeyConfig<T>> | 没有 | 全球二级指数定义. |
ttl | keyof T & string | 没有 | 在指定的属性上启用 DynamoDB TTL 。 字段应当包含以秒计的Unix时间戳. |
pointInTimeRecovery | boolean | { retentionDays: number } | 没有 | 即时复苏(接续后援). true = 打开默认的35天窗口; false =关闭; { retentionDays: n } 继续 n- 日间窗口(1至35岁) (中文(简体) ). 堆栈默认值 defaults.pointInTimeRecovery (关于 BlocksPresets.production关闭在下面 sandbox) (中文(简体) ). 每GB-月备份存储的账单;更短的窗口修剪成本. |
protection | 'disposable' | 'retained' | 'locked' | 没有 | 表格的销毁有多难(spans去除政策+删除保护). 'disposable' = 随堆栈被删除; 'retained' = 堆栈删除时成为孤儿,但直接删除仍然有效; 'locked' =孤儿 财务报告和审定财务报表 删除保护。 省略时, 删除策略 + 删除保护跟随堆栈 defaults (单位:千美元)BlocksPresets.production ≈ 'locked', (中文(简体) ), sandbox ≈ 'disposable') (中文(简体) ). |
encryption | 'aws-managed' | 'customer-managed' | ExternalKmsKeyRef | 没有 | At -rest 加密密钥. 'aws-managed' (默认)使用 aws/dynamodb KMS 密钥(可审计,无密钥充电); 'customer-managed' 提供专门的CMK; 通过 DistributedTable.fromKmsKey(arn) 以您拥有的现有CMK加密(可跨表格共享). |
readValidation | 'off' | 'coerce' | 'strict' | 没有 | 如何读取(get编号 :getBatch编号 :query编号 :scan将存储的项目与 schema。 。 。 . 'coerce' (单位:千美元)默认返回被胁迫值,并在失败时返回原始值+警告(从未丢出); 'strict' 掷出 ValidationFailed 关于不符合同的项目; 'off' 返回未验证的原始值。 见 阅读和计划演变。 。 。 . |
table | ExternalTableRef | 没有 | 将已有的 DynamoDB 表格包起来, 而不是创建 。 Durable/加密选项被忽略——客户拥有表格的配置. |
logger | ChildLogger | 没有 | 内部操作的可选日志。 省略时, 在警告级别创建默认 Logger readValidation: 'coerce' 后退警告浮出水面. |
密钥对象模式
所有接受密钥的方法(get, (中文(简体) ), delete, (中文(简体) ), getBatch, (中文(简体) ), deleteBatch)用分区键字段取出一个密钥对象(如果定义了,则排序密钥字段)。 密钥类型是从您的策略和密钥配置中计算出来的 —— TypeScript 完全执行正确的字段 :
// Table with partition key + sort key
const orders = new DistributedTable(scope, 'orders', {
schema: orderSchema,
key: { partitionKey: 'userId', sortKey: 'orderId' },
});
// Key object requires both fields
await orders.get({ userId: 'alice', orderId: '001' });
// Table with partition key only
const settings = new DistributedTable(scope, 'settings', {
schema: settingsSchema,
key: { partitionKey: 'settingId' },
});
// Key object requires only the partition key
await settings.get({ settingId: 'theme' });
查询
query() 选择单个选项对象。 指定 index 以查询一个GSI,或省略它以查询主键。 这个 where 句式是类型安全的——字段名称和条件类型是从索引(或主密钥)定义中计算出来的.
// Query a GSI
for await (const order of orders.query({
index: 'byStatus',
where: { status: { equals: 'pending' } },
limit: 10,
order: 'desc',
})) {
console.log(order);
}
// Query the primary key (omit index)
for await (const order of orders.query({
where: { userId: { equals: 'alice' }, orderId: { beginsWith: '2024-' } },
})) {
console.log(order);
}
提示 : 当将所有结果收集到数组时,使用
Array.fromAsync()而不是手动循环 :const pending = await Array.fromAsync(orders.query({ index: 'byStatus', where: { status: { equals: 'pending' } }, }));
查询选项 :
| 外地 | 类型 | 需求 | 说明 |
|---|---|---|---|
index | keyof Indexes | 没有 | GSI 查询 。 关闭查询主密钥 . |
where | KeyCondition<T, K> | 对 | 关键条件。 分区密钥需要 { equals }排序密钥支持 equals, (中文(简体) ), greaterThan, (中文(简体) ), lessThan, (中文(简体) ), between, (中文(简体) ), beginsWith等 (中文(简体) ). |
limit | number | 没有 | 要返回的最大项数 . |
order | 'asc' | 'desc' | 没有 | 排序键的方向 。 默认为 'asc'。 。 。 . |
有条件的操作
两者 put 财务报告和审定财务报表 delete 接受可选条件:
// Only write if key doesn't exist (idempotent create)
await table.put(item, { ifNotExists: true });
// Only write if existing item's field matches (optimistic locking)
await table.put(updatedItem, { ifFieldEquals: { version: 3 } });
// Only delete if item exists
await table.delete(key, { ifExists: true });
// Only delete if field matches
await table.delete(key, { ifFieldEquals: { status: 'archived' } });
所有条件失败 error.name === DistributedTableErrors.ConditionalCheckFailed序列化为JSON-RPC 409 (冲突) 在模拟时间和AWS运行时间, 仅 ifFieldEquals 乐观-锁定冲突被标出可以挽回; ifNotExists 编号 : ifExists 存在断言 没有 可回收( 盲重试失败 ) .
无部分更新 : 没有
update()或者说patch()方法。 要更改字段,请做一个读取-修改-写作——get()然后改变它put()整个东西回来。 为安全同步更新, 请通过{ ifFieldEquals: { version: <previous> } }改为put()故此写入失败( 通过ConditionalCheckFailed),如果另一个编剧同时更改了项目(乐观地锁定).
阅读和计划演变
写作总是以 schema。读取根据 readValidation 选项,此选项在方案更改后很重要:在旧方案下写的行可能不再匹配已宣布的类型 T 新增加的一个领域是: 缺席 从读取( 所以数值沉默地违反) T,则a .default() 既未适用,也未坚持回函,a 需要,无违约 字段使上面的读-修改-写周期 写入失败 作为 put() 拒绝留下的形状.
readValidation 有三个模式:
| 模式 | 读取 | 关于一个不符合同的项目 |
|---|---|---|
'coerce' (默认) | 返回图中的被强制输出(默认已填充,类型已缩小) | 返回时 原始内容 数值 + 记录一个警告 - 从不扔 |
'strict' | 对照计划进行验证 | 掷出 ValidationFailed |
'off' | 返回原始存储值,没有验证 | 返回为-is |
默认 'coerce' 关闭计划-进化差距
const orderSchema = z.object({
orderId: z.string(),
total: z.number(),
currency: z.string().default('USD'), // added in a later release
});
const orders = new DistributedTable(scope, 'orders', {
schema: orderSchema,
key: { partitionKey: 'orderId' },
// readValidation: 'coerce' is the default
});
const order = await orders.get({ orderId: 'o1' }); // legacy row → { …, currency: 'USD' }
await orders.put({ ...order, total: 20 }); // round-trips without ValidationFailed
'coerce' 从不掷出 : 返回真正不能被胁迫的值(例如,没有默认要求的字段) 作为 有警告,所以无法恢复的行 保持可读的迁移和 get() 永远不要扔坏一行.
最佳强制(验证人-依赖). 强迫取决于计划 转换 它的投入。 Zod填充默认和铸造;一个只检查的标准Schema验证器(一些Valibot/ArkType schemas)验证而不转换,所以
'coerce'返回这些值的不变值——它从未发明数据.
'coerce'保存未在计划内保存的密钥. 许多计划验证时丢弃未识别的密钥( Zod 对象计划).strip()——然后,一个被读取的修改-写入会持续丢失。 为了避免这样,'coerce'被逼输出的深渊 覆盖原始存储项目: chema 输出赢得每个密钥( 默认被填充, 类型被收缩), 而属性 schema 不声明 —— 从一个更古老的chema 版本, 或列另一个作者拥有的 —— 是 保留。阵列被替换为批发(被胁迫阵列获胜),并保留嵌入的未知密钥。 因此,一个例行的读取-修改-写字从不默默地删除一个你没有触及的字段. (使用时)'strict'如果你想要一个计划不匹配被拒绝,或者'off'完全跳过计划通过。 )
选择 'strict' 用于将计划错配视为腐败和拒绝的表格(注:一行坏,然后整个失败) query编号 :scan编号 :getBatch) (中文(简体) ). 选择 'off' 对于热路径,您所信任的数据是通过此策略写入的,或者读取(并保存)行,您在迁移时尚不能强制.
处理错误
分布表所抛出的错误 error.name 您可以与 isBlocksError数字 :
| 常数 | error.name | 掷出时 |
|---|---|---|
DistributedTableErrors.ConditionalCheckFailed | ConditionalCheckFailedException | 一个 ifNotExists 编号 : ifExists 编号 : ifFieldEquals 条件失败 。 序列化为 HTTP 409 (冲突); 仅用于 ifFieldEquals 乐观冲突(不是) ifNotExists 编号 : ifExists) (中文(简体) ). |
DistributedTableErrors.ValidationFailed | ValidationFailedException | 已配置的项目失败 schema 验证时间 put() 编号 : putBatch()。 。 。 . |
DistributedTableErrors.InvalidQuery | InvalidQueryException | 查询/条件形状错误: 缺失 where,未给出分区键为 { equals },未知索引,多排序条件,或空 ifFieldEquals呼叫器错误. |
DistributedTableErrors.ItemTooLarge | ItemTooLargeException | A 类 put编号 :putBatch 超过 DynamoDB 400 KB 的每个项目大小限制. |
DistributedTableErrors.BatchIncomplete | BatchIncompleteException | 批量操作在重试预算( 持续节奏) 后未处理条目 。 AWS只运行时间 . |
import { isBlocksError } from '@aws-blocks/core';
import { DistributedTableErrors } from '@aws-blocks/bb-distributed-table';
try {
await table.put(item, { ifNotExists: true });
} catch (e: unknown) {
if (isBlocksError(e, DistributedTableErrors.ConditionalCheckFailed)) {
// item already exists
}
if (isBlocksError(e, DistributedTableErrors.ItemTooLarge)) {
// item > 400 KB — split it or store a reference instead
}
throw e;
}
实例
基础 CROUD
这些例子使用Zod,但
schema接受任何标准SchemaV1执行(Zod, Valibot, ArkType). 安装您选择的库, 例如 .npm install zod。 。 。 .
import { z } from 'zod';
const orderSchema = z.object({
userId: z.string(),
orderId: z.string(),
total: z.number(),
status: z.string(),
createdAt: z.number(),
});
const orders = new DistributedTable(scope, 'orders', {
schema: orderSchema,
key: { partitionKey: 'userId', sortKey: 'orderId' },
});
export const api = new ApiNamespace(scope, 'api', (context) => ({
async getOrder(userId: string, orderId: string) {
return await orders.get({ userId, orderId });
},
async createOrder(order: z.infer<typeof orderSchema>) {
await orders.put(order, { ifNotExists: true });
},
async deleteOrder(userId: string, orderId: string) {
await orders.delete({ userId, orderId });
},
}));
以按键排序条件查询
const orders = new DistributedTable(scope, 'orders', {
schema: orderSchema,
key: { partitionKey: 'userId', sortKey: 'orderId' },
indexes: {
byDate: { partitionKey: 'userId', sortKey: 'createdAt' },
},
});
// Ctrl+Space on the where object shows: userId, createdAt
// userId requires { equals }, createdAt supports greaterThan, between, etc.
const results = [];
for await (const order of orders.query({
index: 'byDate',
where: {
userId: { equals: 'alice' },
createdAt: { greaterThan: Date.now() - 86400000 },
},
})) {
results.push(order);
}
TTL( 自动过期项目)
const sessions = new DistributedTable(scope, 'sessions', {
schema: sessionSchema,
key: { partitionKey: 'sessionId' },
ttl: 'expiresAt',
});
// DynamoDB automatically deletes items after the TTL timestamp
await sessions.put({
sessionId: 'abc123',
userId: 'alice',
expiresAt: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
});
批量操作
// Write many items
await orders.putBatch([order1, order2, order3]);
// Read many items
const items = await orders.getBatch([
{ userId: 'alice', orderId: '001' },
{ userId: 'bob', orderId: '002' },
]);
// Delete many items
await orders.deleteBatch([
{ userId: 'alice', orderId: '001' },
{ userId: 'bob', orderId: '002' },
]);
环绕已存在的表格
const legacy = new DistributedTable(scope, 'legacy', {
schema: orderSchema,
key: { partitionKey: 'userId', sortKey: 'orderId' },
table: DistributedTable.fromExisting('my-existing-table'),
});
最佳做法
- 为偶数数据分布设计分区键(例如,
userId, (中文(简体) ),tenantId(中文(简体) ) - 对范围查询使用排序键(例如:时间戳、字母顺序)
- 定义已知访问模式的 GSIs 前置 —— 以后添加它们需要回填
- 使用
{ ifNotExists: true }用于生成偶数 - 使用
{ ifFieldEquals }当多个作者有可能时, 乐观的锁定 - 偏爱
query()结束scan()——扫描每件物品,价格昂贵
缩放成本( AWS)
- 计费: PAY PER REQENT - 没有提供管理能力
- 延迟 : 读取和写出数位数
- 通过量 : 自动缩放, 表格大小无上限
- 项目大小限制 : 每件400KB
- GSI 限制 : 每表最多20个全球二级指数
- 费用: ~每百万人有1.25美元,~每百万人有0.25美元
- 重复性 : 99.99999999999%(119%),跨越3个AZ
重复和安全默认( S)
疲惫的姿势来自 堆栈宽度 BlocksDefaults 交给你 BlocksStack.create 编号 : BlocksBackend.create (单位:千美元)BlocksPresets.production 或者说 BlocksPresets.sandbox 从 @aws-blocks/core/cdk)——每块建筑块都读取同样的取号,所以一张表格的去除政策,去除保护,以及连续备份姿态都跟随了应用程序所选择的预设. 没有每个街区 sandboxMode 猜测.
下 BlocksPresets.production,每个表格中,本块规定船舶:
- 即时恢复 启用( e)
defaults.pointInTimeRecovery)——在过去35天恢复到任何一秒. - 保留 + 删除保护 (单位:千美元)
defaults.removalPolicy = RETAIN, (中文(简体) ),defaults.deletionProtection = true)——既无倒叠倒地,也无流浪cdk destroy/收音机删除可以擦去桌子,直到明确放松. (相当于protection: 'locked'. ) (中文(简体) ) - SSE - KMS (英语) 由AWS管理
aws/dynamodb密钥 – 可通过 CloudTrail 进行验证的加密- rest 可不按每键收费.
下 BlocksPresets.sandbox 换个方式,PITR关闭, RemovalPolicy.DESTROY,删除保护(相当于 protection: 'disposable')——所以丢弃的堆栈保持廉价并 sandbox:destroy 是一个命令撕毁。 SSE-KMS 都保留在其中(加密不属于 BlocksDefaults 每块选项默认为 aws-managed) (中文(简体) ).
PITR不是免费的. 连续备份存储的点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点-点 它是默认的,因为无法恢复的数据损失通常是更糟糕的结果——但对于可再生的数据(caches,衍生表格)集
pointInTimeRecovery: false。 。 。 .
protection: 'locked'编号 :'retained'孤体,堆栈删除的表格. 因为驱逐政策是RETAIN,删除堆栈后留下表格(通过设计——您的数据存活)。 但表格名称是从区块的ID中衍生出来的 所以 后调用同一应用程序失败Table already exists直到你们删除已忽略的表格aws dynamodb delete-table中,如果'locked')或导入到新堆栈中。 这是保留在删除中固有的;使用protection: 'disposable'对于您期望可以自由重现的表格.
索引跟随配置,而表格则在堆栈中. 删除一个索引(或整个)
indexes选项),从您的配置中并重新部署从表格中跳出。 当表格本身离开堆栈时(您删除堆栈,或去掉DistributedTable它的索引是孤寡的'locked'编号 :'retained'表格保存全部,所以导入到新堆栈不需要重建索引。 要告诉两者分开,GSI管理器读取了堆栈的模板. 如果在更新(或回滚更新)的清理阶段无法读取该索引,则该索引保持不变,并报告DELETE_FAILED用于表格的 GSI 自定义资源的事件(逻辑 ID 包含gsiresource来命名表格和索引。 部署本身仍然成功,所以检查该事件的部署输出(或堆栈事件). 要删除您已经没有的索引, 运行aws dynamodb update-table --table-name <table> --global-secondary-index-updates '[{"Delete":{"IndexName":"<index>"}}]'; DynamoDB每次呼叫需要一个索引变化,所以等待每个删除在下个之前完成.
每个堆栈默认值每个表都是压倒性的(每块选项总是胜过) defaults:
// Cost-sensitive prod table: keep it protected, skip PITR's backup cost
const cache = new DistributedTable(scope, 'cache', {
schema, key: { partitionKey: 'id' },
pointInTimeRecovery: false,
});
// Long-lived data you still want to be able to delete directly
const staging = new DistributedTable(scope, 'staging', {
schema, key: { partitionKey: 'id' },
protection: 'retained', // survives stack delete, but not deletion-protected
});
// Compliance-strict table: dedicated customer-managed KMS key
const ledger = new DistributedTable(scope, 'ledger', {
schema, key: { partitionKey: 'id' },
encryption: 'customer-managed',
});
// Share one customer-managed key across several tables (one key, one bill)
const key = DistributedTable.fromKmsKey('arn:aws:kms:us-east-1:111122223333:key/abcd-1234');
const orders = new DistributedTable(scope, 'orders', {
schema, key: { partitionKey: 'id' },
encryption: key,
});
const events = new DistributedTable(scope, 'events', {
schema, key: { partitionKey: 'id' },
encryption: key,
});
压倒性总是会战胜环境默认,这样你就可以在沙盒中(或放轻松一分)明确强制一个完全耐用,受保护的桌子. 当你通过自己的桌子
fromExisting(),这些选项都不适用——您拥有该表的配置.
customer-managed每表提供专用CMK ——一款配有十几张客户管理表的应用程序获得一打KMS密钥(~每个月1美元,外加请求费). 要在多个表格中共享一个密钥, 请创建一个密钥并传递DistributedTable.fromKmsKey(keyArn)作为encryption选项。 使用默认'aws-managed'除非表格需要自己的旋转/关键政策控制.
地方发展
模拟数据持续到磁盘 .bb-data/{fullId}/ 跨 Dev 服务器重启 。 擦拭为 rm -rf .bb-data。模拟验证了400 KB 项的尺寸限制、 计划验证以及有条件的检查失败, 符合 AWS 行为。 索引查询是通过内膜过滤执行的——正确性得到保留,但性能特征与DynamoDB不同.