WuKongIM 频道消息自动保留(Channel Message Retention):集群一致的过期前缀清理方案 即时通讯后端【免费下载链接】WuKongIMMore than just IM 不只是即时通讯(IM)项目地址https://gitcode.com/gh_mirrors/wu/WuKongIM点击查看免费下载本文以 WuKongIM 仓库中的频道消息保留实现方案docs/superpowers/plans/2026-04-30-channel-message-retention.md及其配套设计文档docs/superpowers/specs/2026-04-30-channel-message-retention-design.md为骨架结合源码与测试验证完整讲解其集群一致的自动清理能力从权威边界提交、本地物理删除到读取路径裁剪。读完本文你将掌握 WuKongIM 中“7 天自动清理频道消息”这类能力的全部水位定义、两阶段安全边界计算、committed replay 持久化门禁、retention reset 追赶协议以及如何在WK_CHANNEL_MESSAGE_RETENTION_*配置下正确启用与验证它。为什么频道消息清理必须是集群一致的而不是本地 TTLIM 系统中频道消息会无限增长最常见的诉求是只保留最近 N 天的消息比如 7 天。WuKongIM 对此的定位非常明确这不是每个节点各自执行本地 TTL 删除而是基于频道 ISR 复制语义的日志前缀 retention。当前 channel leader 计算一个单调递增的逻辑清理边界先把边界提交为权威集群状态再由各节点按该边界异步执行本地物理删除。设计文档在方案比较中明确否决了方案 A本地定时 DeleteRange——每个节点自己扫描本地 Pebble 消息表并删除过期消息。它的缺点非常典型会绕过 channel ISR 复制语义容易造成 leader/follower 边界不一致、message sequence 中间挖洞、二级索引悬挂、committed replay 丢失消息来源以及 leader 切换后旧消息重新可见。这正是本方案要避免的全部问题。关键前提是本项目没有独立的单机语义单节点部署也按单节点集群处理。因此消息 retention 必须遵守 channel leader、ISR、slot metadata、committed replay 和本地存储恢复的全部约束不能绕过集群语义直接删除 Pebble 数据。架构总览一句话Retention 是 leader 驱动、集群权威的——当前 channel leader 计算一个安全的RetentionThroughSeq通过 slot metadataRaft/FSM提交每个副本应用这个单调的逻辑读 fence物理删除是本地、异步的由独立的 store retention state 跟踪进度checkpointLogStartOffset保持仅由 snapshot 决定retention 永远不推进它。五类水位定义逻辑边界、物理进度与读起点整个方案围绕一组严格区分的水位展开。理解它们是理解全部后续语义的基础水位所属层含义RetentionThroughSeq权威 slot metadata集群确认不可再对外读取的最高消息序号单调递增唯一权威 retention 边界MinAvailableSeq运行时派生值第一个可读消息序号等于max(RetentionThroughSeq 1, LogStartOffset 1, 1)其中LogStartOffset只来自 snapshotLocalRetentionThroughSeq本地 channel store / replica本节点已经持久采用的 retention floor可在本地缺少旧日志时前进用于重启后保持 LEO/read floor 不回退PhysicalRetentionThroughSeq本地 channel store本节点已物理删除到的最高消息序号允许滞后于权威边界也允许低于LocalRetentionThroughSeqLogStartOffsetreplica checkpoint/snapshotsnapshot-backed 复制日志下界不是 retention 边界首版 retention 不推进它HW/CommittedSeqreplica runtime当前已提交消息水位CheckpointHWreplica durable checkpoint已持久化的安全恢复水位LEOchannel store / replica当前本地日志末端消息序号前缀删除不能让它回退committedReplayCursorchannel store system key已完成 delivery/conversation 补偿重放的消息序号retention 依赖它时必须确保持久化核心语义用公式表达就是RetentionThroughSeq N MinAvailableSeq max(N 1, snapshot lower bound, 1)含义 N的消息已经被集群确认不可再读服务端不再对外返回 N的消息即使本地物理删除尚未完成未来 leader 必须继承该边界不能回退本地物理删除只负责释放空间和移除索引不能决定逻辑可见性。在代码中这一派生逻辑由EffectiveMinAvailableSeq之类的辅助函数实现见 pkg/channel/types.go 中Meta、ReplicaState等类型的 retention 字段定义。RetentionView则在 pkg/channel/retention.go 中完整暴露了 leader/epoch/lease、HW、CheckpointHW、LEO、RetentionThroughSeq、MinAvailableSeq、LocalRetentionThroughSeq、PhysicalRetentionThroughSeq与MinISRMatchOffset供后台 worker 计算安全边界。另一个必须牢记的细节不要更新 checkpointLogStartOffset来表达 retention。现有 recovery 逻辑把非零LogStartOffset视为 snapshot-backed 下界并期望存在兼容的 snapshot payload首版 retention 不创建 snapshot payload因此不能复用该字段。权威提交slot metadata 中的 retention 字段与窄写命令持久化字段与列RetentionThroughSeq写入pkg/db/meta.ChannelRuntimeMeta即方案中的 slot meta由 slot Raft 持久化和复制。新增两个字段// RetentionThroughSeq is the highest channel message sequence that the // authoritative cluster metadata declares unavailable for future reads. RetentionThroughSeq uint64 // RetentionUpdatedAtMS is the wall-clock time in milliseconds when the // authoritative retention boundary was last advanced. RetentionUpdatedAtMS int64编码层面retention_through_seq与retention_updated_at_ms两个列必须支持旧记录缺省解码为 0zero defaults保证老版本编码的数据升级后仍可读。这一点在 pkg/slot/fsm/command.go 的解码逻辑中有直接体现tagRuntimeMetaRetentionThroughSeq等 TLV 标签被解析进 runtime meta 命令同时命令 inspectionpkg/slot/fsm/command_inspection.go会输出retention_through_seq、retention_updated_at_ms、expected_leader_epoch等字段便于排查。单调不回退的合并规则resolveMonotonicChannelRuntimeMeta见 pkg/db/meta/channel_runtime_meta_test.go 的TestChannelRuntimeMetaAdvanceRetention等用例必须保证RetentionThroughSeq只能前进不能回退对于 stale channel epoch、stale leader epoch 或 leader 不匹配的写入不能覆盖当前权威状态对于更高 channel epoch 的 metadata也必须继承现有RetentionThroughSeq不能因拓扑变化把读边界重置为 0。func preserveRetentionBoundary(existing ChannelRuntimeMeta, candidate *ChannelRuntimeMeta) { if candidate.RetentionThroughSeq existing.RetentionThroughSeq { candidate.RetentionThroughSeq existing.RetentionThroughSeq candidate.RetentionUpdatedAtMS existing.RetentionUpdatedAtMS return } if candidate.RetentionThroughSeq existing.RetentionThroughSeq candidate.RetentionUpdatedAtMS existing.RetentionUpdatedAtMS { candidate.RetentionUpdatedAtMS existing.RetentionUpdatedAtMS } }窄写命令AdvanceChannelRetentionThroughSeq为了不重写 leader lease / topology方案新增一个只推进 retention 字段的窄命令AdvanceChannelRetentionThroughSeq。它必须携带完整 fence 信息任一不匹配即拒绝写入// ChannelRetentionAdvance describes a fenced request to advance only the // authoritative channel retention boundary. type ChannelRetentionAdvance struct { ChannelID string ChannelType int64 ExpectedChannelEpoch uint64 ExpectedLeaderEpoch uint64 ExpectedLeader uint64 ExpectedLeaseUntilMS int64 RetentionThroughSeq uint64 RetentionUpdatedAtMS int64 }实现语义见 pkg/slot/fsm/command.go 中EncodeAdvanceChannelRetentionThroughSeqCommand与其 apply 路径wb.AdvanceChannelRetentionThroughSeq加载现有 metadata缺失返回ErrNotFoundChannelEpoch、LeaderEpoch、Leader、LeaseUntilMS任一不匹配返回 stale/conflict 错误测试中对应dberrors.ErrConflictreq.RetentionThroughSeq existing.RetentionThroughSeq时 no-op成功时只更新RetentionThroughSeq与RetentionUpdatedAtMS写入正常 family 值。分布式写入必须走 slot Raft proposepkg/slot/proxy/store.go的AdvanceChannelRetentionThroughSeq方法根据ChannelID计算 hash slot编码窄命令后调用proposeWithHashSlot。读 RPC 因为 JSON 天然携带新结构体字段无需改动线上协议。命令类型在 FSM 中的注册可参考cmdTypeAdvanceChannelRetentionpkg/slot/fsm/command.go命令 inspection 的 catalog 测试pkg/slot/fsm/command_inspection_catalog_test.go覆盖了advance_channel_retention_through_seq的编解码与检视。两阶段安全边界计算不越过未重放、未追上的消息候选边界与最终边界Leader 先计算不含 replay cursor 的候选边界再把当前 leader replay cursor 做 durable confirm最后得到最终边界candidateThroughSeq min( expiredThroughSeq, leaderCheckpointHW, leaderCommittedHW, minISRMatchOffset, ) durableCommittedReplayCursor durableConfirmLeaderReplayCursor() safeThroughSeq min( candidateThroughSeq, durableCommittedReplayCursor, )各约束含义expiredThroughSeq只清理连续过期前缀leaderCheckpointHW不能在 durable checkpoint 尚未覆盖前隐藏或删除消息leaderCommittedHW不能处理未提交消息durableCommittedReplayCursor不能处理尚未完成并持久确认 delivery/conversation 重放的消息minISRMatchOffset只清理当前 ISR 都已经追上的前缀避免 follower 落后于新的读边界。如果candidateThroughSeq current RetentionThroughSeq本轮无需 durable confirm cursor也不提交新边界如果 durable confirm 失败或最终safeThroughSeq current RetentionThroughSeq本轮不提交新边界。这个两阶段顺序避免了用未持久化的 leader-local cursor 计算 retention也允许 cursor 暂时落后时只把 boundary 推进到已重放并确认的部分——不能跳过未重放部分。minISRMatchOffset 必须来自当前 leader epoch 的观测minISRMatchOffset必须来自当前 leader epoch 内已观测到的 ISR fetch/cursor progress不能直接复用 quorum HW也不能把缺失 follower progress 默认当作 HW。未知或未观测到的 ISR 成员只能证明到当前RetentionThroughSeq从而阻止 retention 在该成员真正追上前继续前进。源码中minISRMatchOffset的实现pkg/channel/reactor/retention.go正是这样本地 leader 进度取state.LEOfollower 只从state.Progress[node].Match观测未知成员按state.RetentionThroughSeq起算。物理删除的安全条件在 replica 侧物理 trim 还必须通过一组本地门禁见retentionTrimDecisionpkg/channel/reactor/retention.gothroughSeq CheckpointHW否则checkpoint_lagthroughSeq HW否则hw_lagthroughSeq LEO否则leo_lag若本地LEO boundary执行 retention reset/catch-up floor不执行物理删除throughSeq PhysicalRetentionThroughSeq否则 no-opleader 角色还需throughSeq minISRMatchOffset否则min_isr_lag。这些 blocked reason 常量定义在 pkg/channel/retention.goRetentionBlockedHWLag、RetentionBlockedCheckpointLag、RetentionBlockedLEOLag、RetentionBlockedMinISRLag会被 worker 记录并在观测中上报。Committed Replay Cursor 持久化retention 的安全门禁当前 committed replay cursor 是NoSync的 duplicate-suppression hint热路径写入。Retention 一旦依赖它就必须把它升级为 durable safety gate。首版语义是committed replay 只在 channel leader 上执行followers 不作为提交副作用的独立权威。因此提交新的RetentionThroughSeq前worker 必须 durable confirm 当前 leader 的 replay cursor 已 safeThroughSeq当前 leader cursor 必须在提交 slot metadata 之前用 Sync 做 durable confirm本地/follower 采用 boundary 时的 cursor fast-forward 发生在 metadata commit 之后不能替代 leader 的 pre-commit confirm如果 leader cursor 缺失、无法 durable confirm或确认后仍 current RetentionThroughSeq本轮不得推进 retention boundary应用权威 boundary 的每个节点必须 durable advance 本地 cursor 至少到RetentionThroughSeq再允许 committed replay 从该 channel 继续运行重启后 committed replay 从max(cursor 1, MinAvailableSeq)开始若 cursor 丢失但权威 boundary 已推进必须先 durable fast-forward cursor 到RetentionThroughSeq不能尝试读取 boundary 以下的消息。对应的 store API 实现于 pkg/db/message/compat.go// ConfirmCommittedDispatchCursorDurable syncs an existing cursor when it is at least minSeq. func (s *ChannelStore) ConfirmCommittedDispatchCursorDurable(name string, minSeq uint64) (uint64, error) // AdvanceCommittedDispatchCursorDurable durably moves a replay cursor forward. func (s *ChannelStore) AdvanceCommittedDispatchCursorDurable(name string, seq uint64) errorConfirmCommittedDispatchCursorDurable加载 cursor缺失报错、低于 minSeq 报错以pebble.Sync重写观测到的 cursor 值并返回。worker 可传currentRetention 1让落后于候选边界的 cursor 仍能 cap 最终安全边界AdvanceCommittedDispatchCursorDurable单调前进pebble.Sync拒绝回退已有 cursor。行为契约被 pkg/db/message/compat_operational_contract_test.go 覆盖ConfirmCommittedDispatchCursorDurable(delivery, 2)返回 2再以 minSeq3 confirm 会报ErrCorruptStateAdvanceCommittedDispatchCursorDurable(delivery, 3)成功随后回退到 2 同样报错。本地物理删除与 LEO 保底原子前缀 trim本地物理删除必须在一个 Pebble batch 中完成trimPrefixThroughLimit见 pkg/db/message/retention.go删除 message primary family删除 message payload family删除message_idindex删除client_msg_noindex删除(from_uid, client_msg_no)idempotency index更新本地 retention boundsLocalRetentionThroughSeq、PhysicalRetentionThroughSeq与RetainedMaxSeq/LEO floor。关键约束每次 durable adoption 或物理 trim 的 batch 都以pebble.Sync提交TrimMessagesThrough只按实际扫描到的行推进PhysicalRetentionThroughSeq——不能因为RetainedMaxSeq或 LEO floor 很高就假定行存在重复应用同一 boundary物理删除已完成是 no-op逻辑边界已应用但物理删除滞后时允许重试物理删除删除完成后发布 LEO 为max(leoBefore, throughSeq)保证不回退。方案还考虑了 MQTT 特殊保护trimPrefixThroughLimit中会读取 MQTT activation evidence 与 source 保护状态readMQTTActivationEvidence、channelEntry.loadMQTTSourceState把throughSeq进一步限制到BaseOffset/CopiedThrough之内避免误删尚未完成来源复制的消息。LEO 保底全量前缀删除后 LEO 不回退为避免前缀删除所有 row 后LEO恢复为 0或旧副本本地LEO落后于权威 boundary 后无法从boundary 1继续追赶channel store 需要新增本地 retention/log-bounds system key并在LEO()恢复时取max(messageTable.maxSeq(), retainedMaxSeq)。本地 retention state 由retentionStateVersion版本化编码pkg/db/message/retention.go 的encodeRetentionState/decodeRetentionState固定 25 字节版本 三个 uint64type RetentionState struct { LocalRetentionThroughSeq uint64 PhysicalRetentionThroughSeq uint64 RetainedMaxSeq uint64 }校验规则validateRetentionStateLocalRetentionThroughSeq 0时RetainedMaxSeq必须为 0PhysicalRetentionThroughSeq LocalRetentionThroughSeqRetainedMaxSeq LocalRetentionThroughSeq。AdoptRetentionBoundarypkg/db/message/compat.go在本地LEO throughSeq时也必须可执行它单调更新LocalRetentionThroughSeq、把RetainedMaxSeq至少抬到throughSeq这样重开后的LEO()至少返回 adopted boundary并 durable fast-forward 命名 replay cursor。ListChannelKeys也因此在只剩 retention/system state 而无 table-state 消息行时仍返回该频道保证 worker 能继续清理已全量 trim 但仍需维护 retention state的频道。Retention Reset追赶协议而非 snapshot install当副本本地LEO RetentionThroughSeq时必须执行 retention reset/catch-up持久设置LocalRetentionThroughSeq和retainedMaxSeq至 boundary将运行时LEOfloor 提升到 boundary读取仍从MinAvailableSeq开始后续复制从 boundary 之后继续。该副本在完成 reset 并追上当前 leader 之前不得加入 ISR也不得被提升为 leader。复制请求或 long-poll 的 proof/fetch offset 如果低于RetainedThroughOffset max(RetentionThroughSeq, LogStartOffset)处理规则如果LogStartOffset RetentionThroughSeq且需要 snapshot payload继续使用现有 snapshot-required 语义如果 retention boundary 是主导原因leader 返回明确的 retention reset 结果RetentionReset携带RetentionThroughSeq、RetainedThroughOffset、MinAvailableSeq三个字段不返回稀疏记录Follower 收到 retention reset 后先从权威 metadata 或响应中的 fence 校验 boundary再执行本地AdoptRetentionBoundary(boundary)Adoption 成功后follower 将本地LEOfloor 至少提升到 boundary下次复制从boundary 1继续追上当前 leader 前保持非 ISR 且不可成为 leaderClient/manager 消息读取不返回retention reset 错误而是直接按MinAvailableSeqclamp 或过滤。为了让远程 follower 能正确执行这一协议retention reset 字段必须贯穿整个 transport 链路FetchResponseEnvelope、lane poll 的LongPollItem与LaneResponseItem都要携带 reset 字段pkg/channel/transport 的 codec/longpoll_codec确保 RPC 与长轮询两条路径都完整传递RetentionThroughSeq、RetainedThroughOffset、MinAvailableSeq。带RetentionReset的响应必须立即返回不能作为空/HW-only 响应被 park。读取路径裁剪所有读接口都以 MinAvailableSeq 为底线权威RetentionThroughSeq一旦提交并应用到 runtime就立即成为读路径 fence。即使本地物理删除失败或滞后所有读路径都必须按MinAvailableSeq裁剪。方案要求覆盖的读取面FetchFromSeq 0或低于MinAvailableSeq时从MinAvailableSeq开始pkg/channel/handler/fetch.go旧版 messagesyncup/down/latest 都不得返回MinAvailableSeq以下的消息按 sequence 读取LoadMsg、next/prev range helper 必须 clamp 或过滤到MinAvailableSeqLoadMsgWithFloor、LoadNextRangeMsgsWithFloor、LoadPrevRangeMsgsWithFloor等 floor-aware 变体按MessageID/ClientMsgNo查询即使命中本地未删除索引若 row 的MessageSeq MinAvailableSeq必须表现为未找到或被过滤不能暴露 stale index 命中pkg/channel/handler/message_query.goManager/node 消息查询与远程读取internal/app/manager_messages.go、internal/access/node/channel_messages_rpc.go复用同一裁剪逻辑minAvailableSeq : channel.EffectiveMinAvailableSeq(meta.RetentionThroughSeq, 0)Committed replay读取从max(cursor 1, MinAvailableSeq)开始不能读取 boundary 以下的消息。FetchResult与ChannelRuntimeStatus可扩展返回MinAvailableSeq方便客户端修正本地游标或展示历史已清理。配置与生命周期配置项新增应用配置默认关闭TTL0WK_CHANNEL_MESSAGE_RETENTION_TTL0 WK_CHANNEL_MESSAGE_RETENTION_SCAN_INTERVAL1h WK_CHANNEL_MESSAGE_RETENTION_CHANNEL_BATCH_SIZE128 WK_CHANNEL_MESSAGE_RETENTION_MAX_TRIM_MESSAGES10000字段语义WK_CHANNEL_MESSAGE_RETENTION_TTL消息保留时长0表示关闭自动清理显式负值非法WK_CHANNEL_MESSAGE_RETENTION_SCAN_INTERVAL后台 worker 扫描周期默认1hWK_CHANNEL_MESSAGE_RETENTION_CHANNEL_BATCH_SIZE每轮最多处理的频道数默认128WK_CHANNEL_MESSAGE_RETENTION_MAX_TRIM_MESSAGES单频道单轮最多清理的消息数限制单次 batch 大小与写放大默认10000。对应的配置结构体与默认值/校验逻辑在 internal/app/config.goChannelMessageRetentionConfig、defaultChannelMessageRetentionConfig、validateChannelMessageRetentionConfig环境变量解析在 cmd/wukongim/config.go配置示例需同步补充到wukongim.conf.example。注意当前仓库的配置实现已演进为以PhysicalGCEnabled开关 扫描周期/批量/删除上限为主的节点本地物理清理形态TTL 语义由上层按权威边界驱动启用时ScanInterval默认1m、MaxTrimMessages默认1000与计划文档中的早期默认值略有差异——以实际仓库代码为准。后台 worker 与生命周期顺序internal/runtime/channelretention计划中的 worker 包每轮对每个频道执行读取 runtime retention view要求本节点是当前 leader、频道 active、lease 有效、CommitReady true扫描该频道 store 的连续过期前缀从view.MinAvailableSeq开始受MaxTrimMessages限制计算candidateThroughSeq min(expiredThroughSeq, CheckpointHW, HW, MinISRMatchOffset)不含 replay cursor若candidateThroughSeq view.RetentionThroughSeq记录 no-op 并跳过 cursor confirm对 leader replay cursor 做 durable confirmminSeq view.RetentionThroughSeq 1得到durableCommittedReplayCursor计算safeThroughSeq min(candidateThroughSeq, durableCommittedReplayCursor)若不超过当前 retention 则跳过若扫描/cursor-confirm 耗时较长先 refresh 校验 lease 新鲜度再以完整 fence 调用MetadataStore.AdvanceChannelRetentionThroughSeqmetadata commit 后本地 best-effort 调用ApplyRetentionBoundary失败仅记录由权威 metadata refresh 下一轮 worker 重试slot metadata 是唯一真相源记录 blocked reason继续下一个频道。生命周期顺序上retention worker 必须在committed replay 之后启动、在 committed replay 与 channel log 关闭之前停止避免清理 worker 删除 committed replay 仍需读取的消息。计划中的期望启动顺序为cluster - managed_slots_ready - channelmeta - presence - conversation_projector - delivery_runtime - committed_dispatcher - committed_replay - channel_retention - gateway - api - manager停止顺序相反。故障场景与一致性保证Leader 提交 boundary 后崩溃但本地尚未删除新 leader 从 slot metadata 读到 boundary 后继续应用读语义不回退Follower 删除成功但 leader 崩溃boundary 已是权威状态未来 leader 不应再服务旧消息Follower 暂时未删除读取路径按权威 boundary 裁剪本地多留数据只影响空间不影响语义旧 leader 网络分区后继续清理提交 boundary 时会被 leader epoch 或 lease fence 拒绝非 ISR 老副本重新加入先读取权威 boundary执行 retention reset/catch-up floor再从新边界之后追赶完成追赶前不能加入 ISR也不能凭旧数据成为 leaderCursor durable confirm 成功但 metadata commit 失败只多持久化了 replay cursor不隐藏消息Metadata commit 成功但物理删除失败旧消息仍占空间但所有读路径按 boundary 裁剪。源码验证清单想深入验证上述行为的读者可以在仓库中找到以下关键实现与测试权威元数据窄命令与单调性pkg/slot/fsm/command.go、pkg/db/meta/channel_runtime_meta_test.goTestChannelRuntimeMetaAdvanceRetention覆盖 fence 拒绝命令 inspectionpkg/slot/fsm/command_inspection.go、pkg/slot/fsm/command_inspection_catalog_test.go本地 store 原语pkg/db/message/compat.goScanExpiredMessagePrefix、AdoptRetentionBoundary、TrimMessagesThroughLimit、cursor 持久化、pkg/db/message/retention.go原子 trim 与 retention state codecreplica/reactor 应用路径pkg/channel/reactor/retention.goApplyRetentionBoundary、retentionTrimDecision、minISRMatchOffset、pkg/channel/retention.go类型与 blocked reason 常量契约测试pkg/db/message/compat_operational_contract_test.go、pkg/db/message/retention_contract_test.go、pkg/channel/store/channel_adapter_test.go。总结WuKongIM 的频道消息保留把过期前缀清理建模为集群一致的日志前缀 retention——leader 提交权威边界、副本按边界裁剪读取、物理删除异步滞后从而在保证 leader/follower 读边界一致与 leader 切换不回退的前提下安全地释放本地存储。赞分享即时通讯后端【免费下载链接】WuKongIMMore than just IM 不只是即时通讯(IM)项目地址https://gitcode.com/gh_mirrors/wu/WuKongIM点击查看免费下载相关推荐Apache Pulsar 消息保留Retention与过期TTL管理完全指南Apache Pulsar 消息保留Retention与过期TTL管理完全指南 本文以 Apache Pulsar 的消息保留与过期机制为核心系统讲解消息队列后端流处理WuKongIM InternalV2 消息保留物理清理基于 RetentionThroughSeq 的异步前缀删除设计与实现WuKongIM InternalV2 消息保留物理清理基于 RetentionThroughSeq 的异步前缀删除设计与实现 导读 本文讲解 WuKongI即时通讯后端Apache Pulsar 消息保留Retention与过期TTL策略实战指南Apache Pulsar 消息保留Retention与过期TTL策略实战指南 本指南基于 Apache Pulsar 2.2.0 版本的官方 CookAI 技能前端CLIdsh-plugin上一篇OAuth-Plugin部署指南生产环境配置与监控最佳实践下一篇UN1CA社区贡献指南如何参与三星自定义固件开发创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考