octoshark 0.1.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f8eac1fa769be042b9c258530c49c05ef91723ab
4
- data.tar.gz: 1852cb828c3babdcdcb79298e4619581bb72b216
2
+ SHA256:
3
+ metadata.gz: 743be64d2aeb897827aec871dfd86150ea1ca7ce6505839cea1caf16e85676bc
4
+ data.tar.gz: a801773571a7388fed3d16179be6006d10a6e060fb514813a2e21ba3532816ee
5
5
  SHA512:
6
- metadata.gz: 535e065d62a6cd01e38d6d993e25a02f182a0dfa07df4b3150de13418caabeaf325531bd3560005b62e6e002e39b2835fcea701621d5454c03e01a16916cd325
7
- data.tar.gz: 026dee2bd0e09b7f5ea1c3c2300727ff224f6820e51cc562cec57bd33ad92765267e760957168841054dcb76fd0391d19c4dfc99588092c1c49fdd7487887a39
6
+ metadata.gz: 2fa8e793d0eae00f5d99420e1f3b5bc6218b9b5a5b8c27b0c63dbbde080265d5797cc4d56cbce049cd25459e0f8961228f0acb5d677f35f1de45a42fbd82f610
7
+ data.tar.gz: 16f82b5d43d619ff5d8691229a8377189c10ef3b9d20752649d2e63725bff171afe9f232d2300916b573bff7260ff63d92be08bae567934ff7751285d6240728
@@ -0,0 +1,66 @@
1
+ name: Build Legacy
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - "*"
7
+ push:
8
+ branches:
9
+ - master
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-16.04
14
+ services:
15
+ mysql:
16
+ image: mysql:5.5
17
+ env:
18
+ MYSQL_ROOT_PASSWORD: pass
19
+ ports:
20
+ - "3306:3306"
21
+ options: >-
22
+ --health-cmd="mysqladmin ping"
23
+ --health-interval=10s
24
+ --health-timeout=5s
25
+ --health-retries=3
26
+
27
+ name: ruby-${{ matrix.ruby }} ${{ matrix.gemfile }}
28
+ strategy:
29
+ matrix:
30
+ include:
31
+ - gemfile: rails3.0
32
+ ruby: 2.2
33
+ - gemfile: rails3.1
34
+ ruby: 2.2
35
+ - gemfile: rails3.2
36
+ ruby: 2.2
37
+
38
+ - gemfile: rails4.0
39
+ ruby: 2.4
40
+ - gemfile: rails4.1
41
+ ruby: 2.4
42
+ - gemfile: rails4.2
43
+ ruby: 2.4
44
+
45
+ env:
46
+ BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
47
+ BUNDLE_PATH_RELATIVE_TO_CWD: true
48
+
49
+ steps:
50
+ - uses: actions/checkout@master
51
+
52
+ - name: Set up Ruby
53
+ uses: ruby/setup-ruby@v1
54
+ with:
55
+ ruby-version: ${{ matrix.ruby }}
56
+ bundler: default
57
+ bundler-cache: true
58
+
59
+ - name: Set up database
60
+ run: |
61
+ cp spec/support/config.yml.github spec/support/config.yml
62
+ bundle exec rake db:create
63
+
64
+ - name: Run tests
65
+ run: |
66
+ bundle exec rspec spec
@@ -0,0 +1,64 @@
1
+ name: Build
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - "*"
7
+ push:
8
+ branches:
9
+ - master
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ services:
15
+ mysql:
16
+ image: mysql:5.7
17
+ env:
18
+ MYSQL_ROOT_PASSWORD: pass
19
+ ports:
20
+ - "3306:3306"
21
+ options: >-
22
+ --health-cmd="mysqladmin ping"
23
+ --health-interval=10s
24
+ --health-timeout=5s
25
+ --health-retries=3
26
+
27
+ name: ruby-${{ matrix.ruby }} ${{ matrix.gemfile }}
28
+ strategy:
29
+ matrix:
30
+ include:
31
+ - gemfile: rails5.0
32
+ ruby: 2.6
33
+ - gemfile: rails5.1
34
+ ruby: 2.6
35
+ - gemfile: rails5.2
36
+ ruby: 2.6
37
+
38
+ - gemfile: rails6.0
39
+ ruby: 2.7
40
+ - gemfile: rails6.1
41
+ ruby: 2.7
42
+
43
+ env:
44
+ BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
45
+ BUNDLE_PATH_RELATIVE_TO_CWD: true
46
+
47
+ steps:
48
+ - uses: actions/checkout@master
49
+
50
+ - name: Set up Ruby
51
+ uses: ruby/setup-ruby@v1
52
+ with:
53
+ ruby-version: ${{ matrix.ruby }}
54
+ bundler: default
55
+ bundler-cache: true
56
+
57
+ - name: Set up database
58
+ run: |
59
+ cp spec/support/config.yml.github spec/support/config.yml
60
+ bundle exec rake db:create
61
+
62
+ - name: Run tests
63
+ run: |
64
+ bundle exec rspec spec
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.1.1
1
+ 2.7.4
data/Appraisals CHANGED
@@ -1,19 +1,29 @@
1
- appraise "rails3.1" do
2
- gem "activerecord", "~> 3.1.0"
1
+ appraise "rails5.0" do
2
+ gem "activerecord", "~> 5.0.0"
3
+ gem "mysql2", "~> 0.5.2"
4
+ gem "sqlite3", "~> 1.3.13"
3
5
  end
4
6
 
5
- appraise "rails3.2" do
6
- gem "activerecord", "~> 3.2.0"
7
+ appraise "rails5.1" do
8
+ gem "activerecord", "~> 5.1.0"
9
+ gem "mysql2", "~> 0.5.2"
10
+ gem "sqlite3", "~> 1.4.1"
7
11
  end
8
12
 
9
- appraise "rails4" do
10
- gem "activerecord", "~> 4.0.0"
13
+ appraise "rails5.2" do
14
+ gem "activerecord", "~> 5.2.0"
15
+ gem "mysql2", "~> 0.5.2"
16
+ gem "sqlite3", "~> 1.4.1"
11
17
  end
12
18
 
13
- appraise "rails4.1" do
14
- gem "activerecord", "~> 4.1.0"
19
+ appraise "rails6.0" do
20
+ gem "activerecord", "~> 6.0.0"
21
+ gem "mysql2", "~> 0.5.2"
22
+ gem "sqlite3", "~> 1.4.1"
15
23
  end
16
24
 
17
- appraise "rails4.2" do
18
- gem "activerecord", "~> 4.2.0"
25
+ appraise "rails6.1" do
26
+ gem "activerecord", "~> 6.1.0"
27
+ gem "mysql2", "~> 0.5.2"
28
+ gem "sqlite3", "~> 1.4.1"
19
29
  end
data/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Change log
2
+
3
+ ## 0.3.0 2021-07-20
4
+
5
+ - Add support for Rails 6.1
6
+
7
+ ## 0.2.2 2016-11-09
8
+
9
+ - Fix `alias_method_chain` deprecation in Rails 5
10
+
11
+ ## 0.2.1 2016-09-09
12
+
13
+ - Add support for Rails 5 and Rails 3
14
+
15
+ ## 0.2.0 2016-08-29
16
+
17
+ - `Octoshark::ConnectionManager` is split in two managers `Octoshark::ConnectionPoolsManager` for persistent connections and `Octoshark::ConnectionManager` for non-persistent connections.
18
+ - `Octoshark` class methods like `connection_managers`, `reset_connection_managers!`, `disconnect!` are only relevant and moved to `Octoshark::ConnectionPoolsManager` class.
19
+ - `Octoshark::ConnectionManager#use_database` method has been removed and the functionality moved to `Octoshark::ConnectionPoolsManager#with_connection(name, database_name)` where the second optional argument `database_name` when specified will switch the connection to the database using the `use database` MySQL statement.
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
1
  source 'https://rubygems.org'
2
+ gem "activerecord", "~> 6.1.0"
2
3
 
3
4
  # Specify your gem's dependencies in octoshark.gemspec
4
5
  gemspec
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- ![Octoshark logo](https://dl.dropboxusercontent.com/u/3230730/github/octoshark.png)
1
+ ![Octoshark logo](http://dalibornasevic.com/images/octoshark.png)
2
2
 
3
3
  ![Travis status](https://travis-ci.org/dalibor/octoshark.png)
4
4
 
5
- Octoshark is a simple ActiveRecord connection manager. It provides a connection switching mechanism that can be used in various scenarios like: sharding, master-slave, multi-tenancy, etc. You have control over how connections are configured and used, see below for examples.
5
+ Octoshark is a simple ActiveRecord connection manager. It provides connection switching mechanisms that can be used in various scenarios like master-slave, sharding or multi-tenant architecture.
6
6
 
7
7
 
8
8
  ## Installation
@@ -28,138 +28,142 @@ $ gem install octoshark
28
28
 
29
29
  ## Usage
30
30
 
31
- Create a new connection manager with connection 2 pools for Octoshark to manage. This usually goes in the app initializer.
31
+ Octoshark has two connection managers: `ConnectionPoolsManager` for managing connection pools using persistent connections and `ConnectionManager` for managing non-persistent connections. It depends on your application performance and scaling requirements which one to use.
32
32
 
33
- ```ruby
34
- CONN_MANAGER = Octoshark::ConnectionManager.new({
35
- db1: { adapter: "sqlite3", database: "db/db1.sqlite" },
36
- db2: { adapter: "sqlite3", database: "db/db2.sqlite" }
37
- })
38
- ```
33
+ - If you have a limited number of consumers (application and worker servers), `ConnectionPoolsManager` would be the preferred option. Standard Rails application has a single connection pool, and `ConnectionPoolsManager` just makes it possible for application models to work with multiple connection pools.
34
+
35
+ - If you have a very big infrastructure with lots of consumers (application and worker servers) and you are hitting max connections limit on database servers, i.e. you need to scale horizontally, `ConnectionManager` is the option to use. Because it uses non-persistent connections it comes up with a performance penalty because connections are re-established over and over again. Some ActiveRecord plugins that depend on having an active database connection all the time might need a change in order to work with non-persistent connections.
36
+
37
+ `ConnectionPoolsManager` and `ConnectionManager` can be combined together and many of them can be used at the same time.
39
38
 
40
- Configure which ActiveRecord model will use the Octoshark connection by overriding the `Model.connection` method.
39
+ Here is how to create connection pools manager:
41
40
 
42
41
  ```ruby
43
- class Post < ActiveRecord::Base
44
- def self.connection
45
- CONN_MANAGER.current_connection
46
- end
47
- end
42
+ CONN_MANAGER = Octoshark::ConnectionPoolsManager.new({ c1: config1, c2: config2 })
48
43
  ```
49
44
 
50
- Alternatively, extract it as a module and include in models.
45
+ `config1` and `config2` are standard ActiveRecord database configs:
51
46
 
52
47
  ```ruby
53
- module ShardingModel
54
- extend ActiveSupport::Concern
55
-
56
- module ClassMethods
57
- def connection
58
- OCTOSHARK.current_connection
59
- end
60
- end
61
- end
48
+ config = {
49
+ adapter: 'mysql2',
50
+ host: 'localhost',
51
+ port: 3306,
52
+ database: 'database',
53
+ username: 'root',
54
+ password: 'pass',
55
+ pool: 1,
56
+ encoding: 'utf8',
57
+ reconnect: false
58
+ }
62
59
  ```
63
60
 
64
- To use a specific database connection:
61
+ To switch a connection using a specific pool:
65
62
 
66
63
  ```ruby
67
- CONN_MANAGER.with_connection(:db1) do
68
- # run queries on db1
69
- Post.first
64
+ CONN_MANAGER.with_connection(:c1) do |connection|
65
+ connection.execute("SELECT 1")
70
66
  end
71
67
  ```
72
68
 
73
69
  Multiple `with_connection` blocks can be nested:
74
70
 
75
71
  ```ruby
76
- CONN_MANAGER.with_connection(:db1) do
77
- # run queries on db1
72
+ CONN_MANAGER.with_connection(config1) do
73
+ # run queries on connection specified with config1
78
74
 
79
- CONN_MANAGER.with_connection(:db2) do
80
- # run queries on db2
75
+ CONN_MANAGER.with_connection(config2) do
76
+ # run queries on connection specified with config2
81
77
  end
82
78
 
83
- # run queries on db1
79
+ # run queries on connection specified with config1
84
80
  end
85
81
  ```
86
82
 
87
- `CONN_MANAGER.current_connection` returns the active connection while in the `with_connection` block or raises `Octoshark::Error::NoCurrentConnection` otherwise.
83
+ If you establish a connection to database server instead of to database, then you can use the second optional argument `database_name` to tell the connection manager to switch the connection to that database within the same connection. This is useful when you want to have fewer connection pools and keep number of active connection per database server under control. This option is only MySQL specific for now, it uses `USE database_name` statement to switch the connection.
88
84
 
85
+ ```ruby
86
+ CONN_MANAGER.with_connection(:c1, database_name) do |connection|
87
+ connection.execute("SELECT 1")
88
+ end
89
+ ```
89
90
 
90
- ## Sharding example
91
-
92
- Some models are in the core DB, and others in shard DBs. Shard is selected based on a user attribute. For core models use the default ActiveRecord connection and for sharded models define and use Octoshark connections.
91
+ Using non-persistent connections with `Octoshark::ConnectionManager` has the same API:
93
92
 
94
- Switch the connection in a controller with an around filter:
95
93
 
96
94
  ```ruby
97
- around_filter :select_shard
95
+ CONN_MANAGER = Octoshark::ConnectionManager.new
96
+ ```
98
97
 
99
- def select_shard(&block)
100
- CONN_MANAGER.with_connection(current_user.shard, &block)
98
+ Opening a new connection, executing query and closing the connection:
99
+
100
+ ```ruby
101
+ CONN_MANAGER.with_connection(config) do |connection|
102
+ connection.execute("SELECT 1")
101
103
  end
102
104
  ```
103
105
 
104
- Similar approach applies to other application entry-points like background jobs.
105
-
106
106
 
107
- ## Master-Slave example
107
+ ## Using Octoshark with ActiveRecord models
108
108
 
109
- All models are in master and slave databases. For master models use the default ActiveRecord connection and for slave models define and use Octoshark connections.
109
+ To tell an ActiveRecord model to use the Octoshark connection we can override the `Model.connection` method.
110
110
 
111
111
  ```ruby
112
- class ActiveRecord::Base
112
+ class Post < ActiveRecord::Base
113
113
  def self.connection
114
- # Return the current connection (from with_connection block) or default one
115
- CONN_MANAGER.current_or_default_connection
114
+ CONN_MANAGER.current_connection
116
115
  end
117
116
  end
118
117
  ```
119
118
 
120
- `CONN_MANAGER.current_or_default_connection` method returns the current connection while in `with_connection` block or the default ActiveRecord connection when outside.
121
-
119
+ Alternatively, we can extract it as a module and include in multiple models.
122
120
 
123
- ## Multi-tenant example
121
+ ```ruby
122
+ module ShardingModel
123
+ extend ActiveSupport::Concern
124
124
 
125
- Some models are in the core DB, and others in user's own dedicated database. For core models use the default ActiveRecord connection and for tenant models can use Octoshark's mechanism to create new temporary connection.
125
+ module ClassMethods
126
+ def connection
127
+ CONN_MANAGER.current_connection
128
+ end
129
+ end
130
+ end
131
+ ```
126
132
 
127
- Switch the connection in a controller with an around filter:
133
+ To use a specific database connection:
128
134
 
129
135
  ```ruby
130
- # in initializer
131
- CONN_MANAGER = Octoshark::ConnectionManager.new
136
+ CONN_MANAGER.with_connection(:c1) do
137
+ # run queries on c1
138
+ Post.first
139
+ end
140
+ ```
141
+
142
+ This connection switching in Rails applications is usually done from within an `around_filter` for controllers and in a similar way for other application "entry-points" like background jobs:
132
143
 
133
- # in controller
144
+ ```ruby
134
145
  around_filter :select_shard
135
146
 
136
147
  def select_shard(&block)
137
- CONN_MANAGER.with_new_connection(name, config, reusable: false, &block)
148
+ CONN_MANAGER.with_connection(current_user.shard, &block)
138
149
  end
139
150
  ```
140
151
 
141
- `CONN_MANAGER.with_new_connection` method creates a temporary connection that will automatically disconnect. If you want to reuse it in subsequent connection switches, set `reusable: true` and it will be added to the connection manager and reused with the next calls. Depends on the use-case and what's preferable. In test environment usually you would want to set it to `reusable` so that database cleaner can clean data with transaction strategy.
152
+ `CONN_MANAGER.current_connection` returns the active connection while the execution is in the `with_connection` block or raises `Octoshark::Error::NoCurrentConnection` outside of the `with_connection` block. In some cases, falling back to the default database connection for the Rails app might be preferable which can be done using `CONN_MANAGER.current_or_default_connection`.
142
153
 
143
- Alternatively, for better performance (only supported on MySQL), database connection can be switched with `use database` statement. Once connection manager is defined with connectino configs to database servers, selecting a database can be done with:
144
154
 
145
- ```ruby
146
- CONN_MANAGER.use_database(:db1, 'database') do
147
- # run queries on database server identified by 'db1' using database 'database'
148
- end
149
- ```
155
+ ## Octoshark::ConnectionPoolsManager.reset_connection_managers!
150
156
 
151
- ## Octoshark.reset_connection_managers!
152
-
153
- Whenever ActiveRecord::Base calls `establish_connection` (usually by an ancestor process that must have subsequently forked), `Octoshark.reset_connection_managers!` is automatically called to re-establish the Octoshark connections. It prevents `ActiveRecord::ConnectionNotEstablished` in the scenarios like:
157
+ When using `Octoshark::ConnectionPoolsManager`, whenever ActiveRecord::Base calls `establish_connection` (usually by an ancestor process that must have subsequently forked), `Octoshark.reset_connection_managers!` is automatically called to re-establish the Octoshark connections. It prevents `ActiveRecord::ConnectionNotEstablished` in the scenarios like:
154
158
 
155
159
  * Unicorn before/after fork
156
160
  * Spring prefork/serve
157
161
  * Some rake tasks like `rake db:test:prepare`
158
162
 
159
163
 
160
- ## Database Cleaner
164
+ ## Cleaning test databases
161
165
 
162
- RSpec example on how to clean default and Octoshark data with Database Cleaner:
166
+ For `Octoshark::ConnectionPoolsManager`, we can use [DatabaseCleaner](https://github.com/DatabaseCleaner/database_cleaner) and RSpec like:
163
167
 
164
168
  ```ruby
165
169
  config.before(:suite) do
@@ -173,13 +177,12 @@ config.before(:each) do
173
177
  end
174
178
 
175
179
  config.after(:each) do
176
- setup_database_cleaner
177
180
  DatabaseCleaner.clean_with(:transaction)
178
181
  end
179
182
 
180
183
  def setup_database_cleaner
181
184
  DatabaseCleaner[:active_record, {connection: ActiveRecord::Base.connection_pool}]
182
- Octoshark.connection_managers.each do |manager|
185
+ Octoshark::ConnectionPoolsManager.connection_managers.each do |manager|
183
186
  manager.connection_pools.each_pair do |connection_name, connection_pool|
184
187
  DatabaseCleaner[:active_record, {connection: connection_pool}]
185
188
  end
@@ -187,6 +190,118 @@ def setup_database_cleaner
187
190
  end
188
191
  ```
189
192
 
193
+ For `Octoshark::ConnectionManager` where connections and databases are dynamically created and cannot be configured in the test setup, we can write a custom database cleaner inspired by [DatabaseRewinder](https://github.com/amatsuda/database_rewinder). The example below is for a multi-tenant test setup with a main (core) database and a tenant database for the `CURRENT_USER` in the test suite. `CustomDatabaseCleaner.clean_all` cleans all core database tables before test suite and `CustomDatabaseCleaner.clean` cleans used tables in both core and tenant databases after each test.
194
+
195
+
196
+ ```ruby
197
+ module CustomDatabaseCleaner
198
+ INSERT_REGEX = /\AINSERT(?:\s+IGNORE)?\s+INTO\s+(?:\.*[`"]?(?<table>[^.\s`"]+)[`"]?)*/i
199
+
200
+ @@tables_with_inserts = []
201
+
202
+ class << self
203
+ def record_inserted_table(connection, sql)
204
+ match = sql.match(INSERT_REGEX)
205
+
206
+ if match && match[:table] && tables_with_inserts.exclude?(match[:table])
207
+ tables_with_inserts << match[:table]
208
+ end
209
+ end
210
+
211
+ def clean_all
212
+ with_core_db_connection do |connection|
213
+ clean_tables(connection)
214
+ end
215
+
216
+ reset_tables_with_inserts
217
+ end
218
+
219
+ def clean
220
+ with_core_db_connection do |connection|
221
+ clean_tables(connection, { 'users' => [CURRENT_USER.id] })
222
+ end
223
+
224
+ CURRENT_USER.with_tenant do |connection|
225
+ clean_tables(connection)
226
+ end
227
+
228
+ reset_tables_with_inserts
229
+ end
230
+
231
+ private
232
+ def with_core_db_connection(&block)
233
+ CoreDBManager.with_connection(ActiveRecord::Base.configurations[Rails.env].symbolize_keys, &block)
234
+ end
235
+
236
+ def clean_tables(connection, keep_data = {})
237
+ tables_to_clean = connection.tables.reject { |t| t == ActiveRecord::Migrator.schema_migrations_table_name }
238
+ tables_to_clean = tables_to_clean & tables_with_inserts if tables_with_inserts.present?
239
+
240
+ tables_to_clean.each do |table|
241
+ connection.disable_referential_integrity do
242
+ table_name = connection.quote_table_name(table)
243
+ keep_ids = keep_data[table]
244
+
245
+ if keep_ids
246
+ connection.execute("DELETE FROM #{table_name} WHERE id NOT IN (#{keep_ids.join(',')});")
247
+ else
248
+ connection.execute("DELETE FROM #{table_name};")
249
+ end
250
+ end
251
+ end
252
+ end
253
+
254
+ def reset_tables_with_inserts
255
+ @@tables_with_inserts = []
256
+ end
257
+
258
+ def tables_with_inserts
259
+ @@tables_with_inserts
260
+ end
261
+ end
262
+ end
263
+
264
+ module CustomDatabaseCleaner
265
+ module InsertRecorder
266
+ def execute(sql, *)
267
+ CustomDatabaseCleaner.record_inserted_table(self, sql)
268
+ super
269
+ end
270
+
271
+ def exec_query(sql, *)
272
+ CustomDatabaseCleaner.record_inserted_table(self, sql)
273
+ super
274
+ end
275
+ end
276
+ end
277
+
278
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
279
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.send(:prepend, CustomDatabaseCleaner::InsertRecorder)
280
+ ```
281
+
282
+
283
+ ## Development Setup
284
+
285
+ Setup database config and create databases:
286
+
287
+ ```bash
288
+ cp spec/support/config.yml.template spec/support/config.yml
289
+ rake db:create
290
+ ```
291
+
292
+ Run specs:
293
+
294
+ ```bash
295
+ bundle exec rspec spec
296
+ ```
297
+
298
+ Install different active record versions defined in `Appraisals` and run specs for all of them:
299
+
300
+ ```bash
301
+ bundle exec appraisal
302
+ bundle exec appraisal rspec spec
303
+ ```
304
+
190
305
 
191
306
  ## Logo
192
307