You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails 4.1如何同时向两个PostgreSQL数据库发送写入请求?

解决Rails 4.1应用双写数据到两个PostgreSQL实例的方案

针对你的场景——需要在数据迁移期间让Rails应用同时将所有写入操作发送到老、新两个PostgreSQL实例,且不需要升级Rails 4.1版本,我整理了几个在ActiveRecord层面实现的可行方案,都是经过实践验证的:

1. 先配置双数据库连接

首先在config/database.yml中配置两个数据库连接,分别对应旧库(主业务库)和新库:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5

production:
  primary:
    <<: *default
    database: old_db_production
    username: <%= ENV['OLD_DB_USER'] %>
    password: <%= ENV['OLD_DB_PASSWORD'] %>
    host: <%= ENV['OLD_DB_HOST'] %>
  new_db:
    <<: *default
    database: new_db_production
    username: <%= ENV['NEW_DB_USER'] %>
    password: <%= ENV['NEW_DB_PASSWORD'] %>
    host: <%= ENV['NEW_DB_HOST'] %>

开发、测试环境可以同理配置,方便本地验证双写逻辑。

2. 实现ActiveRecord双写补丁

我们可以给ActiveRecord::Base注入一个模块,重写所有写入相关的方法,让它们在执行主库操作后自动同步执行新库的操作。这种方式不需要修改业务代码,侵入性极低。

首先创建lib/dual_write.rb文件:

module DualWrite
  # 实例方法:处理单条记录的保存、更新、删除
  def save(*)
    main_result = super
    sync_to_new_db { super }
    main_result
  end

  def save!(*)
    main_result = super
    sync_to_new_db { super }
    main_result
  end

  def update(*)
    main_result = super
    sync_to_new_db { super }
    main_result
  end

  def update!(*)
    main_result = super
    sync_to_new_db { super }
    main_result
  end

  def destroy(*)
    main_result = super
    sync_to_new_db { super }
    main_result
  end

  # 类方法:处理批量创建、更新、删除
  module ClassMethods
    def create(*)
      record = super
      sync_to_new_db { super }
      record
    end

    def create!(*)
      record = super
      sync_to_new_db { super }
      record
    end

    def update_all(updates)
      main_result = super
      sync_to_new_db { super(updates) }
      main_result
    end

    def delete_all(conditions = nil)
      main_result = super
      sync_to_new_db { super(conditions) }
      main_result
    end
  end

  def self.included(base)
    base.extend(ClassMethods)
  end

  private

  def sync_to_new_db
    # 切换到新数据库连接
    new_db_conn = self.class.establish_connection(:new_db)
    self.class.connection = new_db_conn
    # 如果主库用了事务,这里也建议包裹事务保证一致性
    ActiveRecord::Base.transaction { yield }
  rescue StandardError => e
    # 关键:捕获新库异常,避免影响主业务流程
    Rails.logger.error "双写新库失败: #{e.message}"
    Rails.logger.error e.backtrace.join("\n")
  ensure
    # 切回主数据库连接,保证后续操作正常
    self.class.connection = self.class.establish_connection(:primary)
  end
end

然后在config/initializers/dual_write.rb中加载这个模块:

require 'dual_write'

ActiveRecord::Base.include(DualWrite)

3. 关键注意事项

  • 事务一致性:如果业务代码依赖事务,一定要在新库操作中也包裹事务,避免主库提交成功但新库部分写入失败的情况。
  • 性能影响:双写会增加单次写入的耗时,建议在 staging 环境先做性能测试;如果性能压力大,可以考虑用异步队列(比如Sidekiq)处理新库写入,但要注意最终一致性的校验。
  • 数据校验:迁移期间定期校验新旧库的数据一致性,比如对比表行数、随机抽样检查记录,确保没有遗漏或错误。
  • 查询逻辑:按照你的需求,查询操作默认走主库即可,不需要修改;如果需要验证新库数据,可以单独写查询方法指定新库连接。

备选方案:驱动层面代理

如果想在驱动层面实现,可以自定义PostgreSQL代理驱动,在发送SQL命令时同时转发到两个数据库。不过这种方式复杂度较高,需要处理连接池、事务同步等问题,灵活性不如ActiveRecord层面的方案,因此更推荐上面的补丁方案。

内容的提问来源于stack exchange,提问作者Vaibhav Kaushal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:45:36