Implement Materialized views PostgreSQL + Rails

Today I would like to talk about Materialized views (henceforth referred as matviews) and how to add it to an Rails app.

What is Matviews?

From wikipedia,

A materialized view is a database object that contains the results of a query.

Basically, matviews are similar to database views except they are stored in disk and updated periodically. Matviews stores the result of an query into a table like structure of its own, from which the data can be queried on. We can’t add or delete rows from it but rest it behaves as an actual table.

Also, when the data the matviews is created upon changes the matviews need to be refreshed before being available.

At the time of this writing, matviews is natively available in Oracle DB, PostgreSQL, Sybase, IBM DB2, Microsoft SQL Server. MySQL doesn’t provide native support for matviews unfortunately, but there are open source alternatives to it.

Matviews in PostgreSQL

Matviews was introduced in postgres version 9.3, and in version 9.4 they introduced an option to refresh the data concurrently.

The syntax for creating a matview in postgres in uncannily similar to how you’ll create a table.

CREATE MATERIALIZED VIEW ex_matview AS SELECT col1, col2 FROM mytable WHERE col3 = condition;

Enough intro, let’s get started on how to add an matview to an existing rails app.

Matviews in Rails

For our simpler example, let’s take we have a table called sales that has the inventory of all the items sold on our website with 2M+ rows on it and in the following schema,

id
item price sold_date order_code
1 Book
23 2015-06-01 17:06:29 UTC AVK21YO
2 Guitar 136
2015-03-23 13:12:03 UTC BJ24GTS

And, we are regularly performing a query for our admin site to show how much books accounted for sale every day. We can use the below query for it

Sale.where(item: 'Book').group_by('DATE TRUNC('day', sold_date)').sum(:price)

Now, this is an expensive query though you add indexes to it. It is a good candidate for a matview. Start by adding a migration.

rails g migration AddMatViewsToSale

and add below lines to your migration file,

now, bin/rake db:migrate and voila! the matviews are created. Now, it won’t be available on schema file since it’s not a table.

Creating a Model

One awesome thing by using matviews with Rails is we can create Models for it. Now, that the matviews are created we can create a model, SalesMatview in our case.

Set the table name as sales_matview using self.table_name and set the model to readonly by adding the below method to it,

def readonly? true end

and a self.refresh method with below line in it.

ActiveRecord::Base.connection.execute(‘REFRESH MATERIALIZED VIEW sales_matview’)

We’re all set to use the matview we’ve just created. You can query from it like how you’ll do on a normal ActiveRecord query. Now, our previous query would become like

SalesMatview.where(item: 'Book').select(:date, :amount)

The queries are similar but you’ll notice an big difference on how these two performs. Give it a try and see it for yourself.

And to refresh the matview, we can simply call the refresh method we created.

SalesMatview.refresh

We can add it to a rake task and schedule it to perform periodically. For the refreshes that are taking too much time there is an option in Postgres to perform them concurrently, just change the refresh code to

REFRESH MATERIALIZED VIEW CONCURRENTLY sales_matview

but keep in mind, a unique key will be required to perform concurrent refreshes.

Give it a try and let me know in comments what do you think of it. See you in next post.

Credits to: http://www.postgresql.org/docs/9.4/static/rules-materializedviews.html http://en.wikipedia.org/wiki/Materialized_view

*****
Written by Vinoth kumar on 01 June 2015