Skip to content

Commit cf4252c

Browse files
committed
Re implmenet connection pooling
1 parent 1d98c86 commit cf4252c

14 files changed

Lines changed: 1025 additions & 280 deletions

File tree

.github/workflows/rspec.yml

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77

88
services:
99
mysql:
10-
image: mysql:5.7
10+
image: mysql:8.0
1111
ports:
1212
- 3306:3306
1313
env:
@@ -22,16 +22,8 @@ jobs:
2222
- uses: actions/checkout@v2
2323
- uses: ruby/setup-ruby@v1
2424
with:
25-
ruby-version: 2.4
25+
ruby-version: 3.4
2626
bundler-cache: true
2727

2828
- name: Run tests
2929
run: bundle exec rspec
30-
31-
- name: Code Coverage
32-
uses: paambaati/codeclimate-action@v2.7.5
33-
env:
34-
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
35-
with:
36-
coverageLocations: |
37-
${{github.workspace}}/coverage/.resultset.json:simplecov

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ group :test, :development do
77
end
88

99
group :test do
10-
gem 'simplecov', '0.17.1', require: false
10+
gem 'simplecov', require: false
1111
end

README.md

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ gem 'mysql_framework'
3434

3535
#### MySQL Connection Pooling Variables
3636

37-
* `MYSQL_START_POOL_SIZE` - how many connections should be created by default (default: `1`)
37+
* `MYSQL_CONNECTION_POOL_ENABLED` - enables/disables pooling (default: `false`)
3838
* `MYSQL_MAX_POOL_SIZE` - how many connections should the pool be allowed to grow to (default: `5`)
39+
* `MYSQL_POOL_TIMEOUT` - how long to wait for a pooled connection before timing out (default: `5` seconds)
40+
* `MYSQL_POOL_IDLE_TIMEOUT` - how long a pooled connection can remain idle before being reaped (default: `300` seconds)
41+
* `MYSQL_POOL_IDLE_REAP_TIME` - time interval between background thread checking for idle connections to reap (default: `60` seconds)
3942

4043
#### MySQL Migration Variables
4144

@@ -161,31 +164,33 @@ MysqlFramework::Connector.new(options)
161164

162165
#### #setup
163166

164-
Sets up the connection pooling. Creates `ENV['MYSQL_START_POOL_SIZE']` `Mysql2::Client` instances up front. This is provided as a separate method to allow for use within process forking where connections would need to be created after forking the process.
167+
Sets up connection pooling using `connection_pool` with `ENV['MYSQL_MAX_POOL_SIZE']` and `ENV['MYSQL_POOL_TIMEOUT']`. Connections are created lazily by the pool when first needed.
165168

166169
```ruby
167170
connector.setup
168171
```
169172

170173
#### #dispose
171174

172-
Closes all the `Mysql2::Client` connections and removes the connection pool. Intended as a clean-up method to be used on process fork shutdown.
175+
Closes pooled `Mysql2::Client` connections and removes the pool. Intended as a clean-up method to be used on process fork shutdown.
173176

174177
```ruby
175178
connector.dispose
176179
```
177180

178181
#### #check_out
179182

180-
Check out a client from the connection pool. Will create new `Mysql2::Client` instances up-to `ENV['MYSQL_MAX_POOL_SIZE']` times if no idle connections are available.
183+
Checks out a `Mysql2::Client` instance from the pool, sanitizes it, and returns it.
184+
When pooling is disabled, it returns a newly created client.
181185

182186
```ruby
183187
client = connector.check_out
184188
```
185189

186190
#### #check_in
187191

188-
Check in a client to the connection pool
192+
Checks a client back in to the pool.
193+
When pooling is disabled, it closes the provided client.
189194

190195
```ruby
191196
client = connector.check_out
@@ -195,7 +200,17 @@ connector.check_in(client)
195200

196201
#### #with_client
197202

198-
Called with a block. The method checks out a client from the pool and yields it to the block. Finally it ensures that the client is always checked back into the pool.
203+
Called with a block. The method obtains a client (from the pool when enabled), yields it to the block, and guarantees cleanup.
204+
205+
When pooling is enabled, it uses the pool lifecycle (`ConnectionPool#with`) and supports optional discarding of the current pooled connection:
206+
207+
```ruby
208+
connector.with_client(discard_current_pool_connection: true) do |client|
209+
# use client
210+
end
211+
```
212+
213+
When pooling is disabled, it creates a fresh client for the block and closes it afterwards.
199214

200215
```ruby
201216
connector.with_client do |client|
@@ -280,10 +295,65 @@ The default options used to initialise MySQL2::Client instances:
280295
database: ENV.fetch('MYSQL_DATABASE'),
281296
username: ENV.fetch('MYSQL_USERNAME'),
282297
password: ENV.fetch('MYSQL_PASSWORD'),
283-
reconnect: true
298+
reconnect: true,
299+
read_timeout: Integer(ENV.fetch('MYSQL_READ_TIMEOUT', 30)),
300+
write_timeout: Integer(ENV.fetch('MYSQL_WRITE_TIMEOUT', 10))
284301
}
285302
```
286303

304+
### MysqlFramework::Stats::AwsMetricPublisher
305+
306+
Publishes connection-pool metrics (`size`, `available`, `idle`) to AWS CloudWatch on a configurable interval via a background thread.
307+
308+
**Setup sequence** — the connector must be set up before the publisher is started:
309+
310+
```ruby
311+
connector = MysqlFramework::Connector.new
312+
connector.setup # must come first
313+
314+
publisher = MysqlFramework::Stats::AwsMetricPublisher.new(
315+
connector: connector,
316+
publish_interval: 300 # seconds, default
317+
)
318+
publisher.start
319+
```
320+
321+
On shutdown, stop the publisher before disposing the connector:
322+
323+
```ruby
324+
publisher.stop
325+
connector.dispose
326+
```
327+
328+
#### Customising CloudWatch dimensions and namespace
329+
330+
Use `MysqlFramework::Stats::DimensionMap` to configure the CloudWatch namespace and dimensions. Each attribute falls back to the corresponding environment variable when not set explicitly:
331+
332+
| Attribute | ENV fallback | CloudWatch dimension |
333+
|---|---|---|
334+
| `service_name` | `SERVICE_NAME` | `ServiceName` |
335+
| `application` | `APPLICATION` | `Application` |
336+
| `environment` | `ENVIRONMENT` | `Environment` |
337+
| `landscape` | `LANDSCAPE` | `Landscape` |
338+
| `namespace` | `AWS_METRICS_NAMESPACE` | (namespace, default: `MysqlFramework`) |
339+
340+
```ruby
341+
dimension_map = MysqlFramework::Stats::DimensionMap.new(
342+
service_name: 'my-service',
343+
application: 'my-app',
344+
environment: 'production',
345+
landscape: 'us-east',
346+
namespace: 'MyCompany/MySQL'
347+
)
348+
349+
publisher = MysqlFramework::Stats::AwsMetricPublisher.new(
350+
connector: connector,
351+
dimension_map: dimension_map,
352+
publish_interval: 60
353+
)
354+
publisher.start
355+
```
356+
287357
### MysqlFramework::SqlCondition
288358

289359
A representation of a MySQL Condition for a column. Created automatically by SqlColumn

docker-compose.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
version: '2.1'
2-
31
services:
42
test-runner:
5-
image: ruby:2.4
3+
image: ruby:3.4
64
working_dir: /usr/src/app
75
container_name: test-runner
86
command: sh -c "while true; do echo 'Container is running..'; sleep 5; done"
@@ -21,7 +19,7 @@ services:
2119

2220
test-mysql:
2321
container_name: test-mysql
24-
image: mysql:5.7
22+
image: mysql:8
2523
restart: always
2624
environment:
2725
MYSQL_ROOT_PASSWORD: admin

0 commit comments

Comments
 (0)