Vadim Yatsenko:服务器优先的读连接池:精确连接复用与热路径性能
内容提要
本文介绍Polar Proxy对读连接池热路径的优化:连接池统一管理读连接,按启动配置、用户/数据库、客户端身份和启动选项四要素精确匹配复用;选副本采用P2C(二选一)比较相对负载,并结合工作线程本地复用减少共享池访问;容量不足时挂起请求等待唤醒而非自旋。此外,查询摘要统计改为线程本地批量刷新,日志关闭时跳过检查,后端计数器批量更新,并通过零拷贝转发大结果集,从而降低锁竞争、内存分配与复制开销,且不改变一致性保证。
延伸解读
精确复用为何是路由正确性的前提
文章强调,读连接只有在启动配置、用户/数据库、客户端身份和启动选项四要素完全匹配时才能复用。原因在于PolarDB连接会协商LSN上报和带内等待等扩展,若复用不匹配的连接,后端行为会与客户端预期不同。上游ProxySQL的通用池无法表达这些维度,只能在“冒险复用”和“拒绝复用”之间二选一。Polar Proxy用四段精确匹配键和索引解决此问题,使连接复用不再牺牲路由保证。
P2C与工作线程本地缓存如何配合
选副本时,P2C只随机比较两个候选的相对负载,避免全量扫描;当只有两个读副本时,改用工作线程本地加权序列轮转。选定服务器后,工作线程把刚用过的匹配连接暂存在线程本地缓存,默认仅保留到本轮事件循环结束,下一次同服务器读可直接复用而不触碰共享池。两者结合让常见路径的选择与复用成本接近常数级,同时共享池仍是连接的唯一所有者。
容量不足时挂起而非自旋的意义
当所有合适连接都在使用且无创建槽位时,上游ProxySQL会自旋并反复扫描。Polar Proxy改为记录待处理请求的匹配键、读计划和等待目标,将其挂起,事件循环继续服务其他会话;连接归还或新建后直接交给该请求,并只唤醒被分配的工作线程。等待仅在确认容量压力后启动,因此首次命中仍走快速路径。这减少了高并发下的无效CPU消耗和锁竞争。
热路径减负的边界与默认行为
查询摘要改为工作线程本地累积后批量刷新,日志关闭时直接跳过检查,后端计数器每64次查询或64KiB批量更新,从而把共享锁移出每查询路径。结果转发方面,零拷贝行批快速通道是可选且默认关闭,而基于iovec的scatter-gather发送默认开启,但仅适用于无TLS的普通套接字且队列中已有至少两个包的情况。这些优化不改变一致性保证,属于纯性能层。
Q&A
Polar Proxy 的读连接池如何判断一个连接是否可以复用?
连接必须满足四部分精确匹配:启动配置生成号、用户/数据库哈希、客户端启动身份(仅当启动配置携带该身份时比较)以及启动选项。任何不匹配都会导致使用不同或新建的连接。
P2C(二选一)选副本策略具体是怎么工作的?
P2C 按配置权重随机选取两个副本,然后比较它们的相对负载(活跃连接数除以权重),选择负载较低的一个。对于带 LSN 目标(wait target)的读请求,会优先选择已回放足够 LSN 的副本,若没有则回退到其他副本并在服务端等待。
当连接池容量不足时,Polar Proxy 如何处理等待的请求?
Polar Proxy 不会自旋重试,而是记录挂起请求(包括匹配键、读计划、等待目标),将其挂起并继续服务其他会话。当有连接归还或新建时,直接交给该挂起请求,并仅唤醒被分配的工作线程(通过 poll 唤醒管道)。
Polar Proxy 如何减少查询摘要统计带来的锁竞争?
每个工作线程在本地小映射中累积查询摘要统计,当映射达到条目上限、执行周期性清理或有人读取计数器时,才批量刷新到共享存储。共享映射仅作为最终报告存储,不再是每查询的竞争点。
Polar Proxy 在转发大结果集时如何避免拷贝?
有两种方式:1) 可选的快速转发路径(默认关闭)通过 PSpeekRowRun/PSdetachRowRun 识别并分离连续行包,转移缓冲区所有权而非逐包拷贝;2) 默认启用的直接路径,当输出队列积累至少两个包(或一个超大包)且前端为无 TLS 的普通 socket 时,直接构建 iovec 并用单次 scatter-gather sendmsg 发送(每次最多 64 个包)。
Polar Proxy 的优化是否会影响一致性保证?
不会。这些优化是纯性能层,不改变基于 LSN 的路由规则和安全回退规则,一致性保证保持不变。