• Stars
    star
    308
  • Rank 135,712 (Top 3 %)
  • Language
    Ruby
  • License
    Apache License 2.0
  • Created over 2 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Blazing fast pagination for ActiveRecord with deferred joins ⚡️

FastPage by PlanetScale

FastPage applies the MySQL "deferred join" optimization to your ActiveRecord offset/limit queries.⚡️

See on RubyGems

Usage

Add fast_page to your Gemfile.

gem 'fast_page'

You can then use the fast_page method on any ActiveRecord::Relation that is using offset/limit.

Example

Here is a slow pagination query:

Post.all.order(created_at: :desc).limit(25).offset(100)
# Post Load (1228.7ms)  SELECT `posts`.* FROM `posts` ORDER BY `posts`.`created_at` DESC LIMIT 25 OFFSET 100

Add .fast_page to your slow pagination query. It breaks it up into two, much faster queries.

Post.all.order(created_at: :desc).limit(25).offset(100).fast_page
# Post Pluck (456.9ms)  SELECT `posts`.`id` FROM `posts` ORDER BY `posts`.`created_at` DESC LIMIT 25 OFFSET 100 
# Post Load (0.4ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1271528, 1271527, 1271526, 1271525, 1271524, 1271523, 1271522, 1271521, 1271520, 1271519, 1271518, 1271517, 1271516, 1271515, 1271514, 1271512, 1271513, 1271511, 1271510, 1271509, 1271508, 1271507, 1271506, 1271505, 1271504) ORDER BY `posts`.`created_at` DESC

Benchmarks

We wanted to see just how much faster using the deferred join could be. We took a table with about ~1 million records in it and benchmarked the standard ActiveRecord offset/limit query vs the query with FastPage.

Here is the query:

AuditLogEvent.page(num).per(100).where(owner: org).order(created_at: :desc)

Both owner and created_at are indexed.

Graph of FastPage vs standard ActiveRecord performance

As you can see in the chart above, it's significantly faster the further into the table we paginate.

Compatible pagination libraries

FastPage has been tested and works with these existing popular pagination gems. If you try it with any other gems, please let us know!

Kaminari

Add .fast_page to the end of your existing Kaminari pagination queries.

Post.all.page(5).per(25).fast_page

Pagy

In any controller that you want to use fast_page, add the following method. This will modify the query Pagy uses when retrieving the records.

def pagy_get_items(collection, pagy)
  collection.offset(pagy.offset).limit(pagy.items).fast_page
end

How this works

The most common form of pagination is implemented using LIMIT and OFFSET.

In this example, each page returns 50 blog posts. For the first page, we grab the first 50 posts. On the 2nd page we grab 100 posts and throw away the first 50. As the OFFSET increases, each additional page becomes more expensive for the database to serve.

-- Page 1
SELECT * FROM posts ORDER BY created_at DESC LIMIT 50;
-- Page 2
SELECT * FROM posts ORDER BY created_at DESC LIMIT 50 OFFSET 50;
-- Page 3
SELECT * FROM posts ORDER BY created_at DESC LIMIT 50 OFFSET 100;

This method of pagination works well until you have a large number of records. The later pages become very expensive to serve. Because of this, applications will often have to limit the maximum number of pages they allow users to view or swap to cursor based pagination.

Deferred join technique

High Performance MySQL recommends using a "deferred join" to increase the efficiency of LIMIT/OFFSET pagination for large tables.

SELECT * FROM posts 
INNER JOIN(select id from posts ORDER BY created_at DESC LIMIT 50 OFFSET 10000) 
AS lim USING(id);

Notice that we first select the ID of all the rows we want to show, then the data for those rows. This technique works "because it lets the server examine as little data as possible in an index without accessing rows."

The FastPage gem makes it easy to apply this optimization to any ActiveRecord::Relation using offset/limit.

To learn more on how this works, check out this blog post: Efficient Pagination Using Deferred Joins

When should I use this?

fast_page works best on pagination queries that include an ORDER BY. It becomes more effective as the page number increases. You should test it on your application's data to see how it improves your query times.

We have only tested fast_page with MySQL. It likely does not produce the same results for other databases. If you test it, please let us know!

Because fast_page runs 2 queries instead of 1, it is very likely a bit slower for early pages. The benefits begin as the user gets into deeper pages. It's worth testing to see at which page your application gets faster from using fast_page and only applying to your queries then.

posts = Post.all.page(params[:page]).per(25)
# Use fast page after page 5, improves query performance
posts = posts.fast_page if params[:page] > 5

Thank you ❤️

This gem was inspired by Hammerstone's fast-paginate for Laravel and @aarondfrancis's excellent blog post: Efficient Pagination Using Deferred Joins. We were so impressed with the results, we had to bring this to Rails as well.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/planetscale/fast_page. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the Apache-2.0 license.

Code of Conduct

Everyone interacting in the FastPage project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

More Repositories

1

beam

A simple message board for your organization or project
TypeScript
2,052
star
2

database-js

A Fetch API-compatible PlanetScale database driver
TypeScript
1,166
star
3

vtprotobuf

A Protocol Buffers compiler that generates optimized marshaling & unmarshaling Go code for ProtoBuf APIv2
Go
867
star
4

cli

The CLI for PlanetScale Database
Go
603
star
5

vitess-operator

Kubernetes Operator for Vitess
Go
303
star
6

nextjs-planetscale-starter

A Next.js starter app with NextAuth.js (Auth), Prisma (ORM), and PlanetScale (database), ready to be deployed on Netlify
TypeScript
257
star
7

nextjs-starter

Next.js starter application using Prisma to connect to PlanetScale
JavaScript
136
star
8

f1-championship-stats

🏎
TypeScript
130
star
9

planetscale-go

Go client library to access the PlanetScale API
Go
125
star
10

docs

PlanetScale documentation
Shell
93
star
11

schemadiff

Declarative schema diffing, normalization, validation and manipulation via command line
Go
91
star
12

discussion

78
star
13

issues-calendar-for-github-projects

A tool to view GitHub Project Issues in a calendar view.
TypeScript
77
star
14

planetscale_rails

Make Rails schema migrations easy with PlanetScale
Ruby
62
star
15

planetscale-node

PlanetScale SQL Proxy NPM Module
TypeScript
55
star
16

sql-proxy

SQL Proxy for PlanetScale DB
Go
53
star
17

connection-examples

A collection of PlanetScale connection examples
Elixir
51
star
18

activerecord-sql_commenter

Rails query logging compatible with sqlcommenter
Ruby
43
star
19

nextjs-conf-2021

The code from "Databases as Code with PlanetScale and Prisma" talk at Next.js Conf 2021
JavaScript
34
star
20

vitess-releases

Vitess releases
Shell
30
star
21

ghcommit-action

GitHub Action to commit files to a git branch using the ghcommit utility
Shell
30
star
22

planetscale-ruby

Ruby client for PlanetScale databases
Ruby
30
star
23

ghcommit

Use GitHub's GraphQL API to commit files to a GitHub repository.
Go
28
star
24

express-example

Example Express.js app connecting to PlanetScale
JavaScript
28
star
25

setup-pscale-action

Setup the PlanetScale CLI for GitHub Actions
TypeScript
20
star
26

boost-beta

Welcome to the PlanetScale Boost Private Beta
17
star
27

create-branch-action

A GitHub Action that creates a new branch of your PlanetScale database
Shell
12
star
28

planetpets

A demo application that uses PlanetScale OAuth v2 to access users' databases and organizations.
TypeScript
11
star
29

planetscale-nestjs

Starter application demonstrating how to connect a NestJS API to a PlanetScale MySQL database
TypeScript
11
star
30

database-js-starter

A sample Node.js application that uses the database-js package.
JavaScript
11
star
31

figma-diagram-generator

Code to go along with the figma DB diagram creator video
TypeScript
11
star
32

airbyte-source

Airbyte source for PlanetScale databases
Go
9
star
33

django_psdb_engine

Disable foreign key constraints in Django
Python
9
star
34

deploy-deploy-request-action

A GitHub Action that deploys an existing deploy request of your PlanetScale database
Shell
8
star
35

laravel-example

Sample application showing how to integrate Laravel with PlanetScale
PHP
7
star
36

cloudflare-workers-quickstart

A sample repository demonstrating how to use PlanetScale with CloudFlare Workers.
JavaScript
7
star
37

laravel-crud-mysql

JavaScript
6
star
38

vitess-framework-testing

Validating Vitess across clients and frameworks.
Ruby
6
star
39

singer-tap

Singer.io tap for extracting PlanetScale data
Go
6
star
40

mysql-dds

Loadable MySQL functions for working with DDSketches
C++
6
star
41

terraform-provider-planetscale

Terraform provider for PlanetScale
Go
6
star
42

django-example

Connect a sample Django application to PlanetScale
Python
6
star
43

sysbench

A fork of sysbench to add Vitess specific Modifications
C
6
star
44

10-minute-app

Set up a stateful app fast
JavaScript
5
star
45

vercel-integration-example

JavaScript
5
star
46

go-planetscale-products

Go
5
star
47

create-deploy-request-action

A GitHub Action that creates a new deploy request for your PlanetScale database
Dockerfile
5
star
48

psdbproxy

MySQL proxy for local connections to a PlanetScale database over HTTP/2
Go
5
star
49

golang-example

Go
4
star
50

integrations

planetscale integration examples
JavaScript
4
star
51

fivetran-source

PlanetScale Source connector for FiveTran
Go
4
star
52

scoop-bucket

Scoop bucket for PlanetScale CLI binaries.
4
star
53

create-branch-password-action

A GitHub Action that creates a new password for your PlanetScale database branch
Shell
4
star
54

pscale-workflow-helper-scripts

Shell
4
star
55

vault-gcp-creds-buildkite-plugin

Retrieve time-limited oauth2 access token for an impersonated account from a Hashicorp Vault GCP Secrets Backend
Shell
4
star
56

heroku-buildpack-planetscale

A Heroku Buildpack for adding Planetscale CLI to your project
Ruby
3
star
57

golang-example-gin

Go
3
star
58

symfony-example

Connect a sample Symfony app to PlanetScale
PHP
3
star
59

log

🪵 📓 PlanetScale's opinionated structured logging library
Go
3
star
60

homebrew-tap

Homebrew repository for the pscale CLI
Ruby
3
star
61

php-example

Connect a sample PHP project to a PlanetScale database.
PHP
3
star
62

mysql-for-python-developers

Python
3
star
63

ps-prechecks

Shell
3
star
64

kubeconform-buildkite-plugin

Run kubeconform against your Kubernetes configurations
Shell
2
star
65

laravel-caching

Sample repo for Laravel caching blog post
PHP
2
star
66

k8s-demo

A simple demo of Vitess on Kubernetes
Python
2
star
67

psdb

Go
2
star
68

transmission

Writes data to MySQL. Quickly.
Rust
2
star
69

cloudranger

Go library for mapping IP address ranges to cloud provider regions (currently: AWS and GCP)
Go
2
star
70

vitess-types

Protobuf types extracted from Vitess
Go
2
star
71

go-bookings-api

An API written in Go simulating a travel booking agency.
Go
1
star
72

django-react-demo

JavaScript
1
star
73

go-logkeycheck

Ensure zap log field names are consistent
Go
1
star
74

vault-oidc-auth-buildkite-plugin

Authenticate to Hashicorp Vault with Buildkite OIDC (JWT) tokens.
Shell
1
star
75

core-prettier

Core Design System shared prettier config
JavaScript
1
star
76

vault-aws-creds-buildkite-plugin

Retrieve time-limited AWS credentials from a Hashicorp Vault AWS Secrets Backend
Shell
1
star
77

aws-connection-strings-example

JavaScript
1
star
78

pargzip

Fork of https://github.com/golang/build/tree/master/pargzip
Go
1
star