readyset 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
+
@@ -0,0 +1,2 @@
1
+ require:
2
+ - rubocop-airbnb
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in readyset.gemspec
6
+ gemspec
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
+ ![Build status](https://github.com/readysettech/readyset-rails/actions/workflows/rspec.yml/badge.svg)
6
+ [![Number of GitHub issues that are open](https://img.shields.io/github/issues/readysettech/readyset-rails)](https://github.com/readysettech/readyset-rails/issues)
7
+ ![Number of GitHub closed issues](https://img.shields.io/github/issues-closed/readysettech/readyset-rails)
8
+ ![Number of GitHub pull requests that are open](https://img.shields.io/github/issues-pr-raw/readysettech/readyset-rails)
9
+ ![GitHub release; latest by date](https://img.shields.io/github/v/release/readysettech/readyset-rails)
10
+ [![Slack](https://img.shields.io/badge/Join%20Slack-gray?logo=slack&logoColor=white)](https://join.slack.com/t/readysetcommunity/shared_invite/zt-2272gtiz4-0024xeRJUPGWlRETQrGkFw)
11
+ [![Follow us on X, formerly Twitter](https://img.shields.io/twitter/follow/ReadySet?style=social)](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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i(spec rubocop)
data/config.ru ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ Bundler.require :default, :development
7
+
8
+ Combustion.initialize! :all
9
+ run Combustion::Application
@@ -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