你已经花费了大量精力,使你的 Rails 应用程序尽可能地快速:每个查询都经过优化,视图被缓存,N+1 查询问题已被修复。
然而,唯一无法解决的问题就是服务器与用户之间的光速限制。
请查看以下表格,它展示了一个部署于美国东部的应用单次请求所增加的网络延迟。如果用户靠近服务器,运行效果良好,但离服务器越远,效果就越差。

位置到美国东部应用的延迟
北加州52ms
巴黎83ms
法兰克福92ms
新加坡214ms
开普敦231ms

如果我们能够将 Rails 应用程序部署到每个用户所在的地方,那不是很棒吗?如今,通过 Fly.io、Heroku 和 Render 等提供商实现应用服务器的全球分布变得非常简单。业内人士称这种方式为部署到“边缘”。
即使如此,我们仍然有一个主要问题需要解决:数据库。数据库也需要支持跨区域部署。
如果我们的应用程序运行在新加坡,但数据库位于美国东部,那么每个数据库查询都会产生大约 200ms 的延迟。

使用 Rails 的多区域数据库

为了实现应用的全球部署,我们必须将数据与应用程序共同定位。
这意味着我们需要做两件事情:

  1. 在应用程序运行的区域设置数据库副本
  2. 使 Rails 应用程序能够从最近的副本读取数据

我们的最终目标是让 Rails 应用程序将所有读取操作发送到最近的数据库副本,同时确保所有写入操作仍然指向主数据库。

设置数据库副本

如果尚未注册 PlanetScale 账户,请先注册。启动一个新的数据库,并按照我们的 Rails 快速入门教程连接数据库。一旦数据库与 Rails 应用程序完成连接,就可以开始配置以支持多区域部署了。
在 PlanetScale 中,我们可以在全球范围内设置只读副本。导航到数据库的主分支,点击底部的“添加区域”以创建副本,选择要添加的区域并获取连接凭证。
PlanetScale 会在选择的区域设置一个副本,并在主区域写入数据时自动保持同步。
为 PlanetScale 数据库添加区域

只读数据库连接

现在只读区域已在 PlanetScale 上配置完毕,您需要在应用程序中设置一个新的只读连接到副本。
修改 database.yml 文件,使其同时包含主数据库和只读副本连接:

default: &default
  adapter: trilogy
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock

development:
  primary:
    <<: *default
    database: multi_region_rails_development
  primary_replica:
    <<: *default
    database: multi_region_rails_development
    replica: true

test:
  primary:
    <<: *default
    database: multi_region_rails_test
  primary_replica:
    <<: *default
    database: multi_region_rails_test
    replica: true

application_record.rb 文件中添加以下内容:

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class

  connects_to database: { writing: :primary, reading: :primary_replica }
end

一旦 Rails 识别了副本连接,您可以通过包裹查询代码的块 connected_to(role: :reading) 手动查询副本:

ActiveRecord::Base.connected_to(role: :reading) do
  books = Book.where(author: "Taylor")
  # 此块中的所有代码将连接到副本
end

自动连接切换

手动包裹每个读取查询太过繁琐。Rails 提供了一种更好的方式:自动连接切换。它允许 Rails 根据需要自动切换主数据库与副本连接。所有写操作将指向主数据库,而读操作将命中副本。
要设置此功能,运行以下命令:

bin/rails g active_record:multi_db

然后在 application.rb 文件中取消注释以下内容:

Rails.application.configure do
  config.active_record.database_selector = { delay: 2.seconds }
  config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
  config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
end

注意这一行:config.active_record.database_selector = { delay: 2.seconds }。它是支持应用程序读取自身写入的关键设置。

复制延迟与读取自身写入

大多数 Rails 应用的网络请求为 GET 请求。这些请求从数据库读取数据。
POST/PUT/PATCH 和 DELETE 请求则更新应用数据。在使用多个数据库连接时,复制延迟(Replication Lag)是一个常见问题。
使用数据库副本时,主数据库的数据写入到副本总会有一段短暂延迟,这就是复制延迟。这种延迟会根据主数据库的繁忙程度而变化。
当用户向数据库写入数据后立即尝试从副本读取相同数据时,可能会因为数据尚未复制到副本而导致错误响应。
为了解决这个问题,Rails 有一个中间件,它会在每次写入后自动设置一个 2 秒的 cookie。该 cookie 存在期间,Rails 会将所有读操作指向主数据库,而不是副本。

连接最近的数据库副本

现在应用能够连接到副本,我们需要配置应用以选择性地连接到最近的副本,以享受低延迟的好处。
为此,我们需要根据 Rails 应用程序的部署位置指定要使用的凭证集。在这个示例中,我们将连接详细信息存储在 Rails 的凭证中:

<%
  region = ENV["APP_REGION"]

  region_replica_mapping = {
      "fra" => Rails.application.credentials.planetscale_fra,
      "gra" => Rails.application.credentials.planetscale_gra
  }

  db_replica_creds = region_replica_mapping[region] || Rails.application.credentials.planetscale
%>

production:
  primary:
    <<: *default
    username: <%= Rails.application.credentials.planetscale&.fetch(:username) %>
    password: <%= Rails.application.credentials.planetscale&.fetch(:password) %>
    database: <%= Rails.application.credentials.planetscale&.fetch(:database) %>
    host: <%= Rails.application.credentials.planetscale&.fetch(:host) %>
    ssl_mode: <%= Trilogy::SSL_VERIFY_IDENTITY %>
  primary_replica:
    <<: *default
    username: <%= db_replica_creds.fetch(:username) %>
    password: <%= db_replica_creds.fetch(:password) %>
    database: <%= db_replica_creds.fetch(:database) %>
    host: <%= db_replica_creds.fetch(:host) %>
    ssl_mode: <%= Trilogy::SSL_VERIFY_IDENTITY %>
    replica: true

完成后,我们的全球部署应用可以从全球部署的数据库读写数据。这会显著加快同区域用户的 GET 请求响应速度,同时所有写操作仍然指向主数据库。



使用 PlanetScale 构建多区域 Rails 应用程序插图

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:http://www.choupangxia.com/2025/09/10/planetscale-rails-application/