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

如何利用Rails 7.2与PostgreSQL创建分区表?

Rails 7.2 + PostgreSQL 范围分区表(复合主键原生实现)

1. 创建分区父表(主表)

生成迁移文件,直接定义包含id和分区键(比如created_at)的复合主键,同时指定PostgreSQL范围分区规则:

class CreateEvents < ActiveRecord::Migration[7.2]
  def change
    # 禁用自动生成单一主键,手动定义复合主键
    create_table :events, id: false, force: :cascade do |t|
      t.bigint :id, null: false
      t.timestamp :created_at, null: false
      # 业务字段示例
      t.string :event_type
      t.jsonb :payload

      # 声明复合主键
      t.primary_key [:id, :created_at]
    end

    # 配置范围分区,以created_at为分区键
    execute <<~SQL
      ALTER TABLE events PARTITION BY RANGE (created_at);
    SQL
  end
end

2. 配置ActiveRecord模型

利用Rails 7+原生复合主键支持,无需任何补丁:

class Event < ApplicationRecord
  # 指定复合主键,顺序需与迁移文件一致
  self.primary_keys = [:id, :created_at]

  # 可选:强制查询携带分区键,避免全分区扫描
  default_scope -> { where.not(created_at: nil) }
end

3. 创建分区表

可手动创建历史分区,或编写脚本自动生成未来分区(比如按月):

手动创建单月分区示例

CREATE TABLE events_202410 PARTITION OF events
FOR VALUES FROM ('2024-10-01') TO ('2024-11-01');

-- 可选:为分区单独添加针对性索引(主表索引会自动继承)
CREATE INDEX idx_events_202410_payload ON events_202410 USING gin (payload);

自动创建分区的Ruby方法

def create_monthly_partition(year, month)
  start_date = Date.new(year, month, 1)
  end_date = start_date.next_month
  partition_name = "events_#{start_date.strftime('%Y%m')}"

  execute <<~SQL
    CREATE TABLE IF NOT EXISTS #{partition_name} PARTITION OF events
    FOR VALUES FROM ('#{start_date}') TO ('#{end_date}');
  SQL
end

可将此方法封装为Rake任务,提前1-2个月创建未来分区,避免写入时无对应分区报错。

4. 数据写入与查询

原生支持所有ActiveRecord操作,无需额外处理:

# 写入自动路由到对应分区
event = Event.new(
  id: SecureRandom.random_number(10**18),
  created_at: Time.current,
  event_type: 'user_login',
  payload: {user_id: 123}
)
event.save!

# 查询必须携带分区键范围,确保只扫描目标分区
Event.where(created_at: 1.week.ago..Time.current)
     .where(event_type: 'user_login')

5. 定时删除旧分区

编写Rake任务定期清理过期分区(比如删除3个月前的分区):

namespace :partitions do
  desc 'Drop old event partitions'
  task drop_old: :environment do
    cutoff_date = Date.today - 3.months
    partition_prefix = 'events_'

    # 查询符合删除条件的分区
    partitions = ActiveRecord::Base.connection.execute <<~SQL
      SELECT tablename FROM pg_tables
      WHERE tablename LIKE '#{partition_prefix}%'
      AND to_date(substring(tablename from '#{partition_prefix}(\\d{6})'), 'YYYYMM') < '#{cutoff_date.strftime('%Y%m')}';
    SQL

    partitions.each do |part|
      ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS #{part['tablename']};"
      puts "Dropped partition: #{part['tablename']}"
    end
  end
end

通过whenever或Rails内置定时任务,每月执行一次该任务即可。

关键注意事项

  • 所有写入、查询必须携带分区键(如created_at),避免触发全分区扫描。
  • 复合主键的顺序必须与迁移文件、模型配置完全一致。
  • 无需旧版Rails的猴子补丁或变通方案,Rails 7.2原生支持复合主键的CRUD操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 20:22:38