ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Teleport DynamoDB 审计事件存储演进:RFD 24 按天分区全局二级索引(timesearchV2)设计解析

2026/9/21 19:22:04 拓冰建站 浏览量
Teleport DynamoDB 审计事件存储演进:RFD 24 按天分区全局二级索引(timesearchV2)设计解析 网络安全认证鉴权运维后端【免费下载链接】teleportThe easiest, and most secure way to access and protect all of your infrastructure.项目地址https://gitcode.com/gh_mirrors/tel/teleport点击查看免费下载Teleport 将审计事件audit events持久化到 AWS DynamoDB 时会面临 DynamoDB 单个分区 10 GB 容量上限带来的写入停摆风险。本文围绕仓库内已实现的设计文档 rfd/0024-dynamo-event-overflow.mdRFD 24展开剖析在全局二级索引GSI上按事件日期yyyy-mm-dd分区这一方案的前因后果并结合 lib/events/dynamoevents/dynamoevents.go 的源码与测试讲清timesearchV2索引的建表、写入、查询与兼容细节。读完本文你将理解 DynamoDB 分区热点的成因、Teleport 事件表与事件索引的物理结构以及大规模审计日志场景下时间范围检索为何必须按日期切分分区键。背景Teleport 的审计事件如何落到 DynamoDBTeleport 的审计日志auth 服务侧的audit_events_uri支持多种后端其中 DynamoDB 是生产环境常用的高可用选项。官方配置样例见 docs/pages/includes/config-reference/auth-service.yamlteleport: storage: # 集群状态存储使用 DynamoDB独立于事件表 type: dynamodb region: us-east-1 table_name: Example_TELEPORT_DYNAMO_TABLE_NAME # 审计事件写入 DynamoDB 事件表表结构与状态表完全不同 audit_events_uri: - dynamodb://events_table_name - file:///var/lib/teleport/audit/events - stdout://需要注意两点docs/pages/reference/deployment/backends.mdx 中有明确警告事件表与状态表必须分开table_name与dynamodb://中的events_table_name不能指向同一张表二者的 schema 不同混用会报错事件表与状态表使用不同代码路径状态存储在 lib/backend/dynamo/dynamodbbk.go事件存储则在 lib/events/dynamoevents/dynamoevents.go两者各自建表、各自维护索引。RFD 24 讨论的正是事件表audit events 表的物理设计。事件表的主键为分区键Hash KeySessionID若事件不属于任何会话则生成随机 UUID见getSessionID排序键Range KeyEventIndex会话内事件序号。由于分区键是会话 ID写入会天然分散到大量分区因此主表不会触碰 10 GB 单分区上限。问题全局二级索引的单分区陷阱DynamoDB 的存储单元是分区partition单个分区最多容纳10 GB数据。Teleport 在主表之上维护了一个只读的全局二级索引GSI作为按时间检索审计事件的物化视图materialized view。RFD 24 指出旧索引存在两个致命设计缺陷1. 分区键是硬编码字符串default旧 GSI 不以会话 ID 分区而是以namespace 字段硬编码字符串default作为分区键。这意味着索引只有一个分区全部历史审计事件都堆积在该分区内在生产部署中该分区逼近 10 GB 上限一旦达到 10 GB索引会停止从主表同步数据新的审计事件将无法被读取——即审计查询功能整体失效。2. 单分区数据过多导致 B-Tree 过深即使不触及 10 GB 硬限制把海量事件塞进一个分区也会让 DynamoDB 内部用于排序的B-Tree 索引层级不断加深从而拖慢检索性能。RFD 24 明确将搜索时间受影响列为需要解决的第二个隐患。问题的本质旧 GSI 的失败在于分区键基数太低namespace 恒为default等价于把所有事件压进同一个分区。RFD 24 的解决思路非常直接——把分区键换成每天变化的值让天成为天然的分区单位。方案总览RFD 24 的三步迁移RFD 24 的完整迁移路径如下新增日期字段在每条审计事件上新增CreatedAtDate字段取值为事件 UTC 时间的yyyy-mm-dd字符串新建按日期分区的 GSI创建与旧索引等价的timesearchV2全局二级索引但分区键改为CreatedAtDate排序键仍为CreatedAt历史数据回填 删除旧索引由 Auth 服务在 DynamoDB 后端初始化时触发一次性的后台任务为存量事件补齐日期字段回填完成后删除旧索引迁移结束。其中回填阶段有一个重要的可用性窗口新索引建立后尚未补上CreatedAtDate的旧事件在回填完成前不可见、不可检索。RFD 24 预期后台任务执行较快事件会很快重新出现因此该窗口对用户影响有限。从当前源码看RFD 24 的主体已经落地timesearchV2是仓库中唯一的按时间检索索引旧索引在代码中已不再出现。实现细节从源码看 timesearchV2下面结合 lib/events/dynamoevents/dynamoevents.go 逐项印证 RFD 24 的设计。1. 新增日期字段与表结构事件表的属性定义tableSchema在代码注释中明确标注了 RFD 24 的痕迹// lib/events/dynamoevents/dynamoevents.go var tableSchema []dynamodbtypes.AttributeDefinition{ // Existing attributes pre RFD 24. {AttributeName: aws.String(keySessionID), AttributeType: dynamodbtypes.ScalarAttributeTypeS}, {AttributeName: aws.String(keyEventIndex), AttributeType: dynamodbtypes.ScalarAttributeTypeN}, {AttributeName: aws.String(keyCreatedAt), AttributeType: dynamodbtypes.ScalarAttributeTypeN}, // New attribute in RFD 24. {AttributeName: aws.String(keyDate), AttributeType: dynamodbtypes.ScalarAttributeTypeS}, }对应常量// keyDate identifies the date the event was created at in UTC. // The date takes the format yyyy-mm-dd as a string. // Specified in RFD 24. keyDate CreatedAtDate日期格式使用 ISO 8601 的2006-01-02即yyyy-mm-dd该格式字符串在代码中以iso8601DateFormat定义。选择字符串日期作为分区键的原因在于它可直接从查询时间范围生成无需任何换算或二次查询。2. 建表GSI 的分区键从 namespace 换成日期createTable创建的 GSI 定义与 RFD 24 提案完全一致GlobalSecondaryIndexes: []dynamodbtypes.GlobalSecondaryIndex{ { IndexName: aws.String(indexTimeSearchV2), // timesearchV2 KeySchema: []dynamodbtypes.KeySchemaElement{ { // Partition by date instead of namespace. AttributeName: aws.String(keyDate), // CreatedAtDateHash Key KeyType: dynamodbtypes.KeyTypeHash, }, { AttributeName: aws.String(keyCreatedAt), // CreatedAtRange Key KeyType: dynamodbtypes.KeyTypeRange, }, }, Projection: dynamodbtypes.Projection{ProjectionType: dynamodbtypes.ProjectionTypeAll}, ProvisionedThroughput: provisionedThroughput, }, },代码注释 Partition by date instead of namespace 直接点明了 RFD 24 的核心改动而常量声明处也写着// indexTimeSearchV2 is the new secondary global index proposed in RFD 24. // Allows searching events by time. indexTimeSearchV2 timesearchV23. 写入路径每条事件都带上日期事件写入时createPutItem日期由事件自身的时间戳格式化而来e : event{ EventKey: EventKey{ SessionID: sessionID, EventIndex: in.GetIndex(), CreatedAt: in.GetTime().Unix(), CreatedAtDate: in.GetTime().Format(iso8601DateFormat), }, ... }也就是说新写入的事件天然携带分区键主表数据会实时同步到timesearchV2的对应日期分区中。这正是GSI 是主表物化视图的体现。4. 查询路径把时间范围翻译成分区键集合RFD 24 指出在该新索引上搜索非常 trivial因为分区键就是从查询起止时间生成的一组日期。源码把这一思想拆成两步第一步生成日期列表。daysBetween按天生成yyyy-mm-dd字符串func daysBetween(start, end time.Time) []string { var days []string oneDay : time.Hour * time.Duration(24) startDay : daysSinceEpoch(start) // start.Unix() / (60*60*24) endDay : daysSinceEpoch(end) for startDay endDay { days append(days, start.Format(iso8601DateFormat)) startDay start start.Add(oneDay) } return days }第二步按日期逐个分区查询。searchEventsRaw生成日期列表后若查询按时间倒序EventOrderDescending则反转列表然后交由eventsFetcher.QueryByDateIndex逐天发起 DynamoDB Queryquery : CreatedAtDate :date AND CreatedAt BETWEEN :start and :end // ... for _, date : range l.dates { l.checkpoint.Date date input : dynamodb.QueryInput{ KeyConditionExpression: aws.String(query), IndexName: aws.String(indexTimeSearchV2), // 走 timesearchV2 ... ScanIndexForward: aws.Bool(l.forward), } ... }每个日期分区内的CreatedAt排序键再配合BETWEEN :start and :end做秒级裁剪即可精确命中目标时间窗内的事件。由于查询是分区键等值 排序键范围这是 DynamoDB 中成本最低、延迟最稳定的访问模式。值得注意的是SearchSessionEvents按会话查事件仍然走主表的SessionID索引IndexName: nil与按时间检索各司其职。5. 分页与断点续传checkpoint跨天查询天然需要分页。实现用checkpointKey记录当前查询到哪一天 该天内的 DynamoDB 游标type checkpointKey struct { Date string json:date,omitempty // 查询对应的日期 Iterator string json:iterator,omitempty // 恢复部分查询的 DynamoDB 游标 }每次 Query 返回后若LastEvaluatedKey非空会把当前事件的EventKey含CreatedAtDate序列化为游标存入checkpoint.Iterator下一轮调用通过StartKey传入 checkpointsearchEventsRaw会先裁剪日期列表到 checkpoint 所在日期dates[0] ! checkpoint.Date就弹出队头再从该日期继续查询若 checkpoint 日期落在新的查询窗口之外代码会做护栏检查窗口前移则重置游标窗口后移则直接空返回对应测试TestCheckpointOutsideOfWindow。旧版本兼容由于 checkpoint 会作为长期运行的导出任务的状态落盘仓库专门保留了legacyCheckpointKey旧格式的 raw DynamoDB 属性值游标在解析StartKey失败时回退到旧格式解码并标注DELETE IN: 19.0.0见 legacy.go 与getCheckpointFromLegacyStartKey。这说明按日期分区改造并未破坏旧客户端的中途续传。6. 容量管理自动扩缩容同样覆盖索引当auto_scaling: true时configureTable会同时为主表和timesearchV2索引注册可扩缩目标与目标跟踪策略dynamoevents.go 第 480-570 行resourceID: fmt.Sprintf(table/%s/index/%s, l.Tablename, indexTimeSearchV2), // read/write 目标跟踪策略DynamoDBIndexReadCapacityUtilization / WriteCapacityUtilization这意味着按日期分区后热门的今天分区即使查询量大其读容量也能被自动扩缩容覆盖不会因为索引侧容量不足而限流。配置与运维实践事件表的完整配置参考以下配置片段综合自 docs/pages/reference/deployment/backends.mdx 与 auth-service.yamlteleport: storage: type: dynamodb region: us-east-1 table_name: Example_TELEPORT_DYNAMO_TABLE_NAME # 状态表 audit_events_uri: - dynamodb://events_table_name # 事件表表名必须与状态表不同 audit_sessions_uri: s3://Example_TELEPORT_S3_BUCKET/records # 审计事件 TTL默认 1 年设为 0 则禁用 TTL。 # 注意只有 DynamoDB 事件后端尊重该字段其他后端通过 URI 查询参数配置。 retention_period: 365d billing_mode: pay_per_request # 或 provisioned continuous_backups: truedynamodb://URI 支持的查询参数dynamodb://events_table_name?regionus-east-1endpointdynamo.example.comuse_fips_endpointtrueregion指定 AWS 区域endpoint指向自建/兼容 DynamoDB 的端点非 AWS 场景use_fips_endpoint启用 FIPS 端点优先级为 URI 参数 --fips启动标志 AWS_USE_FIPS_ENDPOINT环境变量。TTL 的实现同样与 RFD 24 的分区设计兼容事件写入时按retention_period设置Expires属性setExpiryDynamoDB 会异步删除过期项每个日期分区都会按 TTL 自然收缩长期运行也不会让历史分区无限膨胀。高可用部署限制事件后端依赖 DynamoDB Streams 实现事件监听。官方文档明确警告AWS 会对同时读取同一 stream 分片的进程限流因此部署读取 DynamoDB 后端的 Auth 服务实例不能超过两个见 backends.mdx 中的 danger 提示。这也是在规划 HA 集群时与分区设计同等重要的约束。测试验证分区与分页逻辑的可信度仓库在 dynamoevents_test.go 中为按日期分区方案提供了直接测试TestIndexExists建表后断言timesearchV2索引存在且处于 Active/Updating 状态验证新 GSI 是建表流程的固定组成部分TestDateRangeGenerator验证daysBetween能正确处理跨月日期区间如2021-08-30到2021-09-01这是分区键集合正确性的基石TestCheckpointOutsideOfWindow验证 checkpoint 日期落在查询窗口外时不会 panic能安全返回空结果TestSizeBreak用 200 KB 大事件模拟分页边界验证processQueryOutput在响应超过events.MaxEventBytesInResponse时能保存 checkpoint、下一轮继续拉取确保跨天查询不会丢事件TestSearchSessionEventsBySessionID等套件则覆盖主键索引与会话检索路径保证按日期索引改造没有破坏既有查询。总结RFD 24 是 Teleport 在 DynamoDB 审计日志场景下的一次关键存储架构修正其核心可归纳为三点问题本质是分区键基数过低旧 GSI 用硬编码default分区单分区数据逼近 10 GB 后索引停止同步、审计读取失效解法是按天切分分区新增CreatedAtDateyyyy-mm-dd字段并作为timesearchV2GSI 的分区键事件写入即带上分区键查询时用daysBetween把时间范围展开成分区键集合逐个分区 Query兼顾容量扩展与检索性能迁移是平滑的通过一次性的历史回填补齐旧事件并以 checkpoint 兼容逻辑DELETE IN: 19.0.0保证长期导出任务在升级过程中不中断。对于正在规划大规模审计日志存储的团队这份设计文档与其源码落地rfd/0024-dynamo-event-overflow.md、dynamoevents.go、dynamoevents_test.go提供了一个可复用的范式当 DynamoDB 索引的热点无法消除时把分区键换成业务上有界、可推导的时间维度是性价比最高的一步。赞分享网络安全认证鉴权运维后端【免费下载链接】teleportThe easiest, and most secure way to access and protect all of your infrastructure.项目地址https://gitcode.com/gh_mirrors/tel/teleport点击查看免费下载相关推荐Mastra DynamoDB 存储适配器深度解析单表设计、TTL 过期、域存储架构与分页 API 演进Mastra DynamoDB 存储适配器深度解析单表设计、TTL 过期、域存储架构与分页 API 演进 本文围绕 Mastra 官方 mastra/dyn人工智能Agent 框架AI AgentRAG后端Event Store事件存储设计实战PostgreSQL、EventStoreDB 与 DynamoDB 四套实现模板全解析Event Store事件存储设计实战PostgreSQL、EventStoreDB 与 DynamoDB 四套实现模板全解析 本文基于 agents 开AI 插件AI 技能开发工具Temporal Python SDK工作流事件存储分区键设计Temporal Python SDK工作流事件存储分区键设计 在分布式系统中工作流事件的高效存储与检索是保证系统稳定性和性能的关键。Temporal作为一上一篇Mobile-Agent架构深度解析跨平台智能调度引擎的技术突破与实践指南下一篇Cryptozombies项目解析ZombieFactory智能合约深度剖析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考