pgdice 0.4.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +2 -2
  3. data/.codeclimate.yml +4 -1
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.github/workflows/gempush.yml +41 -0
  7. data/.rubocop.yml +5 -1
  8. data/.ruby-version +1 -1
  9. data/CHANGELOG.md +21 -0
  10. data/README.md +22 -23
  11. data/SECURITY.md +15 -0
  12. data/examples/aws/README.md +28 -0
  13. data/examples/aws/cloudformation/scheduled_events.json +59 -0
  14. data/examples/aws/lib/sqs_listener.rb +47 -0
  15. data/examples/aws/lib/sqs_listener/default_event_handler.rb +32 -0
  16. data/examples/aws/lib/sqs_listener/exceptions/unknown_task_error.rb +4 -0
  17. data/examples/aws/lib/sqs_listener/fallthrough_event_handler.rb +18 -0
  18. data/examples/aws/lib/sqs_listener/sqs_event_router.rb +32 -0
  19. data/examples/aws/lib/sqs_listener/typed_event_handler/task_event_handler.rb +46 -0
  20. data/examples/aws/lib/sqs_listener/typed_event_handler/tasks/database_tasks.rb +37 -0
  21. data/examples/aws/lib/sqs_message_deleter.rb +32 -0
  22. data/examples/aws/lib/sqs_poller.rb +67 -0
  23. data/examples/aws/tasks/poll_sqs.rake +8 -0
  24. data/examples/aws/workers/pg_dice_worker.rb +54 -0
  25. data/lib/pgdice.rb +3 -0
  26. data/lib/pgdice/database_connection_factory.rb +5 -5
  27. data/lib/pgdice/date_helper.rb +1 -1
  28. data/lib/pgdice/partition_helper.rb +2 -2
  29. data/lib/pgdice/pg_slice_manager.rb +4 -4
  30. data/lib/pgdice/query_executor.rb +22 -0
  31. data/lib/pgdice/query_executor_factory.rb +20 -0
  32. data/lib/pgdice/version.rb +1 -1
  33. data/pgdice.gemspec +5 -5
  34. metadata +80 -62
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12fa7e75440961dbabdca09146b652c0b849f4d0e92f7c6e66c5e95d932f7449
4
- data.tar.gz: 6c9df08948a598ae17ddfe76ceb9c594c5566a479dce26bf2dd698f9eb54969d
3
+ metadata.gz: ad12a02674afc68542e4b8038aaddcfb107eba52bc208f700ef57e880783d100
4
+ data.tar.gz: 6d384e71518d19b9e8319d6d84632ded6b3de0bcdd4fb6fa2cef80d837fc100d
5
5
  SHA512:
6
- metadata.gz: a54b4dcdc2e9bed2798f5ccee31c1189036b0b37123e23b29c949d53bfbfe3fe7af1f288d4077ff66f3ce04374c113a91746e583bca3404aa221408afa05ceda
7
- data.tar.gz: a418c411de0d82e221a1c7de358b1eb9dfd18a0bafd4207c84441852e87adfa1eb674063a194b50b1472e783f21842635dbc2c381958d1d0fc792a788c0914d3
6
+ metadata.gz: 8880202263ce06487635f87118d82b723405f465a5d39f087310961e965a553b4510088bc11f23d12fb37b76faf1bc0e8d5a706b26d28ad2afcb10111cbf07c8
7
+ data.tar.gz: a3c14f23d6b52a991f7997a9d887877e58b93e8c0b8717e65ea42ade699eae5fbfd1642eea788ddcc8eefe9d9dd01a12cff540772e1d2f69281119fcb6a73cad
@@ -6,13 +6,13 @@ version: 2
6
6
  defaults: &defaults
7
7
  working_directory: ~/repo
8
8
  docker:
9
- - image: circleci/ruby:2.5.1
9
+ - image: circleci/ruby:2.6.6
10
10
  environment:
11
11
  DATABASE_HOST: 127.0.0.1
12
12
  DATABASE_USERNAME: pgdice
13
13
  PGDICE_LOG_TARGET: STDOUT
14
14
 
15
- - image: circleci/postgres:10.6-alpine-ram
15
+ - image: circleci/postgres:12.1-alpine-ram
16
16
  environment:
17
17
  POSTGRES_USER: pgdice
18
18
  POSTGRES_DB: pgdice_test
@@ -2,4 +2,7 @@ version: "2"
2
2
  checks:
3
3
  argument-count:
4
4
  config:
5
- threshold: 5
5
+ threshold: 5
6
+
7
+ exclude_patterns:
8
+ - "examples/**/*"
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: needs investigation
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+
16
+ **Expected behavior**
17
+ A clear and concise description of what you expected to happen.
18
+
19
+ **Screenshots**
20
+ If applicable, add screenshots to help explain your problem.
21
+
22
+ **Please include this information**
23
+ - Postgres Version: [e.g. 10.6]
24
+ - PgDice Version [e.g. 0.14.3]
25
+
26
+ **Additional context**
27
+ Add any other context about the problem here.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: ''
5
+ labels: needs investigation
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Is your feature request related to a problem? Please describe.**
11
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
+
13
+ **Describe the solution you'd like**
14
+ A clear and concise description of what you want to happen.
15
+
16
+ **Describe alternatives you've considered**
17
+ A clear and concise description of any alternative solutions or features you've considered.
18
+
19
+ **Additional context**
20
+ Add any other context or screenshots about the feature request here.
@@ -0,0 +1,41 @@
1
+ name: Ruby Gem
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+
8
+ jobs:
9
+ build:
10
+ name: Build + Publish
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@master
15
+ - name: Set up Ruby 2.6
16
+ uses: actions/setup-ruby@v1
17
+ with:
18
+ version: 2.6.x
19
+
20
+ # - name: Publish to GPR
21
+ # run: |
22
+ # mkdir -p $HOME/.gem
23
+ # touch $HOME/.gem/credentials
24
+ # chmod 0600 $HOME/.gem/credentials
25
+ # printf -- "---\n:github: Bearer ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
26
+ # gem build *.gemspec
27
+ # gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
28
+ # env:
29
+ # GEM_HOST_API_KEY: ${{secrets.GPR_AUTH_TOKEN}}
30
+ # OWNER: username
31
+
32
+ - name: Publish to RubyGems
33
+ run: |
34
+ mkdir -p $HOME/.gem
35
+ touch $HOME/.gem/credentials
36
+ chmod 0600 $HOME/.gem/credentials
37
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
38
+ gem build *.gemspec
39
+ gem push *.gem
40
+ env:
41
+ GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
@@ -8,4 +8,8 @@ Metrics/BlockLength:
8
8
  # TODO: Evaluate if this is an acceptable parameter list. It's a constructor after all.
9
9
  Metrics/ParameterLists:
10
10
  Exclude:
11
- - lib/pgdice/table.rb
11
+ - lib/pgdice/table.rb
12
+
13
+ AllCops:
14
+ Exclude:
15
+ - 'examples/**/*'
@@ -1 +1 @@
1
- ruby-2.5.1
1
+ ruby-2.6.6
@@ -2,6 +2,27 @@
2
2
  All notable changes to this project will be documented in this file.
3
3
  This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
+ ## [v1.0.1] : 2020-01-31
6
+ ### Changes
7
+ - Added better examples/docs.
8
+ - Bump `pg` to `~> 1.2.2`
9
+
10
+ ## [v0.4.3] : 2019-04-23
11
+ ### Changes
12
+ - Fix #31 where the new connection retry code was eagerly initializing the logger
13
+
14
+
15
+ ## [v0.4.2] : 2019-04-22
16
+ ### Changes
17
+ - Fix #19 where PgDice wouldn't recover from a broken PG::Connection
18
+ by adding new retry behavior
19
+
20
+
21
+ ## [v0.4.1] : 2019-03-21
22
+ ### Changes
23
+ - Fix bug where partitioning by months would break when the month was < 10
24
+
25
+
5
26
  ## [v0.4.0] : 2018-12-06
6
27
  ### Changes
7
28
  - Add `only:` option to `assert_tables` so users can assert on only `past`
data/README.md CHANGED
@@ -12,18 +12,9 @@ PgDice is a utility for creating and maintaining partitioned database tables tha
12
12
  PgDice is intended to be used by scheduled background jobs in frameworks like [Sidekiq](https://github.com/mperham/sidekiq)
13
13
  where logging and clear exception messages are crucial.
14
14
 
15
+ # Maintenance status
15
16
 
16
- ## Disclaimer
17
-
18
- There are some features in this gem which allow you to drop database tables.
19
-
20
- If you choose to use this software without a __tested and working__ backup and restore strategy in place then you
21
- are a fool and will pay the price for your negligence. THIS SOFTWARE IS PROVIDED "AS IS",
22
- WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. By using this software you agree that the creator,
23
- maintainers and any affiliated parties CANNOT BE HELD LIABLE FOR DATA LOSS OR LOSSES OF ANY KIND.
24
-
25
- See the [LICENSE](LICENSE) for more information.
26
-
17
+ This project is stable and used daily in production.
27
18
 
28
19
  # Installation
29
20
 
@@ -61,6 +52,7 @@ PgDice.configure do |config|
61
52
 
62
53
  # Set a config file or build the tables manually
63
54
  config.config_file = Rails.root.join('config', 'pgdice.yml') # If you are using rails, else provide the absolute path.
55
+ config.config_file = Rails.root.join('config', 'pgdice.yml') # If you are using rails, else provide the absolute path.
64
56
  # and/or
65
57
  config.approved_tables = PgDice::ApprovedTables.new(
66
58
  PgDice::Table.new(table_name: 'comments', past: 90, future: 7, period: 'day'),
@@ -69,7 +61,6 @@ PgDice.configure do |config|
69
61
  end
70
62
  ```
71
63
 
72
-
73
64
  ### Configuration Parameters
74
65
 
75
66
  - `database_url` - **Required**: The postgres database url to connect to.
@@ -179,7 +170,6 @@ PgDice.undo_partitioning!('comments')
179
170
  This method will revert the changes made by partitioning a table. Don't rely on this
180
171
  in production if you mess up; you need to test everything thoroughly.
181
172
 
182
-
183
173
  ## Maintaining partitioned tables
184
174
 
185
175
  ### Adding more tables
@@ -196,7 +186,6 @@ PgDice.add_new_partitions('comments')
196
186
  that the partitioned table was defined with.
197
187
  - The example `comments` table we have been using was configured to always keep `7` future partitions above.
198
188
 
199
-
200
189
  ### Listing droppable partitions
201
190
 
202
191
  Sometimes you just want to know what's out there and if there are tables ready to be dropped.
@@ -213,7 +202,6 @@ PgDice.list_droppable_partitions_by_batch_size('comments')
213
202
 
214
203
  This method will show partitions that are within the configured `batch_size`.
215
204
 
216
-
217
205
  #### Notes on `list_droppable_partitions`
218
206
 
219
207
  - This method uses the `past` value from the `PgDice::Table` to determine which tables are eligible for dropping.
@@ -235,7 +223,6 @@ PgDice.drop_old_partitions('comments')
235
223
  for the `PgDice::Table`.
236
224
  - The example `comments` table has been configured with `past: 90` tables.
237
225
  So if there were 100 tables older than `today` it would drop up to `batch_size` tables.
238
-
239
226
 
240
227
  # Validation
241
228
 
@@ -247,7 +234,7 @@ To validate that your expected number of tables exist, you can run:
247
234
  PgDice.assert_tables('comments')
248
235
  ```
249
236
 
250
- An [InsufficientTablesError](lib/pgdice.rb) will be raised if any conditions are not met.
237
+ An [InsufficientTablesError](lib/pgdice/error.rb) will be raised if any conditions are not met.
251
238
 
252
239
  This will check that there are 7 future tables from now and that there are 90 past tables
253
240
  per our configuration above.
@@ -268,7 +255,6 @@ PgDice.approved_tables
268
255
 
269
256
  The [ApprovedTables](lib/pgdice/approved_tables.rb) object responds to the most common enumerable methods.
270
257
 
271
-
272
258
  # Miscellaneous Notes
273
259
 
274
260
  All methods for `PgDice` take a hash which will override whatever values would have been automatically supplied.
@@ -279,6 +265,12 @@ PgDice.list_droppable_partitions('comments', past: 60)
279
265
  ```
280
266
  This example would use `60` instead of the configured value of `90` from the `comments` table we configured above.
281
267
 
268
+ # Examples
269
+
270
+ 1. [Here's an example on how to use PgDice in AWS](examples/aws) and the [README](examples/aws/README.md) which will guide
271
+ you through what is going on.
272
+
273
+ 1. [Here's an example on how to write a config.yml for PgDice](examples/config.yml)
282
274
 
283
275
  # FAQ
284
276
 
@@ -292,7 +284,7 @@ This example would use `60` instead of the configured value of `90` from the `co
292
284
  password = config[Rails.env]["password"]
293
285
 
294
286
  "postgres://#{username}:#{password}@#{host}/#{database}"
295
- end
287
+ end
296
288
  ```
297
289
 
298
290
  1. I'm seeing off-by-one errors for my `assert_tables` calls?
@@ -303,7 +295,7 @@ end
303
295
 
304
296
  1. Full `PG::Connection` support (no more database URLs).
305
297
  1. Non time-range based partitioning. [PgParty](https://github.com/rkrage/pg_party) might be a good option!
306
-
298
+ 1. Hourly partitioning
307
299
 
308
300
  # Development
309
301
 
@@ -312,7 +304,6 @@ You can also run `bin/console` for an interactive prompt that will allow you to
312
304
 
313
305
  To install this gem onto your local machine, run `bundle exec rake install`.
314
306
 
315
-
316
307
  ## Running tests
317
308
 
318
309
  You're going to need to have postgres 10 or greater installed.
@@ -323,7 +314,6 @@ Run the following commands from your terminal. Don't run these on anything but a
323
314
  1. `createdb pgdice_test`
324
315
  1. Now you can run the tests via `guard` or `rake test`
325
316
 
326
-
327
317
  ## Contributing
328
318
 
329
319
  Bug reports and pull requests are welcome on GitHub at
@@ -331,11 +321,20 @@ Bug reports and pull requests are welcome on GitHub at
331
321
  to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
332
322
  the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
333
323
 
334
-
335
324
  # License
336
325
 
337
326
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
338
327
 
328
+ # Disclaimer
329
+
330
+ There are some features in this gem which allow you to drop database tables, due to the dangerous nature of
331
+ dropping database tables, please ensure you have a tested and working backup and restore strategy.
332
+
333
+ THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
334
+ By using this software you agree that the creator, maintainers, and any affiliated parties
335
+ CANNOT BE HELD LIABLE FOR DATA LOSS OR LOSSES OF ANY KIND.
336
+
337
+ See the [LICENSE](LICENSE) for more information.
339
338
 
340
339
  # Code of Conduct
341
340
 
@@ -0,0 +1,15 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ The versions here are currently supported for security patches.
6
+
7
+ | Version | Supported |
8
+ | ------- | ------------------ |
9
+ | latest | :white_check_mark: |
10
+ | < 1.0 | :x: |
11
+
12
+
13
+ ## Reporting a Vulnerability
14
+
15
+ Please open a new issue
@@ -0,0 +1,28 @@
1
+ # How can I use PgDice in production?
2
+
3
+ This collection of files is how I use PgDice in production. I'll describe the architecture here so you'll have a place
4
+ to start.
5
+
6
+ 1. `tasks/poll_sqs.rake` is run using some sort of process manager like systemd on the ec2 instance. I like to run
7
+ the poll_sqs stuff on my Sidekiq instances because they are the ones who eventually handle the work anyway.
8
+
9
+ 1. `lib/sqs_poller.rb` is used to handle the looping logic for the rake task. It invokes `lib/sqs_listener.rb` for each
10
+ iteration.
11
+
12
+ 1. `lib/sqs_listener.rb` calls AWS SQS to receive messages and then passes each one into the `lib/sqs_listener/sqs_event_router.rb`
13
+ to be routed to the correct message handler.
14
+
15
+ 1. Inside `lib/sqs_listener/sqs_event_router.rb` the message is parsed and passed through a case statement.
16
+ This could be abstracted better but for now if the message has a field of `event_type` and a value of `"task"` then
17
+ the router will send it off to the `TaskEventHandler` which in this case is
18
+ `lib/sqs_listener/typed_event_handler/task_event_handler.rb`
19
+
20
+ 1. In the `TaskEventHandler` the task is sent to a handler which responds to the task specified in the message body field `task`.
21
+
22
+ 1. The handler for the task (in this case, `DatabaseTasks`) handles the parameters for invoking the Sidekiq worker: `PgDiceWorker`
23
+
24
+ 1. Finally, the `PgDiceWorker` is called and handles invoking `PgDice` based on the parameters passed in.
25
+
26
+
27
+ Hopefully that wasn't too confusing. There's a lot of steps in here because the system that uses PgDice handles lots
28
+ of different types of SQS events and needs to be as resilient as possible.
@@ -0,0 +1,59 @@
1
+ {
2
+ "Description": "Deployment stack",
3
+ "Parameters": {
4
+ "PgDiceEnabled": {
5
+ "Type": "String",
6
+ "Description": "The ENABLED/DISABLED state of the cloudwatch scheduled events for PgDice."
7
+ }
8
+ },
9
+ "Resources": {
10
+ "PgDiceDailyAddPartitions": {
11
+ "DependsOn": "IncomingSQS",
12
+ "Type": "AWS::Events::Rule",
13
+ "Properties": {
14
+ "State":{
15
+ "Ref": "PgDiceEnabled"
16
+ },
17
+ "Description": " PgDice daily add partitions",
18
+ "Name": "PgDiceDailyAddPartitions",
19
+ "ScheduleExpression": "rate(1 day)",
20
+ "Targets": [
21
+ {
22
+ "Arn": {
23
+ "Fn::GetAtt": [
24
+ "IncomingSQS",
25
+ "Arn"
26
+ ]
27
+ },
28
+ "Id": "PgDiceDailyAddPartitionsId",
29
+ "Input": "{\"event_type\":\"task\",\"task\":\"add_new_partitions\"}"
30
+ }
31
+ ]
32
+ }
33
+ },
34
+ "PgDiceDailyDropPartitions": {
35
+ "DependsOn": "IncomingSQS",
36
+ "Type": "AWS::Events::Rule",
37
+ "Properties": {
38
+ "State":{
39
+ "Ref": "PgDiceEnabled"
40
+ },
41
+ "Description": " PgDice daily drop partitions",
42
+ "Name": "PgDiceDailyDropPartitions",
43
+ "ScheduleExpression": "rate(1 day)",
44
+ "Targets": [
45
+ {
46
+ "Arn": {
47
+ "Fn::GetAtt": [
48
+ "IncomingSQS",
49
+ "Arn"
50
+ ]
51
+ },
52
+ "Id": "PgDiceDailyDropPartitionsId",
53
+ "Input": "{\"event_type\":\"task\",\"task\":\"drop_old_partitions\"}"
54
+ }
55
+ ]
56
+ }
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-sqs'
4
+
5
+ # READ_ONLY_SQS can be set to ensure we don't delete good messages
6
+ class SqsListener
7
+ DEFAULT_VISIBILITY_TIMEOUT ||= 600
8
+ attr_reader :logger, :queue_url, :visibility_timeout
9
+
10
+ def initialize(opts = {})
11
+ @logger = opts[:logger] ||= Sidekiq.logger
12
+ @queue_url = opts[:queue_url] ||= ENV['SqsQueueUrl']
13
+ @sqs_client = opts[:sqs_client] ||= Aws::SQS::Client.new
14
+ @sqs_event_router = opts[:sqs_event_router] ||= SqsEventRouter.new(logger: logger)
15
+ increase_timeout_resolver = opts[:increase_timeout_resolver] ||= -> { ENV['READ_ONLY_SQS'].to_s == 'true' }
16
+ @visibility_timeout = calculate_visibility_timeout(increase_timeout_resolver.call)
17
+
18
+ logger.debug { "Running in environment: #{ENV['RAILS_ENV']} and using sqs queue: #{queue_url}" }
19
+ end
20
+
21
+ # http://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/sqs-example-get-messages-with-long-polling.html
22
+ def call
23
+ # This uses long polling to retrieve sqs events so we can process them
24
+ response = @sqs_client.receive_message(queue_url: queue_url,
25
+ max_number_of_messages: 10,
26
+ wait_time_seconds: 20,
27
+ visibility_timeout: visibility_timeout)
28
+
29
+ if response.messages&.size&.positive?
30
+ logger.debug { "The number of messages received from the queue was: #{response.messages&.size}" }
31
+ end
32
+
33
+ # Iterate over all the messages in the response (Response is a Struct which acts like an object with methods)
34
+ response.messages&.each do |message|
35
+ @sqs_event_router.handle_message(message)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def calculate_visibility_timeout(increase_timeout)
42
+ visibility_timeout = increase_timeout ? DEFAULT_VISIBILITY_TIMEOUT * 4 : DEFAULT_VISIBILITY_TIMEOUT
43
+
44
+ logger.info { "Visibility timeout set to: #{visibility_timeout} seconds" }
45
+ visibility_timeout
46
+ end
47
+ end