租户删除
删除租户是一个生命周期过程。删除操作立即切断访问,随后在后台回收租户数据;只有回收完成后,租户标识令牌才能再次使用。
本页介绍这两个时刻之间会发生什么、哪些情况会阻碍删除完成,以及平台有意保留哪些记录。
各阶段的时间安排
| 立即 | 撤销登录权限,禁用租户并标记为正在删除,同时记录访问切断时刻。约一分钟内,拒绝新的设备连接、数据接入和指令下发。 |
| 一到两分钟内 | 后台协调器开始从每个持有租户数据的存储系统回收数据,持续执行直到所有系统都报告不再持有数据。 |
| 至少 12 小时后 | 移除租户记录,其标识令牌可再次使用。 |
删除操作本身具有幂等性。删除不存在或已经处于删除过程中的租户会成功返回,不产生其他变更。因此,可以重新运行中途失败的清理脚本。
如果租户仍有用户成员关系,删除会被拒绝,因为移除成员关系才是真正撤销用户访问权限的操作。先移除成员关系,再删除租户。
为什么保留令牌 12 小时
租户数据通常在最初一到两分钟内就被清除。继续保留的是令牌;平台报告删除完成前,必须满足两项独立等待条件。
每个存储系统必须持续报告已清空。 一次干净的结果不足以证明清除完成:清扫结束时,一个已经在执行的写入可能随后落地。因此,平台要求持续静默期(默认五分钟);任何一轮仍发现需要删除的数据,都会重新开始计时。
删除之前建立的连接不能再写入。 已连接的设备会保留消息代理凭据直到凭据过期,最长 12 小时。设备凭据记录已被删除,因此无法重新连接,但现有连接仍存活。如果提前释放令牌,迟到写入可能落在一个之后供新租户使用的名字下,导致新租户继承这些数据。整个过程正是为了防止这种情况,因此等待时间从删除时刻算起,并与凭据有效期一致。
两项等待条件都满足之前,令牌保持占用。使用该令牌创建租户会返回相应错误。
删除哪些数据
租户数据分散在多个地方,回收过程依次检查每个存储系统:
- 主数据库:其中所有功能领域的模式;
- 遥测数据库:保存事件历史;
- 消息代理:租户的流,以及 MQTT 网关自身的会话状态、排队消息和订阅;
- 键值存储:缓存的查找和解析结果;
- 运行中的事件处理引擎:打开的检测窗口、运行中的定时器和已启用的数据缺失检查。这些状态存在于内存中,因此要求引擎驱逐租户,而不是通过查询清理;
- 对象存储:租户品牌标志等上传资源。
只有所有系统都报告已清空,删除才能完成。无法访问的存储系统会在下一轮重试,不会跳过。
有意保留的内容
部分记录会有意保留,它们都不包含租户自身的数据:
- 用户。 用户账户属于整个实例,不属于租户。被移除的是用户与租户之间的成员关系。
- 实例级定义:角色、层级、OAuth 客户端、签名密钥和系统设置。它们在每套部署中只存在一份,不随租户删除。
- 删除记录。 每次删除都会持久记录删除了什么、何时删除,并为每个存储系统保留一条记录。记录不会保留租户名称或联系信息,否则清除证据反而成了最后一个仍存放客户信息的地方。
- 审计日志,其中的身份信息已销毁。 详见下文。
备份是上述说明的例外。 备份包含整个数据库,因此被删除租户的数据仍留在备份归档中,直到超过数据库的恢复窗口:核心数据默认 30 天(backup_retention_rdb),事件数据默认 7 天(backup_retention_tsdb)。在此之前,恢复到删除之前的时间点会带回这些数据。发送到你提供的对象存储的备份,还受该存储自身生命周期规则约束。使用卷快照基础备份时,集群运行期间会按同一窗口清理云提供商处的快照;如果集群在清理完成前被删除,快照及其中的数据会留在提供商处,直到你在那里删除它们。
审计日志
实体的每次变更都会记录到审计日志中:发生时间、涉及的表、操作、行数以及操作者。删除租户时保留日志,因为它证明删除确实发生。清除日志会同时抹去清除操作的证据。
任何人的身份信息都不会保留。有两个字段可能标识一个人:
- 操作者;对于人工登录,这通常是邮箱地址;
- 受影响行的标签;可能是邮箱,或你为客户、设备、资产选择的名称。
删除租户后,这两个字段都被永久置空。 字段写为空白,而不是混淆或哈希。邮箱很短、容易猜测,持有邮箱列表的人可以将哈希匹配回原地址,那只是名义上的清除。
删除后,租户日志条目仍描述发生了什么(例如“14:02 删除了三台设备”),但不再列出任何人的姓名。这是保留策略正常生效,不是数据缺失。
原成员的登录历史在租户删除后仍保留。如果你的义务也涉及登录记录,应通过数据库和日志保留策略处理,而不是依靠租户删除。
登录分两步:先以个人身份认证,再选择租户。第一步无论成功或失败,都会记录邮箱地址,但不关联租户,因为尚未选择租户。没有租户的记录不可能被任何租户删除操作触及。删除覆盖的是成员在租户内部执行的操作。
另外还有两项较小的限制:
- 原成员在租户删除完成之后尝试登录,会产生一条标识该用户的新记录;此时该令牌可能已经属于其他租户。
- 用户自身资料的日志条目仍记录被修改的账户。账户不属于租户,也不随租户删除,因此能够访问两者的运营方仍可以建立关联。销毁的是日志中的姓名,不是日志所引用账户的存在。
什么会阻碍删除完成
删除长期未完成,通常由以下情况之一造成;平台日志会明确指出原因:
- 事件处理服务未运行。 内存中的引擎状态只能由持有它的进程清除。服务缩容到零时,删除会等待,这是正确行为,因为数据仍然存在。启动服务后,下一轮即可处理。
- 未配置对象存储,但租户曾上传资源。 对象引用已知,对象本身却无法访问。配置保存该对象的存储后端,或通过平台之外的方式移除对象。
- 存储系统不可访问。 这被视为“稍后重试”,而不是终止失败。系统恢复后,删除自动继续。
这些情况不会导致删除任务丢失。工作清单就在租户自身记录中,因此协调器停止、副本重新调度,甚至系统停机一周后,都会在下一轮继续推进。
如何获知阻塞
删除阻塞不会让其他功能看起来故障,因此需要独立告警。协调器每轮访问租户,却没有可报告的运行错误,这一轮仍成功。计划任务指标用于发现协调器停止运行,因此删除阻塞期间也可能始终健康;它们回答的是另一个问题。
TenantPurgeStalled 专门回答删除是否阻塞。当最早尚未完成的删除已持续超过配置的令牌保留时间两倍时触发,此时所有强制等待早已结束。管理 API 的 tenantDeletions 查询会列出租户和仍未完成的存储系统。与管理 API 的其他列表一样,它支持分页;读取尚未完成的删除:
query {
tenantDeletions(criteria: {pageNumber: 1, pageSize: 50, completed: false}) {
results { token epoch awaiting blockedBy stores { store complete retaining } }
pagination { totalRecords }
}
}
配套的 TenantPurgeVisibilityLost 在这些数据完全停止采集时触发。没有它,未被抓取指标的用户管理服务,与没有任何未完成删除的服务看起来完全一样。
配置
这些设置位于用户管理服务的 tenantPurge 下。三项设置中,0 都表示“使用默认值”。
| 设置 | 默认值 | 含义 |
|---|---|---|
intervalSeconds | 60 | 协调器运行间隔。负值会完全禁用协调器。 |
settleSeconds | 300 | 每个存储系统必须持续报告已清空的时间。必须大于 140。 |
tokenHoldSeconds | 43200 | 删除令牌保持占用的时间,从删除时刻计算。 |
禁用协调器是一项受支持的运维操作,例如维护窗口内不应删除任何行时。这是安全的:待处理删除保持待处理,不会丢失;关闭期间不会释放任何令牌。
两个等待窗口都不能禁用。 任一窗口的负值都会在配置加载时被拒绝,而不是生效。关闭静默窗口意味着尚未观察到数据消失,就宣布已清除;关闭令牌保留意味着旧会话仍可用该名称写入时就释放名称。降低 tokenHoldSeconds 是实际的配置选择,因为删除租户的名字会占用这么久,但它影响正确性,不只是清理速度。
删除期间
设备流量约一分钟内停止。 删除状态被识别后,所有传输方式的新连接、数据接入和指令下发都被拒绝。被拒绝设备获得与超出速率限制时相同的响应,随后退避重试。
已经连接的设备不会被主动断开。 它们保留现有消息代理凭据直到过期,最长 12 小时,过期后无法重新连接。这就是令牌保留的原因。
用户管理服务不可访问时,这些拒绝暂时不生效,直到服务恢复。这是刻意的权衡,否则实例上所有租户的设备连接都会硬性依赖用户管理服务。提前拒绝流量是为了避免回收不断追赶新数据;清除本身并不依赖它。
数据库拒绝写入
数据库单独拒绝写入,不依赖其他服务。从回收首次触及主数据库和遥测数据库起,各数据库拒绝被删除租户的所有写入,无论写入来自 API、服务消费的流还是自身后台任务。检查在被拒绝写入的同一个数据库事务内运行。
这使保证成为真正的数据清除,而不只是清扫:即使原本用于提前停止流量的措施全部失败,回收掉的行也不能重新出现并长期保留。迟到写入会再次清扫;写入尚未停止时,删除不会完成。只有删除完成时才解除拒绝,这也正是令牌释放时刻,因此用同一令牌创建的新租户从第一个请求起就能正常写入。
拒绝检查在事务首次写入某租户时执行。因此,删除开始时已经为该租户写入的事务,仍能提交,包括其后续写入。只要事务在静默窗口结束前提交,删除过程会捕获这些写入。对于服务停止与数据库通信的事务,例如 Pod 冻结或网络中断,数据库提供相应保证:事务空闲 60 秒后终止并回滚,明显短于允许的最小静默窗口。数据库记录 terminating connection due to idle-in-transaction timeout,事务写入内容不会保留。所属请求返回错误,从流中获取的工作会重新投递。
拒绝覆盖保存租户记录的两个数据库。消息代理、键值存储和对象存储由相同回收轮次处理,但没有等效的写入拒绝。迟到消息或对象会在后续轮次清除,而不是立即拒绝。这也说明为什么所有系统必须在整个静默窗口内保持干净,删除才能完成。
对外连接器与通知
对外连接器同样受拒绝机制保护,而且这里尤其重要。其他地方,稍晚获准的消息只是暂留平台、等待回收的数据。对外连接器会把租户数据发送到你拥有、平台却不拥有的系统(webhook 端点、MQTT 消息代理、Kafka 主题、SNS 或 SQS 队列);发送后,后续回收轮次无法撤回。因此,被删除租户的连接器投递会被拒绝并丢弃,不会留待检查。
通知也会停止。被删除租户的告警不再发送邮件或调用通知 webhook,尚未解除的告警不再升级。这不依赖设备流量:升级会按定时器再次通知未确认告警。如果没有该拒绝,被删除租户的值班人员会继续收到已经不存在租户的告警通知。
删除发生时已经执行中的动作会完成;拒绝大约在删除后一分钟内生效,而非立即生效。如果删除必须保证外部系统或收件人不再收到任何内容,请先禁用租户连接器和通知策略,再删除租户。
目前可查看的内容
正在删除的租户,其管理控制台详情页有 Deletion(删除) 页签。它回答运营方最关心的问题:*完成了吗?如果没有,为什么?*其中包含:
- 易懂的状态说明;
- 阻塞事项;
- 每个存储系统一行,显示已清空、仍持有数据,或失败后正在重试。
这三种行状态有意区分。仍持有数据通常需要有人改变某项条件;运行失败则会在下一轮清扫中自动重试。
某些系统会在“已清空”行中添加简短说明,指出未检查的内容,例如该实例不存在的遥测数据库,或删除有意豁免的内部缓存。这只是“已清空”的脚注,不是需要处理的问题。
已完成删除位于 Admin → Deletions(管理 → 删除记录),而不是租户页面。删除完成会移除租户,因此也不再有租户页面可显示结果。实例级列表是持久证据,记录令牌、请求时间、完成时间、删除量和各存储系统报告。需要证明客户数据已删除时,可查看此列表。
这里有意不提供两项操作:
- 没有重试按钮。每轮清扫已自动重试,按钮反而会让人误以为没有自动重试。
- 没有强制完成。它可能将未发生的清除记录为已完成。