0.0
No commit activity in last 3 years
No release in over 3 years
ActiveRecord::Migration utility for creating partitions in PostgreSQL.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 1.14
~> 5.0
>= 0
~> 10.0
 Project Readme

PgPartitions

Partitioning postgres takes some doing. PgPartitions adds methods to your migrations to help you manage them.

Installation

Add this line to your application's Gemfile:

gem 'pg_partitions'

And then execute:

$ bundle

Usage

Imagine you have a comments table with millions of rows and your queries are starting to be a bit slow. Postgres partitioning allows yo to divide your comments table into smaller tables.

In a migration, you'll first need to include PgPartitions.

class PartitionComments < ActiveRecord::Migration[5.1]
  include PgPartitions

  def change
    # ...
  end
end

Let's assume we have a column called year that stores the year the comment was created. We can partition our table based on the value of that column:

add_partition :comments, :comments_2016, check: 'year = 2016'
add_partition :comments, :comments_2017, check: 'year = 2017'

After we create our partitions, the query plan is going to change a little bit:

Comment.all.explain
=> EXPLAIN for: SELECT "comments".* FROM "comments"
                               QUERY PLAN
------------------------------------------------------------------------
 Append  (cost=0.00..60.80 rows=4081 width=12)
   ->  Seq Scan on comments  (cost=0.00..0.00 rows=1 width=12)
   ->  Seq Scan on comments_2016  (cost=0.00..30.40 rows=2040 width=12)
   ->  Seq Scan on comments_2017  (cost=0.00..30.40 rows=2040 width=12)

See how it's querying our partitions in addition to the parent table? Now, watch what happens when we put a WHERE condition on the year column:

Comment.where(year: 2016).explain
=> EXPLAIN for: SELECT "comments".* FROM "comments" WHERE "comments"."year" = $1 [["year", 2016]]
                              QUERY PLAN
----------------------------------------------------------------------
 Append  (cost=0.00..35.50 rows=11 width=12)
   ->  Seq Scan on comments  (cost=0.00..0.00 rows=1 width=12)
         Filter: (year = 2016)
   ->  Seq Scan on comments_2016  (cost=0.00..35.50 rows=10 width=12)
         Filter: (year = 2016)

Notice how it never looked at the comments_2017 table? That's the magic of partitions.

Now, there's one remaining issue. When we insert data into the comments table, we need it to route to be inserted into a partition instead of the actual table. For that, we can create a trigger:

add_partition_trigger :comments, :comments_by_year, [
  { if:    'NEW.year = 2016', insert: :comments_2016 },
  { elsif: 'NEW.year = 2017', insert: :comments_2017 },
  { else:  "RAISE EXECEPTION 'comments_by_year recieived an unexpected value: %', NEW.year;" }
]

If the new record has a year of 2016, it'll be inserted into the comments_2016 table. If the year is 2017, it'll be inserted into the comments_2017 table. Otherwise, the trigger will throw an error.

Now, imagine a year goes by and you need to add another partition for 2018. You'll need to add the partition and update the trigger:

add_partition :comments, :comments_2018, check: 'NEW.year = 2018'

update_partition_trigger :comments, :comments_by_year, [
  { if:    'NEW.year = 2016', insert: :comments_2016 },
  { elsif: 'NEW.year = 2017', insert: :comments_2017 },
  { elsif: 'NEW.year = 2018', insert: :comments_2018 },
  { else:  "RAISE EXECEPTION 'comments_by_year recieived an unexpected value: %', NEW.year;" }
]

Caveats

  • You'll have to set config.active_record.schema_format = :sql. PgPartition doesn't support the use of schema.rb.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/rzane/pg_partitions.

License

The gem is available as open source under the terms of the MIT License.