如何利用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
相关产品推荐
相关产品推荐

