Chris van Eijk:PostgreSQL 中按租户划分的审计日志:触发器的开销有多大

Chris van Eijk:PostgreSQL 中按租户划分的审计日志:触发器的开销有多大

💡 原文英文,约1400词,阅读约需5分钟。
📝

内容提要

实测PostgreSQL 17多租户审计日志触发器开销:2万次单行更新每次增加32微秒;10万行批量更新慢3.6至4.4倍。语句触发器配转换表比行触发器快约两成,但仅限AFTER且不能指定列。只存变更列可使日志缩小五倍,但比较更耗CPU。用行级安全策略限制租户只能读自己的日志,应用角色仅可SELECT和INSERT,日志追加不可改。

🔎

延伸解读

触发器开销的适用场景

文章实测显示,单行更新时触发器仅增加32微秒,对普通Web请求几乎无感;但批量更新10万行时,语句耗时从0.6秒增至2.6秒,并产生94MB审计日志。因此,触发器审计更适合低频单行操作,而迁移、回填等批量操作需评估性能影响,避免因审计导致耗时成倍增加。

行触发器与语句触发器的选择

对于批量更新,语句触发器配合转换表比行触发器快约20%,但仅支持AFTER触发器且不能指定列。单行更新时语句触发器略慢,因其需为每行构建转换表。选择时需权衡:若批量操作频繁,优先语句触发器;若需针对特定列审计,则只能使用行触发器。

存储变更列与全行的权衡

仅存储变更列可使审计日志缩小五倍(从93.6MB降至18.8MB),但需逐键比较新旧行,导致批量更新耗时增加约45%。若日志主要用于展示变更内容,差异存储更高效;若需恢复历史行,全行存储更方便。宽表且仅少数列频繁变更时,差异存储优势明显。

租户隔离与日志不可篡改

通过行级安全策略,应用角色仅能SELECT和INSERT自己的审计日志,且触发器插入时自动校验租户上下文,防止跨租户写入。但超级用户和表所有者仍可修改或关闭保护,因此对法律纠纷等场景,需将日志复制到应用角色无法访问的存储中,确保不可篡改。

❓

Q&A

在PostgreSQL 17中,审计触发器对单行更新和批量更新的性能影响有多大?

在2万次单行更新中,行触发器每次增加32微秒;对10万行的批量更新,行触发器使语句慢4.4倍,语句触发器慢3.6倍。

行触发器和语句触发器在批量更新时哪个更快?为什么?

语句触发器更快。在10万行更新中,语句触发器耗时2104毫秒,行触发器2579毫秒,快约五分之一。因为语句触发器通过转换表一次性处理所有变更行,而行触发器每行执行一次。

只记录变更列与记录完整行相比,在存储和性能上有什么权衡?

只记录变更列使日志缩小五倍(从93.6MB到18.8MB),但触发器更慢(批量更新从2579ms增加到3747ms),因为需要逐列比较新旧行。适合宽表且只有少数列频繁变更的场景。

如何利用行级安全策略确保租户只能访问自己的审计日志?

对审计表启用行级安全并强制应用,创建SELECT和INSERT策略,使用current_setting('app.tenant')限制tenant_id。应用角色仅授予SELECT和INSERT权限,禁止UPDATE和DELETE,使日志只能追加。

使用语句触发器配合转换表有哪些限制?

转换表只能用于AFTER触发器,且使用转换表的UPDATE触发器不能指定列,因此无法限制为仅当特定列更新时触发。

审计日志的追加不可改特性如何保证?有什么例外情况?

通过仅授予应用角色SELECT和INSERT权限,并启用行级安全策略,使日志只能追加。但超级用户和表所有者仍可修改或关闭保护,因此对于需要法律效力的场景,应将日志复制到应用数据库角色无法访问的地方。

🏷️

标签

➡️

继续阅读