Fly公司如何实现全球分布式Postgres?


本文讨论如何使用标准工具和简单的 Fly 功能部署具有全局复制 Postgres 的标准 CRUD 应用程序,用于读取和写入。
如果您过去曾构建过全球分布的应用程序,那么您可能熟悉这些挑战。扩展只能读取的数据库很容易。数据库引擎具有支持“读取副本”的功能,以实现高可用性和缓存,因此您可以快速响应传入的读取请求。这很好:这种方案通常对读取性能更敏感,普通应用程序的读取量非常大。
但是当用户执行更新数据库的操作时,这些方案就会失效。将更新从单个写入器流式传输到一堆副本很容易。但是一旦写入可以登陆多个实例,大规模则歇斯底里!分布式写入很难。
事实证明,只要与应用程序进行一点合作,就很容易区分读取和写入。
当一个写请求进来时,就试着写,就像一个假人。如果写入者在A地而请求到达B地,则写入失败;您无法写入只读副本。好的!该失败将产生可预测的异常。抓住它:

rescue_from ActiveRecord::StatementInvalid do |e|
  if e.cause.is_a?(PG::ReadOnlySqlTransaction)
    r = ENV["PRIMARY_REGION"]
    response.headers[
"fly-replay"] = "region=#{r}"
    Rails.logger.info
"Replaying request in #{r}"
    render plain:
"retry in region #{r}", status: 409
  else
    raise e
  end
end

在 8 行代码中,我们捕获了只读异常并吐出一个fly-replay标头,它告诉我们的代理在写入者的区域中重试相同的请求。
我们的代理完成剩下的工作。
将整个 HTTP 请求发送到需要的位置,这种方法比将数据库从应用程序实例移开要快得多。
 
Fly-Replay 标头
当请求到达A服务器时,我们会根据负载数据估计哪个进程具有容量。然后我们将请求发送到运行该进程的服务器。然后我们检查进程是否还有容量。如果是,那太好了,我们将请求转储到用户进程中。如果没有,我们向边缘服务器发送回复,说“嘿,这个进程已满,请再试一次”。这种效果我们称之为延迟消除:如果我们可以足够快地尝试应用程序进程的每个实例,我们将始终能够为请求提供服务。
 
80% 的时间它每次都有效
使用像 Postgres 这样的数据库的很大一部分原因是您想要强一致性。高度一致的数据库易于使用。您向一致的数据库发出写入,然后进行读取,读取看到写入的结果。当您失去此功能时,事情会很快变得复杂。
显然,一旦您开始根据本地条件将数据库(或 HTTP)请求路由到不同的服务器,您就放弃了某些一致性。我们不能违背物理定律!当您从法兰克福读取然后写入达拉斯时,达拉斯会快速将更改的行复制到法兰克福,但不会立即复制。有一个不一致的窗口。
首先,在很多情况下,这可能无关紧要。有大量应用程序,其中写入和后续读取之间的短暂不一致不是大问题。数据复制得足够快,您的用户可能不会看到不一致的地方。如果显示陈旧数据不会导致真正的问题,也许下次再担心。
其次,如果你想要“read-your-own-writes-between-requests”行为,你可以使用相同的标头来实现。当您设置fly-replay为 writer 时,请在用户会话中设置时间戳,在此期间您可以快速重播所有请求。HTTP 很便宜!
第三,您可以模拟同步复制。实际同步复制在跨地域集群上效果不佳,但 Postgres 确实让您看到查询副本的新鲜度。我们运行这些类型的查询以进行健康检查。您可以在您的应用程序中构建一些逻辑来检查用户区域上的复制延迟并延迟 HTTP 响应,直到它准备就绪。
 
黑客新闻网友讨论:
这种方法是构建全球分布式 postgres 的最无聊的方法
 
HTTP 很便宜!延迟很昂贵,但我们所有的 HTTP 诡计都发生在我们的代理和用户 VM 之间。