readyset 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +15 -0
- data/.rubocop_airbnb.yml +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +383 -0
- data/Rakefile +12 -0
- data/config.ru +9 -0
- data/docker-compose.yml +54 -0
- data/lib/active_record/connection_adapters/readyset_adapter.rb +52 -0
- data/lib/active_record/readyset_connection_handling.rb +18 -0
- data/lib/readyset/caches.rb +20 -0
- data/lib/readyset/configuration.rb +30 -0
- data/lib/readyset/controller_extension.rb +34 -0
- data/lib/readyset/error.rb +3 -0
- data/lib/readyset/explain.rb +60 -0
- data/lib/readyset/health/healthchecker.rb +127 -0
- data/lib/readyset/health/healthchecks.rb +41 -0
- data/lib/readyset/model_extension.rb +40 -0
- data/lib/readyset/query/cached_query.rb +104 -0
- data/lib/readyset/query/proxied_query.rb +116 -0
- data/lib/readyset/query/queryable.rb +23 -0
- data/lib/readyset/query.rb +23 -0
- data/lib/readyset/railtie.rb +68 -0
- data/lib/readyset/relation_extension.rb +29 -0
- data/lib/readyset/utils/window_counter.rb +58 -0
- data/lib/readyset/version.rb +5 -0
- data/lib/readyset.rb +168 -0
- data/lib/tasks/readyset.rake +162 -0
- data/lib/templates/caches.rb.tt +11 -0
- data/readyset.gemspec +52 -0
- data/sig/readyset.rbs +4 -0
- metadata +321 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a7eff87ba2d4dfc95a623d57486cefc986da1ef725d7fd4193b3611aa86b72be
|
4
|
+
data.tar.gz: fd8ce3ee3520d4943f759c716877d5ec82e185101403316529031346aec01d7d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bf03429429fec9e83f2d7a82dd9985302865f724f59fe93612b862a78447a5a210f024dbca1235e11ac1ae28edb277b6ba0b712a770ed09a2905b78b5a4525e9
|
7
|
+
data.tar.gz: 6979425963dc743d812707ce5d2e89c871ba779c2c85de035afa8c285c68a188b25fe43991752937aff6edb01829097edbe2a46eb380c0c058b031f814be56a2
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require rails_helper
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
inherit_from:
|
2
|
+
- .rubocop_airbnb.yml
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
TargetRubyVersion: 3.2
|
6
|
+
NewCops: disable
|
7
|
+
|
8
|
+
Style/StringLiterals:
|
9
|
+
EnforcedStyle: single_quotes
|
10
|
+
Enabled: true
|
11
|
+
|
12
|
+
Style/StringLiteralsInInterpolation:
|
13
|
+
EnforcedStyle: single_quotes
|
14
|
+
Enabled: true
|
15
|
+
|
data/.rubocop_airbnb.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.2.1
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 ReadySet Technology Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,383 @@
|
|
1
|
+
# ReadySet Rails
|
2
|
+
|
3
|
+
A gem for caching with [ReadySet](https://readyset.io) within Rails applications.
|
4
|
+
|
5
|
+

|
6
|
+
[](https://github.com/readysettech/readyset-rails/issues)
|
7
|
+

|
8
|
+

|
9
|
+

|
10
|
+
[](https://join.slack.com/t/readysetcommunity/shared_invite/zt-2272gtiz4-0024xeRJUPGWlRETQrGkFw)
|
11
|
+
[](https://twitter.com/readysetio)
|
12
|
+
|
13
|
+
:star: If you find this gem useful, please consider giving us a star on GitHub! Your support helps us continue to innovate and deliver exciting new features.
|
14
|
+
|
15
|
+
## Table of Contents
|
16
|
+
- [What is ReadySet?](#what-is-readyset)
|
17
|
+
- [What does this gem do?](#what-does-this-gem-do)
|
18
|
+
- [Installing](#installing)
|
19
|
+
- [Quickstart](#quickstart)
|
20
|
+
- [Usage](#usage)
|
21
|
+
- [Getting Started with Caching](#getting-started-with-caching)
|
22
|
+
- [Query Routing in Controllers](#query-routing-in-controllers)
|
23
|
+
- [Query Routing in Models](#query-routing-in-models)
|
24
|
+
- [Cache Migrations](#cache-migrations)
|
25
|
+
- [Automatic Failover](#automatic-failover)
|
26
|
+
- [Configuration Options](#configuration-options)
|
27
|
+
- [License](#license)
|
28
|
+
|
29
|
+
## What is ReadySet?
|
30
|
+
|
31
|
+
[ReadySet](https://readyset.io) is a database acceleration engine that acts as
|
32
|
+
a read replica and implements a novel type of query caching that automatically
|
33
|
+
keeps your caches up-to-date by watching your database's replication stream.
|
34
|
+
ReadySet helps you scale your database by shedding load and reducing query
|
35
|
+
latency.
|
36
|
+
|
37
|
+
## What does this gem do?
|
38
|
+
|
39
|
+
This gem makes it easy to use ReadySet from within your Rails application by
|
40
|
+
allowing you to selectively route queries to ReadySet. The high-level features
|
41
|
+
of this gem include:
|
42
|
+
|
43
|
+
- A `Readyset.route` method that takes a block and routes to ReadySet any
|
44
|
+
queries within that block
|
45
|
+
- A controller extension that allows you to route to ReadySet all queries
|
46
|
+
that occur within specific controller actions
|
47
|
+
- A model extension that allows you to define queries to be routed to ReadySet
|
48
|
+
within the context of an existing model
|
49
|
+
- Rake tasks that allow you to easily manage cache migration on ReadySet,
|
50
|
+
ensuring that a consistent set of caches exists across all of your
|
51
|
+
environments
|
52
|
+
- Automatic failover back to your primary database in the event that your
|
53
|
+
ReadySet instance is unreachable (disabled by default)
|
54
|
+
|
55
|
+
**Note that ReadySet only guarantees support for PostgreSQL right now, so this
|
56
|
+
gem only supports PostgreSQL.**
|
57
|
+
|
58
|
+
## Installing
|
59
|
+
|
60
|
+
If you run into any trouble with the below steps, please feel free to reach
|
61
|
+
out via our [community Slack](https://join.slack.com/t/readysetcommunity/shared_invite/zt-2272gtiz4-0024xeRJUPGWlRETQrGkFw)!
|
62
|
+
|
63
|
+
1. Follow the instructions [here](https://docs.readyset.io/get-started/install-rs/postgres)
|
64
|
+
to install and run ReadySet
|
65
|
+
2. Add the following line to your Gemfile and run `bundle install`:
|
66
|
+
```sh
|
67
|
+
gem 'readyset'
|
68
|
+
```
|
69
|
+
The ReadySet Rails gem currenly supports Ruby versions >= 3.0 and Rails
|
70
|
+
versions >= 6.1.
|
71
|
+
3. Add a section to your `config/database.yml` file with ReadySet's connection
|
72
|
+
information. If you currently connect to only one database, you'll need to
|
73
|
+
move your primary database connection information to be nested under a new
|
74
|
+
key named `primary`.
|
75
|
+
```yaml
|
76
|
+
development:
|
77
|
+
primary:
|
78
|
+
# This is the connection information for your primary database
|
79
|
+
database: testdb
|
80
|
+
username: postgres
|
81
|
+
password: readyset
|
82
|
+
adapter: postgresql
|
83
|
+
port: 5432
|
84
|
+
readyset:
|
85
|
+
# This is the connection information for ReadySet
|
86
|
+
database: testdb
|
87
|
+
username: postgres
|
88
|
+
password: readyset
|
89
|
+
adapter: readyset
|
90
|
+
host: "127.0.0.1"
|
91
|
+
port: 5433
|
92
|
+
# This setting tells Rails that there's no need to run migrations or other database
|
93
|
+
# administration tasks against ReadySet
|
94
|
+
database_tasks: false
|
95
|
+
```
|
96
|
+
You can verify that ReadySet is up and your application is connected by
|
97
|
+
running `rails readyset:status`:
|
98
|
+
```sh
|
99
|
+
$ rails readyset:status
|
100
|
+
+----------------------------+------------------------+
|
101
|
+
| Database Connection | Connected |
|
102
|
+
| Connection Count | 1 |
|
103
|
+
| Snapshot Status | Completed |
|
104
|
+
| Maximum Replication Offset | (0/6DBBD78, 0/6DBBFF0) |
|
105
|
+
| Minimum Replication Offset | (0/6DBBD78, 0/6DBBFF0) |
|
106
|
+
| Last started Controller | 2024-01-17 18:49:02 |
|
107
|
+
| Last completed snapshot | 2024-01-19 15:18:02 |
|
108
|
+
| Last started replication | 2024-01-19 15:18:02 |
|
109
|
+
+----------------------------+------------------------+
|
110
|
+
```
|
111
|
+
You can also view the tables that ReadySet knows about and their statuses by
|
112
|
+
running `rails readyset:tables`:
|
113
|
+
```sh
|
114
|
+
$ rails readyset:tables
|
115
|
+
+---------------------------------+-------------+-------------+
|
116
|
+
| table | status | description |
|
117
|
+
+---------------------------------+-------------+-------------+
|
118
|
+
| "public"."posts" | Snapshotted | |
|
119
|
+
+---------------------------------+-------------+-------------+
|
120
|
+
```
|
121
|
+
4. Run `Readyset.configure` wherever you configure other gems in your
|
122
|
+
application, and set any desired configuration options:
|
123
|
+
```ruby
|
124
|
+
Readyset.configure do |config|
|
125
|
+
# Set your config options here
|
126
|
+
end
|
127
|
+
```
|
128
|
+
The list of available configuration options can be found
|
129
|
+
[here](#configuration-options).
|
130
|
+
5. FOR RAILS 7 USERS: Enable Rails's query log tags features by setting
|
131
|
+
`config.active_record.query_log_tags_enabled = true` wherever you configure
|
132
|
+
your ActiveRecord settings. This will append information to your
|
133
|
+
ActiveRecord query logs that tells you where the given query was routed (e.g.
|
134
|
+
to ReadySet or to your primary database)
|
135
|
+
|
136
|
+
## Quickstart
|
137
|
+
|
138
|
+
1. Follow the instructions above to set up ReadySet and install the gem
|
139
|
+
2. Route a query to ReadySet in your application like so:
|
140
|
+
```ruby
|
141
|
+
Readyset.route do
|
142
|
+
Post.where(user_id: user_id)
|
143
|
+
end
|
144
|
+
```
|
145
|
+
3. Start up your application and drive traffic through the part of your
|
146
|
+
application that invokes the query you routed in the previous step
|
147
|
+
4. If you are running Rails 7 and enabled query log tags as explained in step 5
|
148
|
+
[above](#installing), you should see an annotation next to the query in your
|
149
|
+
application logs that denotes where the query was routed. If the
|
150
|
+
`destination` tag has the value `readyset`, the query was routed to
|
151
|
+
ReadySet.
|
152
|
+
|
153
|
+
If you are running Rails 6, you can validate that the query was routed to
|
154
|
+
ReadySet by running `rails readyset:proxied_queries`. A "proxied" query is
|
155
|
+
one that was served by ReadySet but was proxied to your primary database,
|
156
|
+
since a cache for the query does not yet exist:
|
157
|
+
```sh
|
158
|
+
$ rails readyset:proxied_queries
|
159
|
+
+--------------------+-------------------------------------------------------+-------------+-------+
|
160
|
+
| id | text | supported | count |
|
161
|
+
+--------------------+-------------------------------------------------------+-------------+-------+
|
162
|
+
| q_281c5f9b8e4013bb | SELECT | yes | 1 |
|
163
|
+
| | * | | |
|
164
|
+
| | FROM | | |
|
165
|
+
| | "posts" | | |
|
166
|
+
| | WHERE | | |
|
167
|
+
| | ("user_id" = $1) | | |
|
168
|
+
+--------------------+-------------------------------------------------------+-------------+-------+
|
169
|
+
```
|
170
|
+
5. Create a cache for the query by running
|
171
|
+
`rails readyset:proxied_queries:cache_all_supported`. This will create caches for
|
172
|
+
all of the queries proxied by ReadySet that are supported to be cached. You
|
173
|
+
can verify that the expected caches were created by running
|
174
|
+
`rails readyset:caches`:
|
175
|
+
```sh
|
176
|
+
$ rails readyset:caches
|
177
|
+
+--------------------+--------------------+-------------------------------------+--------+-------+
|
178
|
+
| id | name | text | always | count |
|
179
|
+
+--------------------+--------------------+-------------------------------------+--------+-------+
|
180
|
+
| q_281c5f9b8e4013bb | q_281c5f9b8e4013bb | SELECT | false | 0 |
|
181
|
+
| | | "public"."posts"."user_id" | | |
|
182
|
+
| | | FROM | | |
|
183
|
+
| | | "public"."posts" | | |
|
184
|
+
| | | WHERE | | |
|
185
|
+
| | | ("public"."posts"."user_id" = $1) | | |
|
186
|
+
+--------------------+--------------------+-------------------------------------+--------+-------+
|
187
|
+
```
|
188
|
+
6. Drive traffic through the part of your application that invokes your cached
|
189
|
+
query. The first invocation of the query will be a cache miss, but the
|
190
|
+
second will be served from the cache. You can verify that the cache was
|
191
|
+
successfully used by looking at the `count` column in the output of
|
192
|
+
`rails readyset:caches`:
|
193
|
+
```sh
|
194
|
+
$ rails readyset:caches
|
195
|
+
+--------------------+--------------------+-------------------------------------+--------+-------+
|
196
|
+
| id | name | text | always | count |
|
197
|
+
+--------------------+--------------------+-------------------------------------+--------+-------+
|
198
|
+
| q_281c5f9b8e4013bb | q_281c5f9b8e4013bb | SELECT | false | 1 |
|
199
|
+
| | | "public"."posts"."user_id" | | |
|
200
|
+
| | | FROM | | |
|
201
|
+
| | | "public"."posts" | | |
|
202
|
+
| | | WHERE | | |
|
203
|
+
| | | ("public"."posts"."user_id" = $1) | | |
|
204
|
+
+--------------------+--------------------+-------------------------------------+--------+-------+
|
205
|
+
```
|
206
|
+
|
207
|
+
## Usage
|
208
|
+
|
209
|
+
### Getting Started with Caching
|
210
|
+
|
211
|
+
Queries in arbitrary code blocks can be routed to ReadySet using the
|
212
|
+
`Readyset.route` method like so:
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
Readyset.route do
|
216
|
+
Post.where(user_id: user_id)
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
Any queries invoked in the given block will be routed to the ReadySet instance
|
221
|
+
configured in your `config/database.yml` file; however, until a cache is created
|
222
|
+
for a particular query, invocations of that query against ReadySet will be proxied
|
223
|
+
to your database. To create a cache for a specific query, you have a few options:
|
224
|
+
|
225
|
+
- Invoke `.create_readyset_cache` directly on an ActiveRecord query in the
|
226
|
+
Rails console:
|
227
|
+
```ruby
|
228
|
+
Post.where(user_id: 1).create_readyset_cache!
|
229
|
+
```
|
230
|
+
- Create caches for all of the queries supported by ReadySet that ReadySet has
|
231
|
+
seen and proxied to your database since it last started up using the provided
|
232
|
+
Rake task:
|
233
|
+
```sh
|
234
|
+
rails readyset:proxied_queries:cache_all_supported
|
235
|
+
```
|
236
|
+
**Note:** If you route a query to ReadySet, decide you no longer want to cache
|
237
|
+
that query, and stop routing that query to ReadySet, that query will still
|
238
|
+
exist in ReadySet's list of queries that it has proxied to your database.
|
239
|
+
This means that running the above Rake task will still create a cache for that
|
240
|
+
query **even though it is no longer annotated to be routed to ReadySet in your
|
241
|
+
application code**. The list of queries ReadySet has proxied can be cleared by
|
242
|
+
restarting ReadySet or by running `rails readyset:proxied_queries:drop_all`.
|
243
|
+
- View the list of queries that ReadySet has proxied by running the following
|
244
|
+
in a Rails console:
|
245
|
+
```ruby
|
246
|
+
Readyset::Query::ProxiedQuery.all
|
247
|
+
```
|
248
|
+
You can invoke `#cache!` on the queries in this list for which you'd like to
|
249
|
+
create caches on ReadySet.
|
250
|
+
- View the list of queries that ReadySet has proxied *and* that are supported
|
251
|
+
by ReadySet to be cached by running the following:
|
252
|
+
```sh
|
253
|
+
rails readyset:proxied_queries:supported
|
254
|
+
```
|
255
|
+
Pick a query from the list that you'd like to cache, and pass the ID to the
|
256
|
+
`rails readyset:create_cache` command like so:
|
257
|
+
```sh
|
258
|
+
rails 'readyset:create_cache[your_query_id]'
|
259
|
+
```
|
260
|
+
|
261
|
+
Once a cache has been created for a particular query, it will persist on
|
262
|
+
ReadySet across restarts (although any in-memory cached data will be lost when
|
263
|
+
ReadySet goes down). You can view the list of existing caches using the provided
|
264
|
+
Rake task:
|
265
|
+
```sh
|
266
|
+
rails readyset:caches
|
267
|
+
```
|
268
|
+
To drop a given cache in the list printed by the above command, you can pass the
|
269
|
+
name of the cache to the `readyset:caches:drop` Rake task like so:
|
270
|
+
```sh
|
271
|
+
rails 'readyset:caches:drop[my_cache]'
|
272
|
+
```
|
273
|
+
You can also view the list of existing caches in an interactive form via the
|
274
|
+
Rails console:
|
275
|
+
```ruby
|
276
|
+
Readyset::Query::CachedQuery.all
|
277
|
+
```
|
278
|
+
You can invoke `#drop!` on any of the caches in this list to remove the cache
|
279
|
+
from ReadySet.
|
280
|
+
|
281
|
+
#### Query Routing in Controllers
|
282
|
+
|
283
|
+
The gem includes an extension to `ActionController` that allows you to route to
|
284
|
+
ReadySet all of the queries that occur within the context of a given controller
|
285
|
+
action:
|
286
|
+
```ruby
|
287
|
+
class PostsController < ActionController
|
288
|
+
route_to_readyset only: :show
|
289
|
+
|
290
|
+
def show
|
291
|
+
@post = Post.where(id: params[:id])
|
292
|
+
end
|
293
|
+
end
|
294
|
+
```
|
295
|
+
`route_to_readyset` takes the same parameters as Rails's
|
296
|
+
[`around_filters`](https://guides.rubyonrails.org/action_controller_overview.html#after-filters-and-around-filters).
|
297
|
+
|
298
|
+
#### Query Routing in Models
|
299
|
+
|
300
|
+
The gem also includes an extension that allows you to define queries in your
|
301
|
+
model that will be routed to ReadySet:
|
302
|
+
```ruby
|
303
|
+
class Post < ApplicationRecord
|
304
|
+
readyset_query :posts_for_user, ->(user_id) { where(user_id: user_id) }
|
305
|
+
end
|
306
|
+
```
|
307
|
+
The above example will define a `.posts_for_user` class method on the `Post`
|
308
|
+
model that invokes the query `Post.where(user_id: user_id)` against ReadySet.
|
309
|
+
**Note that other invocations of this query outside of the context of the
|
310
|
+
`.posts_for_user` method will not be routed to ReadySet.**
|
311
|
+
|
312
|
+
This feature allows you to specify which queries should be routed to ReadySet
|
313
|
+
in a centralized location and prevents the need to use `Readyset.route`
|
314
|
+
everywhere a cached query is invoked.
|
315
|
+
|
316
|
+
### Cache Migrations
|
317
|
+
|
318
|
+
Once you have a set of caches you are happy with in your development
|
319
|
+
environment, you'll need a way to easily reproduce the same set of caches in
|
320
|
+
other environments (e.g. staging, production, etc.). To facilitate this, the
|
321
|
+
gem includes a "migration" feature, that allows you to dump the current set of
|
322
|
+
caches to a "migration file" and re-create these caches using the same
|
323
|
+
migration file.
|
324
|
+
|
325
|
+
The following Rake task dumps the current set of caches to the
|
326
|
+
`db/readyset_caches.rb` file:
|
327
|
+
```sh
|
328
|
+
rails readyset:caches:dump
|
329
|
+
```
|
330
|
+
This file should be checked into version control with your application code. To
|
331
|
+
update a ReadySet instance so that its set of caches matches the caches in your
|
332
|
+
migration file:
|
333
|
+
```sh
|
334
|
+
rails readyset:caches:migrate
|
335
|
+
```
|
336
|
+
This command 1) drops any caches that exist on ReadySet that are not present in
|
337
|
+
the migration file and 2) creates any caches that are present in the migration
|
338
|
+
file that do not exist on ReadySet. To run the command for a particular Rails
|
339
|
+
environment, you can set the `RAILS_ENV` environment variable.
|
340
|
+
|
341
|
+
### Automatic Failover
|
342
|
+
|
343
|
+
To handle situations where ReadySet is unreachable for any reason, the gem
|
344
|
+
includes an automatic failover feature. The gem tracks the number of ReadySet
|
345
|
+
connection failures over a configurable window of time, and if the number of
|
346
|
+
errors exceeds the configured threshold, any queries previously being routed to
|
347
|
+
ReadySet will be routed to the primary database. A background task is started up
|
348
|
+
that periodically attempts to establish a connection to ReadySet and check its
|
349
|
+
status. When the task confirms that ReadySet is available again, it allows
|
350
|
+
queries to be routed to ReadySet again.
|
351
|
+
|
352
|
+
This feature is disabled by default. To enable it, set the
|
353
|
+
`config.failover.enabled` configuration option to `true`. You can read about the
|
354
|
+
other available configuration options [here](#configuration-options).
|
355
|
+
|
356
|
+
## Configuration Options
|
357
|
+
|
358
|
+
The gem's configuration options can be set by passing a block to
|
359
|
+
`Readyset.configure` and setting options on the yielded
|
360
|
+
`Readyset::Configuration` object. The available options are documented below.
|
361
|
+
The values below are the default values for each of the options.
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
Readyset.configure do |config|
|
365
|
+
# Whether the gem's automatic failover feature should be enabled.
|
366
|
+
config.failover.enabled = false
|
367
|
+
# Sets the interval upon which the background task will check
|
368
|
+
# ReadySet's availability after failover has occurred.
|
369
|
+
config.failover.healthcheck_interval = 5.seconds
|
370
|
+
# Sets the time window over which connection errors are counted
|
371
|
+
# when determining whether failover should occur.
|
372
|
+
config.failover.error_window_period = 1.minute
|
373
|
+
# Sets the number of errors that must occur within the configured
|
374
|
+
# error window period in order for failover to be triggered.
|
375
|
+
config.failover.error_window_size = 10
|
376
|
+
# The file in which cache migrations should be stored.
|
377
|
+
config.migration_path = File.join(Rails.root, 'db/readyset_caches.rb')
|
378
|
+
end
|
379
|
+
```
|
380
|
+
|
381
|
+
## License
|
382
|
+
|
383
|
+
[MIT License](LICENSE)
|
data/Rakefile
ADDED
data/config.ru
ADDED
data/docker-compose.yml
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
services:
|
2
|
+
readyset:
|
3
|
+
image: public.ecr.aws/readyset/readyset:latest-stable
|
4
|
+
platform: linux/amd64
|
5
|
+
ports:
|
6
|
+
# The ReadySet Adapter listen port, i.e. what your application / SQL shell connects to
|
7
|
+
- "5433:5433"
|
8
|
+
# ReadySet Prometheus metrics available at http://localhost:6034/metrics
|
9
|
+
# e.g. curl -X GET http://localhost:6034/metrics
|
10
|
+
- "6034:6034"
|
11
|
+
environment:
|
12
|
+
DEPLOYMENT_ENV: readyset_rails_test
|
13
|
+
DB_DIR: /state
|
14
|
+
QUERY_CACHING: explicit
|
15
|
+
STANDALONE: 'true'
|
16
|
+
DEPLOYMENT: docker_compose_deployment
|
17
|
+
LISTEN_ADDRESS: 0.0.0.0:5433
|
18
|
+
UPSTREAM_DB_URL: postgresql://postgres:readyset@postgres/testdb
|
19
|
+
CONTROLLER_ADDRESS: 0.0.0.0
|
20
|
+
volumes:
|
21
|
+
- "readyset:/state"
|
22
|
+
healthcheck:
|
23
|
+
test: [ "CMD", "curl", "--fail", "127.0.0.1:6034/health" ]
|
24
|
+
interval: 2s
|
25
|
+
timeout: 1s
|
26
|
+
retries: 5
|
27
|
+
start_period: 5s
|
28
|
+
depends_on:
|
29
|
+
postgres:
|
30
|
+
condition: service_healthy
|
31
|
+
postgres:
|
32
|
+
image: postgres:14
|
33
|
+
environment:
|
34
|
+
- POSTGRES_PASSWORD=readyset
|
35
|
+
- POSTGRES_DB=testdb
|
36
|
+
ports:
|
37
|
+
# The ReadySet Adapter listen port, i.e. what your application / SQL shell connects to
|
38
|
+
- "5432:5432"
|
39
|
+
expose:
|
40
|
+
- 5432
|
41
|
+
command:
|
42
|
+
- "postgres"
|
43
|
+
- "-c"
|
44
|
+
- "wal_level=logical"
|
45
|
+
healthcheck:
|
46
|
+
test: ["CMD", "pg_isready", "-U", "postgres"]
|
47
|
+
interval: 5s
|
48
|
+
timeout: 5s
|
49
|
+
retries: 12
|
50
|
+
volumes:
|
51
|
+
- postgres:/var/lib/postgresql/data
|
52
|
+
volumes:
|
53
|
+
postgres: ~
|
54
|
+
readyset: ~
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
3
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
4
|
+
require 'readyset/error'
|
5
|
+
|
6
|
+
module ActiveRecord
|
7
|
+
module ConnectionAdapters
|
8
|
+
# The ReadySet adapter is a proxy object that delegates all its methods to an inner
|
9
|
+
# PostgreSQLAdapter instance.
|
10
|
+
class ReadysetAdapter
|
11
|
+
ADAPTER_NAME = 'Readyset'.freeze
|
12
|
+
|
13
|
+
# Finds the root cause of the given error and includes the Readyset::Error module in that
|
14
|
+
# error's singleton class if the root cause was a `PG::Error`. This allows us to invoke
|
15
|
+
# `#is_a?` on the error to determine if the error came from a connection to ReadySet.
|
16
|
+
#
|
17
|
+
# @param e [Exception] the error whose cause should be annotated
|
18
|
+
# @return [void]
|
19
|
+
def self.annotate_error(e)
|
20
|
+
if e.cause
|
21
|
+
annotate_error(e.cause)
|
22
|
+
else
|
23
|
+
if e.is_a?(::PG::Error)
|
24
|
+
e.singleton_class.instance_eval do
|
25
|
+
include ::Readyset::Error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.method_missing(...)
|
34
|
+
PostgreSQLAdapter.send(...)
|
35
|
+
rescue => e
|
36
|
+
annotate_error(e)
|
37
|
+
raise e
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(pg_conn)
|
41
|
+
@inner = pg_conn
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(...)
|
45
|
+
@inner.send(...)
|
46
|
+
rescue => e
|
47
|
+
self.class.annotate_error(e)
|
48
|
+
raise e
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
# The methods in these modules are required for Rails to recognize our custom adapter
|
5
|
+
module ReadysetConnectionHandling
|
6
|
+
def readyset_adapter_class
|
7
|
+
ConnectionAdapters::ReadysetAdapter
|
8
|
+
end
|
9
|
+
|
10
|
+
def readyset_connection(config) # :nodoc:
|
11
|
+
pg_conn = postgresql_connection(config)
|
12
|
+
readyset_adapter_class.new(pg_conn)
|
13
|
+
rescue => e
|
14
|
+
readyset_adapter_class.annotate_error(e)
|
15
|
+
raise e
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Readyset
|
2
|
+
# Defines the DSL used in the gem's "migration" files. The DSL should be used by inheriting
|
3
|
+
# from this class and invoking the `.cache` class method to define new caches.
|
4
|
+
class Caches
|
5
|
+
class << self
|
6
|
+
attr_reader :caches
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.cache(always: false)
|
10
|
+
@caches ||= Set.new
|
11
|
+
|
12
|
+
query = yield
|
13
|
+
|
14
|
+
@caches << Query::CachedQuery.new(
|
15
|
+
text: query.strip,
|
16
|
+
always: always,
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# lib/readyset/configuration.rb
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module Readyset
|
5
|
+
class Configuration
|
6
|
+
attr_accessor :migration_path, :shard
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@migration_path = File.join(Rails.root, 'db/readyset_caches.rb')
|
10
|
+
@shard = :readyset
|
11
|
+
end
|
12
|
+
|
13
|
+
def failover
|
14
|
+
if @failover
|
15
|
+
@failover
|
16
|
+
else
|
17
|
+
inner = ActiveSupport::OrderedOptions.new
|
18
|
+
inner.enabled = false
|
19
|
+
inner.healthcheck_interval = 5.seconds
|
20
|
+
inner.error_window_period = 1.minute
|
21
|
+
inner.error_window_size = 10
|
22
|
+
@failover = inner
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def hostname
|
27
|
+
ActiveRecord::Base.configurations.configs_for(name: shard.to_s).configuration_hash[:host]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|