GDQIssues
请添加/重新设置优先级讨论要点。
当前状态
有 2 个使用“基于快照的分组”技术进行排队的实现:Slony 和 PgQ。
- Slony - 排队不是通用的,只能用于 Slony 本身。历史上排队逻辑存在问题,但现在应该没问题了。但这可能让一些人对“排队表”产生了不好的印象。
- Pgq - 排队可以使用通用 API。很少有人听说过它,更少人熟悉它。最初的作者没有广告预算。(快速概述,完整文档)
有一些替代的想法在流传,但它们似乎经不起任何仔细审查
- 使用 WAL 进行排队
- 使用组通信系统进行排队
- 使用非事务性存储进行排队。
至少有一个产品使用非通用排队 - Bucardo。它们不使用基于快照的分组,因此无法保证批次在重复读取时不会发生更改。如果您只通知“此 PK 发生了事件”,这没什么问题,但对于其他任何事情都不行。
需求
通用排队实现应满足以下需求
- 可用于多个基于 PostgreSQL 的复制系统(Longdiste、Slony、Bucardo?)
- 不需要生产者和消费者运行相同的 PostgreSQL 主要版本
- 允许多个读取器
- 允许多个写入器(libpq 客户端)
- 必须允许读取器停机,而不会丢失事件,也不会影响写入器
- 稳定的批次 - 批次内容在重复读取时必须保持不变。这是远程服务器中事务处理所需的。
非问题
以下是似乎已经确定的事项。要重新打开它们需要非常好的、实际的论据。
Josh:GDQ 上的讨论时间还不到一周。现在就确定主要问题似乎为时过早。
Marko:确实。但是将所有问题都保持开放状态会导致过度讨论。最初的分类是我个人的意见,基于现有的排队实现 - PgQ。因此,要求在进行与现有工作代码不同的操作时提供实际论据似乎是个好主意。在没有实际理由的情况下决定设计似乎不切实际……无论如何,页面似乎开启了关于该主题的讨论,让我们现在让页面反映社区共识。
事务性与非事务性
事务性。否则,它将无法用于复制和期望其事件持久化的应用程序。
我们可以选择将事务性队列转换为非事务性队列,使用全局临时表(见下文)。
专门针对非事务性使用进行设计似乎是错误的。而且,还有一些专门的解决方案可以更好地解决这个问题。
外部 Postgres 与内部
如果您拥有比当前 PostgreSQL 更高效的事务性存储引擎,请替换 PostgreSQL 的存储引擎……
我记得有人争论过由于“写入开销”而通过网络写入。
- 服务器超载。额外的排队写入会导致服务器崩溃。这是典型的“医生,我疼”的情况。
- 主要是写入负载(例如,某种事务性日志记录)。额外的排队写入会使写入负载加倍。解决方案很简单:直接写入队列。但是,只有在您拥有通用(不限于复制)队列的情况下才能看到此解决方案。
Josh:建议使用一种特殊类型的表,可以在写入时进行 fsync,或者根据设置不进行 fsync。这将允许我们消除 GDQ 的 WAL 开销,这是一个可观的节省。它还将支持在重启后不关心保留队列的应用程序。
Marko:是的,有选择地关闭事务性开销可能是个好主意。
基于快照的分组与其他方法
似乎不存在可以认真考虑的替代方案。
需求
- 多个读取器
- 多个写入器
- 写入是事务性的
- 读取是事务性的
- 必须允许读取器停机,而不会丢失事件,也不会影响写入器
- 稳定的批次 - 批次内容在重复读取时必须保持不变。这是远程服务器中事务处理所需的。
轮询与推送
轮询。
在繁忙的服务器中,总是存在可用事件。没有必要针对空闲服务器进行优化。
Josh:不同意。轮询会自动将延迟引入所有更新处理,这意味着永远无法实现近同步复制,也无法集成到基于事件的系统中。此外,在部分数据正在复制的系统中,更新可能是不稳定的,但计时仍然至关重要。
Marko:您不可能拥有既具有高吞吐量又具有低延迟的队列,而且还尝试使其成为事务性的。“近同步”复制似乎是为不同的使用场景提供不同队列实现的理由。
限于复制或通用。
通用。
这也使得“修改触发器格式”成为非问题。
Itagaki:“将所有数据存储为文本格式”可能不是“通用”队列的最佳解决方案。我们需要考虑“类型安全”的通用解决方案吗?例如,CREATE GDQ name (seq_id bigint, dml_type "char", pk pk_type, tup table_type)
。
Marko:您似乎在谈论下面提到的“用户定义事件表结构”。我建议反对它,因为它不允许在同一个队列中使用不同的事件类型。使用当前的 PgQ(固定表格式),我们有每个事件类型的“接口表”,它们是具有适当结构和类型的空表,以及 BEFORE 触发器(pgq.logutriga),它将事件格式化并插入队列,然后告诉 Postgres 跳过实际的插入。因此,我们可以使用 INSERT 插入接口表,以纯 SQL 方式插入事件,并实现完全的类型安全性。使用 GDQ,我们可以更容易地创建此类每个事件类型的接口表,尽管现在已经相当容易了
普通表与自定义存储。
表。如果自定义存储消除了执行普通 SELECT/INSERT/UPDATE/DELETE 的能力,那么它将过于限制。
Josh:如何将这一点与降低队列写入开销的愿望协调起来?
Marko:使用全局临时表的可选功能似乎是良好的平衡。任何更特殊的存储,即使它为了效率而放弃了正常的表访问,似乎都只是针对非常狭窄的使用案例进行设计,而忽略了它应该如何开发、维护、管理和使用……我强烈建议不要与专门的、非事务性的、内存中的排队解决方案竞争。
自定义访问协议与纯 SQL
纯 SQL。
例如,在 PgQ 中,我们有充当队列消费者的纯 PL/pgSQL 函数。
失去这种灵活性将是糟糕的。
有或没有中间件
没有。
这里的中间件是指解决方案绑定到执行事件处理的代码。[用户代码作为中间件插件。]
没有中间件是指解决方案使事件可供读取,而不决定如何处理它们。[用户代码是 SQL 客户端。]
潜在问题
以下是几个从头开始编写队列时可能选择任一方的决定。
似乎没有哪种选择明显优于另一种选择。
但实际上,如果决定与当前的 PgQ 不同,就意味着要编写大量代码……
固定事件表结构与用户定义结构。
这里的固定格式是指事件数据被多路复用到几个预定义的字段中。例如 PgQ
- ev_type - 对事件进行分类的简短 ID。
- ev_data - 针对事件数据的 URL 编码/JSON/XML 容器。
- ev_extra1 - 关于事件的元数据。尽管 PgQ 中有几个 extraN 字段,但一个就足够了,如果它被定义为可扩展格式(例如,URL 编码的键值对)。
用户定义结构是指自由格式的表定义,除了用于排队的几个固定(或隐藏)字段。
固定格式
- 可以在没有核心更改的情况下实现
- 允许同一个队列中存在多个事件类型。
- 允许可能希望使用其自身事件类型的通用框架(Skytools 3.0 中的 Cascading)
可定制结构
- 学术上更漂亮
- 与表轮换不兼容
- 与同一个队列中的多个事件类型不兼容
- 与通用框架不兼容。
- 是否需要核心支持以上功能?
基于函数的 API 与特殊语法
特殊语法(核心中的队列逻辑)是必需的,因为
- 实现工作队列的唯一方法(Oracle)
- 普及新技术的唯一方法(PostgreSQL?)
- 实现非普通表存储的唯一方法。
- 允许定制事件表格式的最佳方法。
尽管可以使用基于函数的 API 实现可定制的事件表格式,但在许多地方这样做会很笨拙。如果需要此功能,最好在核心代码中实现它。
因此,除非实施者决定使用可定制的结构或非表存储,否则不需要核心代码中的队列。
有趣的问题
目标是什么?
- Slony/Londiste 之间可以共享的东西,但也可以用作通用队列?
- 与 Oracle 语法兼容吗?
- 以任何代价实现最低开销?
- 必须在核心代码中吗?
- 在任何维度上都是完美的东西吗?可能涉及可插拔后端?
- 能够与其他数据库和缓存系统一起工作的东西?
如何将 PgQ 加入 /contrib?
为什么不将 PgQ 或其清理后的变体放入 /contrib 中?
反对的论据可以相当清楚地说明人们对队列实现的期望。如果没有充分的理由,那就让我们去做吧。
可以简化的潜在领域
- "事件重试"功能与主队列功能联系非常松散,可以从基本实现中移除并构建在它的基础之上。
- 可以清理内部表结构。
核心更改最小
虽然 Slony 和 PgQ 证明了用当前核心功能实现队列是可能的,但独立的重新实现实际上是不可能的,因为一些逻辑部分相当复杂。我们可以将只有复杂的部分移到核心,从而使队列重新实现更容易。
WHERE txid_field BETWEEN snapshot1 AND snapshot2
使此类查询执行起来相当困难。将优化逻辑添加到核心。
轮换表
需要一些技巧才能在避免锁定问题的同时使读取、写入和截断轮换表工作。
使之更容易。
全局临时表
GTT 将是一个表格,它
- 对所有用户可见,如同普通表
- 数据直接写入表,跳过 WAL 写入
- 不执行 fsync
- 表在崩溃时被截断,也许在重启时也会被截断。
GTT 将使我们能够轻松地将事务性队列转换为非事务性队列,适合那些希望以最大效率换取事件持久性的人。
假设同步复制也应该忽略此类表。
不同排队技术的示例
上述提到的基于快照的分组 (SBG?) 可用于高吞吐量和低延迟情况。
是否有可能在一个实现中同时实现低延迟和高吞吐量的可调性,这一点值得商榷。这似乎暗示我们可能需要针对这两种情况分别使用不同的实现。
SBG,高吞吐量,事务性
关键在于拥有大型批次。单独的进程写入快照,读取器轮询新的快照并在它们可用时读取它们。延迟来自两个因素 - 快照写入器写入新批次的周期和读取器轮询的周期。我们不想消除第一个因素,因为这是使处理大量数据有效的原因。可以通过监听/通知消除后者,但这似乎不是一个好主意,因为对于更多读取器来说,让它们在同一时间请求读取不是一个好主意。
此外
- 读取器停机不是问题
- 大量事件不是问题。
缺点
- 需要明显的延迟来最小化每个事件的开销。
SBG,低延迟,事务性
关键在于立即批处理。读取器自身写入快照,并立即从该快照和前一个快照中读取事件。与简单地从表中读取相比,SBG 允许稳定的批次和仅插入表。
此外
- 延迟仅取决于读取器轮询周期。
- 延迟可以通过 LISTEN/NOTIFY 消除。
缺点
- 不允许读取器停机。
- 大量事件可能成为问题。
非事务性
以上两种方法都可以通过 GTT 转换为非事务性。
当有人希望更有效的非事务性队列时,是否最好转向专用解决方案?