Help us understand the problem. What is going on with this article?

Rails+Postgresql9.X+partition対応の実装時考慮

More than 3 years have passed since last update.

前提

Rails 4.2.6
ActiveRecord 4.2.6
activerecord-import 0.15.0
paranoia 2.1.5
PostgreSQL 9.5
※古くてごめんなさい

個人的な結論

よほどの事情がなければRailsのアプリケーションでDB(PostgreSQL前提)のpartitionは切らないほうが良いです。
どうしても切らなければならない場合、CRUD全ての処理で考慮が必要になります。
そもそも億件超えるような要件であればRails以外を選択する、DBはAurora検討するなど、無理やり頑張らないほうが良いと思いました。

CRUD各処理における実装考慮点

以下は考慮点なので、実装例までは掲載していません。
細かい実装は要件しだいなので、載せるのが厳しいかと思ってのことです。

find/find_by/where

一番オーソドックスなパターン
各検索メソッドの前にパーティションキーを充てるのみで対応可能

OK
User.where(partition_key: value).find(1)
User.where(partition_key: value).find_by(id: 1)
User.where(partition_key: value, id: 1).first

build

※後で追記したいです

destroy/destroy_all(paranoia前提)

初めに結論ですが、以下のメソッドにパッチを充てないと対応できません。
※もしくはparanoiaにパッチ充てるか
ActiveRecord::Persistence.ClassMethods#update_columns

例えば以下のように記載しても対応されません。

NG
user = User.where(partition_key: value).find(1)
user.destroy

users = User.where(partition_key: value, name: '田中')
users.destroy_all

実装を追うとわかるのですが、updateが実行される前に必ずunscopeされますし、何よりActiveRecordがupdate時にはサロゲートキーしかレコード条件に当ててくれません。

ActiveRecord/Persistence.ClassMethods#update_columns
    def update_columns(attributes)
      raise ActiveRecordError, "cannot update a new record" if new_record?
      raise ActiveRecordError, "cannot update a destroyed record" if destroyed?

      attributes.each_key do |key|
        verify_readonly_attribute(key.to_s)
      end

      updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)

      attributes.each do |k, v|
        raw_write_attribute(k, v)
      end

      updated_count == 1
    end

delete(使っていないので未検証・対応は必須)

物理削除実装していないのでわかりませんが、たぶんこちらもサロゲートキーしか当てていないはずなので、パッチが必要になると思います。

joins/eager_load

join先テーブルのパーティションキーをscopeに含めれば対応完了です。

save

以下参照
https://qiita.com/ya-mada/items/399d26975a276111bd3e

update

update_columns同様unscopedされてた上でidのみを当てているため、defult scopeも無意味です。
したがってパッチを充てないと厳しいです。

ActiveRecord/Relation#_update_record
    def _update_record(values, id, id_was) # :nodoc:
      substitutes, binds = substitute_values values

      scope = @klass.unscoped

      if @klass.finder_needs_type_condition?
        scope.unscope!(where: @klass.inheritance_column)
      end

      relation = scope.where(@klass.primary_key => (id_was || id))
      bvs = binds + relation.bind_values
      um = relation
        .arel
        .compile_update(substitutes, @klass.primary_key)

      @klass.connection.update(
        um,
        'SQL',
        bvs,
      )
    end

bulk insert(active record import)

基本的に考慮は不要です。
ただし、公式ページにある以下の例、has_manyのパターンを一括でimportするオプションは使えません。
背景としては、recursiveも結局RETURNINGに依存している為となります。
※partitionを切ったテーブルではRETURNINGを使えません。

import
Book.import books, recursive: true

Postgresql10.Xについて

partitionの実装方式変っちゃいます。

今回作りこんだ処理でパーティションキーが適切に当たるかを検証したいです。

ya-mada
future
ITを武器とした課題解決型のコンサルティングサービスを提供します
http://future-architect.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした