messaging 3.6.2 → 3.7.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 +4 -4
- data/.circleci/config.yml +36 -34
- data/.env +1 -0
- data/Gemfile.lock +14 -30
- data/docker-compose.yml +62 -0
- data/lib/messaging/adapters/kafka/producer.rb +2 -2
- data/lib/messaging/adapters/postgres/categories/row.rb +30 -0
- data/lib/messaging/adapters/postgres/categories.rb +28 -13
- data/lib/messaging/adapters/postgres/category.rb +7 -21
- data/lib/messaging/adapters/postgres/category_with_partitions.rb +89 -0
- data/lib/messaging/adapters/postgres/store.rb +9 -0
- data/lib/messaging/adapters/test/category.rb +0 -4
- data/lib/messaging/message.rb +4 -0
- data/lib/messaging/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 33403486d5e5dad21b6ed8b8d5c8e99d090ce19d79f5278e0aa5ee43dd43c8d5
|
|
4
|
+
data.tar.gz: d174552154912fe26401443c8b5e74b202c0faeb054f8ed6affad299843da91d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4477a3ec21fd27ca4735741ec690c8a9f9308c14b18b4c2236a04ba3991dec1fe16359896d1058bb5cf36fda9b2d119cdb8f79870237d1a4d4bed05be6561ac7
|
|
7
|
+
data.tar.gz: b8ec79f61ac86766d586f166053f829e7329f06e57366cfc88586ffd4a9d28d5047958f9fcf35f1c8ba50f489d48404b623d572197e10b7cdc8f654def0cb5f6
|
data/.circleci/config.yml
CHANGED
|
@@ -2,51 +2,53 @@
|
|
|
2
2
|
#
|
|
3
3
|
# Check https://circleci.com/docs/2.0/language-ruby/ for more details
|
|
4
4
|
#
|
|
5
|
-
version: 2
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
docker:
|
|
9
|
-
- image: circleci/ruby:2.4.1-node-browsers
|
|
10
|
-
environment:
|
|
11
|
-
- CC_TEST_REPORTER_ID: 94ada9b95ee3f232a6e984809d37917cfee90ac47805429e8c49742b2e8d2276
|
|
12
|
-
- RAILS_ENV: test
|
|
13
|
-
- image: circleci/postgres:12.5-ram
|
|
14
|
-
environment:
|
|
15
|
-
- POSTGRES_HOST_AUTH_METHOD: trust
|
|
16
|
-
- POSTGRES_PASSWORD: password
|
|
5
|
+
version: 2.1
|
|
6
|
+
orbs:
|
|
7
|
+
slack: circleci/slack@4.8.2
|
|
17
8
|
|
|
18
|
-
|
|
9
|
+
shared: &shared
|
|
10
|
+
docker:
|
|
11
|
+
# https://github.com/bukowskis/fineart/wiki/Updating-the-development-docker-image-for-Circle-CI
|
|
12
|
+
- image: ghcr.io/bukowskis/base-image/development:ruby-2.6.6-v5
|
|
13
|
+
auth:
|
|
14
|
+
username: $GHCR_USER
|
|
15
|
+
password: $GHCR_PASSWORD
|
|
16
|
+
- image: cimg/postgres:12.11
|
|
17
|
+
environment:
|
|
18
|
+
- POSTGRES_HOST_AUTH_METHOD: trust
|
|
19
|
+
resource_class: large
|
|
20
|
+
environment:
|
|
21
|
+
- CC_TEST_REPORTER_ID: 94ada9b95ee3f232a6e984809d37917cfee90ac47805429e8c49742b2e8d2276
|
|
22
|
+
- DISABLE_SPRING: 1
|
|
23
|
+
- RACK_ENV: test
|
|
19
24
|
|
|
25
|
+
commands:
|
|
26
|
+
bundle_install:
|
|
20
27
|
steps:
|
|
21
|
-
- run:
|
|
22
|
-
name: Install aptitude because it has much more helpful error messages when ending up in dependency hell
|
|
23
|
-
command: sudo apt-get install --assume-yes aptitude
|
|
24
|
-
|
|
25
|
-
- run:
|
|
26
|
-
name: Install Postgres Client
|
|
27
|
-
command: sudo aptitude -y install postgresql-client
|
|
28
|
-
|
|
29
|
-
- checkout
|
|
30
|
-
|
|
31
|
-
- run:
|
|
32
|
-
name: Setup Code Climate test-reporter
|
|
33
|
-
command: |
|
|
34
|
-
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
|
35
|
-
chmod +x ./cc-test-reporter
|
|
36
|
-
|
|
37
28
|
- restore_cache:
|
|
38
29
|
name: Restore Bundler Cache
|
|
39
|
-
key:
|
|
30
|
+
key: bukowskis-bundle-{{ .Environment.CACHE_VERSION }}-{{ checksum "Gemfile.lock" }}
|
|
40
31
|
|
|
41
32
|
- run:
|
|
42
|
-
name:
|
|
43
|
-
command: bundle install --jobs=4 --retry=3
|
|
33
|
+
name: Bundle Install
|
|
34
|
+
command: bundle install --path=vendor/bundle --jobs=4 --retry=3
|
|
44
35
|
|
|
45
36
|
- save_cache:
|
|
46
|
-
key:
|
|
37
|
+
key: bukowskis-bundle-{{ .Environment.CACHE_VERSION }}-{{ checksum "Gemfile.lock" }}
|
|
47
38
|
paths:
|
|
48
39
|
- vendor/bundle
|
|
49
40
|
|
|
41
|
+
jobs:
|
|
42
|
+
specs:
|
|
43
|
+
<<: *shared
|
|
44
|
+
steps:
|
|
45
|
+
- checkout
|
|
46
|
+
- bundle_install
|
|
47
|
+
- run:
|
|
48
|
+
name: Setup Code Climate test-reporter
|
|
49
|
+
command: |
|
|
50
|
+
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
|
51
|
+
chmod +x ./cc-test-reporter
|
|
50
52
|
- run:
|
|
51
53
|
name: Database Setup
|
|
52
54
|
command: |
|
|
@@ -65,4 +67,4 @@ workflows:
|
|
|
65
67
|
version: 2
|
|
66
68
|
messaging_specs:
|
|
67
69
|
jobs:
|
|
68
|
-
-
|
|
70
|
+
- specs
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
messaging (3.
|
|
4
|
+
messaging (3.7.1)
|
|
5
5
|
activerecord
|
|
6
6
|
activesupport
|
|
7
7
|
after_transaction
|
|
@@ -45,8 +45,8 @@ GEM
|
|
|
45
45
|
tzinfo (~> 1.1)
|
|
46
46
|
addressable (2.7.0)
|
|
47
47
|
public_suffix (>= 2.0.2, < 5.0)
|
|
48
|
-
after_transaction (0.0.
|
|
49
|
-
activerecord
|
|
48
|
+
after_transaction (0.0.5)
|
|
49
|
+
activerecord
|
|
50
50
|
arel (9.0.0)
|
|
51
51
|
axiom-types (0.1.1)
|
|
52
52
|
descendants_tracker (~> 0.0.4)
|
|
@@ -73,24 +73,23 @@ GEM
|
|
|
73
73
|
descendants_tracker (0.0.4)
|
|
74
74
|
thread_safe (~> 0.3, >= 0.3.1)
|
|
75
75
|
diff-lcs (1.3)
|
|
76
|
-
digest-crc (0.
|
|
76
|
+
digest-crc (0.6.0)
|
|
77
77
|
dnsruby (1.61.3)
|
|
78
78
|
addressable (~> 2.5)
|
|
79
79
|
docile (1.3.2)
|
|
80
|
-
dry-configurable (0.
|
|
80
|
+
dry-configurable (0.13.0)
|
|
81
81
|
concurrent-ruby (~> 1.0)
|
|
82
|
-
dry-core (~> 0.
|
|
83
|
-
dry-container (0.
|
|
82
|
+
dry-core (~> 0.6)
|
|
83
|
+
dry-container (0.9.0)
|
|
84
84
|
concurrent-ruby (~> 1.0)
|
|
85
|
-
dry-configurable (~> 0.
|
|
86
|
-
dry-core (0.
|
|
85
|
+
dry-configurable (~> 0.13, >= 0.13.0)
|
|
86
|
+
dry-core (0.7.1)
|
|
87
87
|
concurrent-ruby (~> 1.0)
|
|
88
|
-
dry-equalizer (0.
|
|
89
|
-
dry-initializer (3.0.
|
|
88
|
+
dry-equalizer (0.3.0)
|
|
89
|
+
dry-initializer (3.0.4)
|
|
90
90
|
em-websocket (0.5.1)
|
|
91
91
|
eventmachine (>= 0.12.9)
|
|
92
92
|
http_parser.rb (~> 0.6.0)
|
|
93
|
-
equalizer (0.0.11)
|
|
94
93
|
erubi (1.9.0)
|
|
95
94
|
ethon (0.12.0)
|
|
96
95
|
ffi (>= 1.3.0)
|
|
@@ -101,7 +100,6 @@ GEM
|
|
|
101
100
|
ffi (1.11.3)
|
|
102
101
|
forwardable-extended (2.6.0)
|
|
103
102
|
gemoji (3.0.1)
|
|
104
|
-
geocoder (1.2.13)
|
|
105
103
|
github-pages (203)
|
|
106
104
|
github-pages-health-check (= 1.16.1)
|
|
107
105
|
jekyll (= 3.8.5)
|
|
@@ -151,7 +149,6 @@ GEM
|
|
|
151
149
|
octokit (~> 4.0)
|
|
152
150
|
public_suffix (~> 3.0)
|
|
153
151
|
typhoeus (~> 1.3)
|
|
154
|
-
hashie (4.1.0)
|
|
155
152
|
html-pipeline (2.12.2)
|
|
156
153
|
activesupport (>= 2)
|
|
157
154
|
nokogiri (>= 1.4)
|
|
@@ -272,21 +269,11 @@ GEM
|
|
|
272
269
|
listen (3.2.1)
|
|
273
270
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
274
271
|
rb-inotify (~> 0.9, >= 0.9.10)
|
|
275
|
-
locality (1.1.0)
|
|
276
|
-
activesupport
|
|
277
|
-
geocoder (= 1.2.13)
|
|
278
|
-
hashie
|
|
279
|
-
i18n
|
|
280
|
-
maxminddb (= 0.1.8)
|
|
281
|
-
operation
|
|
282
|
-
trouble
|
|
283
272
|
loofah (2.4.0)
|
|
284
273
|
crass (~> 1.0.2)
|
|
285
274
|
nokogiri (>= 1.5.9)
|
|
286
|
-
maxminddb (0.1.8)
|
|
287
275
|
mercenary (0.3.6)
|
|
288
|
-
meter (1.
|
|
289
|
-
locality (~> 1.1.0)
|
|
276
|
+
meter (1.4.0)
|
|
290
277
|
useragent (~> 0.16.3)
|
|
291
278
|
method_object (1.0.0)
|
|
292
279
|
dry-initializer
|
|
@@ -303,7 +290,6 @@ GEM
|
|
|
303
290
|
mini_portile2 (~> 2.4.0)
|
|
304
291
|
octokit (4.14.0)
|
|
305
292
|
sawyer (~> 0.8.0, >= 0.5.3)
|
|
306
|
-
operation (1.4.1)
|
|
307
293
|
pathutil (0.16.2)
|
|
308
294
|
forwardable-extended (~> 2.6)
|
|
309
295
|
pg (1.1.4)
|
|
@@ -358,7 +344,7 @@ GEM
|
|
|
358
344
|
rspec-support (3.9.0)
|
|
359
345
|
ruby-enum (0.7.2)
|
|
360
346
|
i18n
|
|
361
|
-
ruby-kafka (1.
|
|
347
|
+
ruby-kafka (1.5.0)
|
|
362
348
|
digest-crc
|
|
363
349
|
rubyzip (1.3.0)
|
|
364
350
|
safe_yaml (1.0.5)
|
|
@@ -384,18 +370,16 @@ GEM
|
|
|
384
370
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
|
385
371
|
thor (0.20.3)
|
|
386
372
|
thread_safe (0.3.6)
|
|
387
|
-
trouble (0.0.13)
|
|
388
373
|
typhoeus (1.3.1)
|
|
389
374
|
ethon (>= 0.9.0)
|
|
390
375
|
tzinfo (1.2.5)
|
|
391
376
|
thread_safe (~> 0.1)
|
|
392
377
|
unicode-display_width (1.6.0)
|
|
393
378
|
useragent (0.16.10)
|
|
394
|
-
virtus (
|
|
379
|
+
virtus (2.0.0)
|
|
395
380
|
axiom-types (~> 0.1)
|
|
396
381
|
coercible (~> 1.0)
|
|
397
382
|
descendants_tracker (~> 0.0, >= 0.0.3)
|
|
398
|
-
equalizer (~> 0.0, >= 0.0.9)
|
|
399
383
|
|
|
400
384
|
PLATFORMS
|
|
401
385
|
ruby
|
data/docker-compose.yml
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
version: '3.4'
|
|
2
|
+
|
|
3
|
+
x-shared: &shared
|
|
4
|
+
tmpfs:
|
|
5
|
+
- /tmp
|
|
6
|
+
environment:
|
|
7
|
+
- RAILS_ENV=${RAILS_ENV:-development}
|
|
8
|
+
- BOOTSNAP_CACHE_DIR=/rails_cache/bootsnap
|
|
9
|
+
volumes:
|
|
10
|
+
- .:/app
|
|
11
|
+
- bundle:/usr/local/bundle
|
|
12
|
+
networks:
|
|
13
|
+
- ${NETWORK_NAME:-default}
|
|
14
|
+
|
|
15
|
+
services:
|
|
16
|
+
runner:
|
|
17
|
+
<<: *shared
|
|
18
|
+
image: ghcr.io/bukowskis/base-image/development:ruby-2.6.6-v5
|
|
19
|
+
stdin_open: true
|
|
20
|
+
tty: true
|
|
21
|
+
command: /bin/sh
|
|
22
|
+
|
|
23
|
+
postgres:
|
|
24
|
+
image: postgres:${PG_VERSION}
|
|
25
|
+
volumes:
|
|
26
|
+
- pg_data:/var/lib/postgresql/data
|
|
27
|
+
ports:
|
|
28
|
+
- 5432:5432
|
|
29
|
+
environment:
|
|
30
|
+
- POSTGRES_HOST_AUTH_METHOD=trust
|
|
31
|
+
|
|
32
|
+
zookeeper:
|
|
33
|
+
image: zookeeper
|
|
34
|
+
ports:
|
|
35
|
+
- "2181:2181"
|
|
36
|
+
|
|
37
|
+
kafka:
|
|
38
|
+
image: niks123123/kafka:2.7.0
|
|
39
|
+
ports:
|
|
40
|
+
- "9092:9092"
|
|
41
|
+
depends_on:
|
|
42
|
+
- zookeeper
|
|
43
|
+
environment:
|
|
44
|
+
KAFKA_ADVERTISED_HOST_NAME: kafka
|
|
45
|
+
KAFKA_ADVERTISED_PORT: 9092
|
|
46
|
+
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
|
47
|
+
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true'
|
|
48
|
+
volumes:
|
|
49
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
50
|
+
|
|
51
|
+
volumes:
|
|
52
|
+
bundle:
|
|
53
|
+
name: bundle
|
|
54
|
+
pg_data:
|
|
55
|
+
name: pg_data_${PG_VERSION}
|
|
56
|
+
|
|
57
|
+
networks:
|
|
58
|
+
default:
|
|
59
|
+
name: messaging_default
|
|
60
|
+
external:
|
|
61
|
+
name: ${EXTERNAL_NETWORK}
|
|
62
|
+
external: true
|
|
@@ -20,8 +20,8 @@ module Messaging
|
|
|
20
20
|
def call(message)
|
|
21
21
|
reconnect if forked?
|
|
22
22
|
producer.produce(message.to_json, key: message.message_key, topic: message.topic)
|
|
23
|
-
rescue ::Kafka::BufferOverflow =>
|
|
24
|
-
|
|
23
|
+
rescue ::Kafka::BufferOverflow => e
|
|
24
|
+
ExceptionHandler.call(e, message: message.to_json)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def shutdown
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Messaging
|
|
2
|
+
module Adapters
|
|
3
|
+
class Postgres
|
|
4
|
+
class Categories
|
|
5
|
+
class Row
|
|
6
|
+
extend Dry::Initializer
|
|
7
|
+
|
|
8
|
+
param :table_name
|
|
9
|
+
param :type
|
|
10
|
+
param :expression
|
|
11
|
+
|
|
12
|
+
def category_class
|
|
13
|
+
return CategoryWithPartitions if type == 'p'
|
|
14
|
+
|
|
15
|
+
Category
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def category_name
|
|
19
|
+
regexp = /FOR VALUES IN \(\'(.*)\'\)/
|
|
20
|
+
expression.match(regexp)[1]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_category
|
|
24
|
+
category_class.new(category_name, table_name)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -5,9 +5,7 @@ module Messaging
|
|
|
5
5
|
include Enumerable
|
|
6
6
|
|
|
7
7
|
def each
|
|
8
|
-
all_categories.each
|
|
9
|
-
yield Category.new(name)
|
|
10
|
-
end
|
|
8
|
+
all_categories.each { |c| yield c }
|
|
11
9
|
end
|
|
12
10
|
|
|
13
11
|
# Get a category by name
|
|
@@ -16,10 +14,7 @@ module Messaging
|
|
|
16
14
|
# @return [nil] if no category exists with the given name
|
|
17
15
|
# @return [Category]
|
|
18
16
|
def [](name)
|
|
19
|
-
|
|
20
|
-
return unless category
|
|
21
|
-
|
|
22
|
-
Category.new(category)
|
|
17
|
+
all_categories.find { |c| c.name == name }
|
|
23
18
|
end
|
|
24
19
|
|
|
25
20
|
# Creates a table partition for the given category
|
|
@@ -27,20 +22,36 @@ module Messaging
|
|
|
27
22
|
# @param name [String] the name of the category
|
|
28
23
|
# @return [Category]
|
|
29
24
|
def create(name)
|
|
30
|
-
table_name =
|
|
25
|
+
table_name = Category.table_name_for(name)
|
|
31
26
|
sql = <<~SQL
|
|
32
27
|
CREATE TABLE messaging.#{table_name}
|
|
33
28
|
PARTITION OF messaging.messages FOR VALUES IN ('#{name}');
|
|
34
29
|
SQL
|
|
35
30
|
connection.execute sql
|
|
36
|
-
Category.new(name)
|
|
31
|
+
Category.new(name, table_name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Creates a table partition for the given category
|
|
35
|
+
# that in turn is partitioned based on created_at
|
|
36
|
+
#
|
|
37
|
+
# @param name [String] the name of the category
|
|
38
|
+
# @return [CategoryWithPartitions]
|
|
39
|
+
def create_and_partition_by_day(name)
|
|
40
|
+
table_name = Category.table_name_for(name)
|
|
41
|
+
sql = <<~SQL
|
|
42
|
+
CREATE TABLE messaging.#{table_name}
|
|
43
|
+
PARTITION OF messaging.messages FOR VALUES IN ('#{name}')
|
|
44
|
+
PARTITION BY RANGE (created_at);
|
|
45
|
+
SQL
|
|
46
|
+
connection.execute sql
|
|
47
|
+
CategoryWithPartitions.new(name, table_name)
|
|
37
48
|
end
|
|
38
49
|
|
|
39
50
|
# Drops the table partition (including all messages) for the given category
|
|
40
51
|
#
|
|
41
52
|
# @param name [String] the name of the category
|
|
42
53
|
def drop(name)
|
|
43
|
-
table_name =
|
|
54
|
+
table_name = Category.table_name_for(name)
|
|
44
55
|
sql = <<~SQL
|
|
45
56
|
drop TABLE messaging.#{table_name}
|
|
46
57
|
SQL
|
|
@@ -55,15 +66,19 @@ module Messaging
|
|
|
55
66
|
|
|
56
67
|
def fetch_categories
|
|
57
68
|
sql = <<~SQL
|
|
58
|
-
SELECT child.relname AS category
|
|
69
|
+
SELECT child.relname AS category,
|
|
70
|
+
child.relkind AS category_type,
|
|
71
|
+
pg_get_expr(child.relpartbound, child.oid, true) AS expression
|
|
59
72
|
FROM pg_inherits
|
|
60
73
|
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
|
|
61
74
|
JOIN pg_class child ON pg_inherits.inhrelid = child.oid
|
|
62
75
|
JOIN pg_namespace ON pg_namespace.oid = parent.relnamespace
|
|
63
|
-
WHERE pg_namespace.nspname = 'messaging'
|
|
76
|
+
WHERE pg_namespace.nspname = 'messaging'
|
|
77
|
+
AND parent.relname = 'messages'
|
|
78
|
+
AND child.relkind in ('r', 'p')
|
|
64
79
|
ORDER BY category
|
|
65
80
|
SQL
|
|
66
|
-
connection.
|
|
81
|
+
connection.select_rows(sql).map { |r| Row.new(*r).to_category }
|
|
67
82
|
end
|
|
68
83
|
|
|
69
84
|
def connection
|
|
@@ -2,16 +2,13 @@ module Messaging
|
|
|
2
2
|
module Adapters
|
|
3
3
|
class Postgres
|
|
4
4
|
class Category
|
|
5
|
-
|
|
6
|
-
attr_reader :name
|
|
5
|
+
extend Dry::Initializer
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def initialize(name)
|
|
14
|
-
@name = name
|
|
7
|
+
param :name
|
|
8
|
+
param :table_name, default: -> { self.class.table_name_for(name) }
|
|
9
|
+
|
|
10
|
+
def self.table_name_for(name)
|
|
11
|
+
name.parameterize(separator: '_')
|
|
15
12
|
end
|
|
16
13
|
|
|
17
14
|
# Access to all messages in the category sorted by created_at
|
|
@@ -20,19 +17,8 @@ module Messaging
|
|
|
20
17
|
SerializedMessage.where(stream_category: name).order(:created_at)
|
|
21
18
|
end
|
|
22
19
|
|
|
23
|
-
def messages_older_than(time)
|
|
24
|
-
messages.where('created_at < ?', time)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def delete_messages_older_than!(time)
|
|
28
|
-
SerializedMessage.transaction do
|
|
29
|
-
ActiveRecord::Base.connection.execute "SET LOCAL statement_timeout = '0'"
|
|
30
|
-
messages_older_than(time).delete_all
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
20
|
def inspect
|
|
35
|
-
"#<Category
|
|
21
|
+
"#<Category: #{name}>"
|
|
36
22
|
end
|
|
37
23
|
end
|
|
38
24
|
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Messaging
|
|
2
|
+
module Adapters
|
|
3
|
+
class Postgres
|
|
4
|
+
class CategoryWithPartitions < Category
|
|
5
|
+
def add_partition(date:)
|
|
6
|
+
from, to = partition_range_for(date: date)
|
|
7
|
+
partition_name = partition_name_for(date: from)
|
|
8
|
+
|
|
9
|
+
return if partition_exists?(partition_name)
|
|
10
|
+
|
|
11
|
+
sql = <<~SQL
|
|
12
|
+
CREATE TABLE IF NOT EXISTS messaging.#{partition_name}
|
|
13
|
+
PARTITION OF messaging.#{table_name} FOR VALUES FROM ('#{from}') TO ('#{to}')
|
|
14
|
+
SQL
|
|
15
|
+
|
|
16
|
+
SerializedMessage.transaction do
|
|
17
|
+
AdvisoryTransactionLock.call key: partition_name
|
|
18
|
+
SerializedMessage.connection.execute sql
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Creates multiple partitions
|
|
23
|
+
#
|
|
24
|
+
# @param start_date [Date] from which date to create partitions
|
|
25
|
+
# @param days [Integer] how many days worth of partitions to create
|
|
26
|
+
def add_partitions(start_date:, days: 1)
|
|
27
|
+
first = start_date.to_date
|
|
28
|
+
last = first + (days - 1)
|
|
29
|
+
|
|
30
|
+
(first..last).each do |date|
|
|
31
|
+
add_partition(date: date)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Removes a partition including the included messages
|
|
36
|
+
#
|
|
37
|
+
# @param partition_name [String] the name of the partition to drop
|
|
38
|
+
def drop_partition(partition_name)
|
|
39
|
+
return unless partition_name.match?(/^#{table_name}_\d{4}_\d{2}_\d{2}$/)
|
|
40
|
+
|
|
41
|
+
SerializedMessage.connection.execute "drop TABLE messaging.#{partition_name}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Removes all partitions older than the given date
|
|
45
|
+
#
|
|
46
|
+
# @param date [Date] the cutoff date
|
|
47
|
+
def drop_partitions_older_than(date)
|
|
48
|
+
max_partition_name = partition_name_for(date: date)
|
|
49
|
+
partitions.select { |p| p < max_partition_name }.each do |p|
|
|
50
|
+
drop_partition(p)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def partition_name_for(date:)
|
|
55
|
+
"#{name}_%d_%02d_%02d" % [date.year, date.month, date.day]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def partition_range_for(date:)
|
|
59
|
+
from = date.to_time.beginning_of_day
|
|
60
|
+
to = from + 1.day
|
|
61
|
+
[from, to]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def partition_exists?(partition_name)
|
|
65
|
+
partitions.include? partition_name
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def partitions
|
|
69
|
+
sql = <<~SQL
|
|
70
|
+
SELECT child.relname AS name
|
|
71
|
+
FROM pg_inherits
|
|
72
|
+
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
|
|
73
|
+
JOIN pg_class child ON pg_inherits.inhrelid = child.oid
|
|
74
|
+
JOIN pg_namespace ON pg_namespace.oid = parent.relnamespace
|
|
75
|
+
WHERE pg_namespace.nspname = 'messaging'
|
|
76
|
+
AND parent.relname = '#{table_name}'
|
|
77
|
+
AND child.relkind = 'r'
|
|
78
|
+
ORDER BY name
|
|
79
|
+
SQL
|
|
80
|
+
SerializedMessage.connection.select_values(sql)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def inspect
|
|
84
|
+
"#<CategoryWithPartitions: #{name}>"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require_relative 'category'
|
|
2
|
+
require_relative 'category_with_partitions'
|
|
2
3
|
require_relative 'categories'
|
|
4
|
+
require_relative 'categories/row'
|
|
3
5
|
require_relative 'stream'
|
|
4
6
|
require_relative 'streams'
|
|
5
7
|
|
|
@@ -83,6 +85,13 @@ module Messaging
|
|
|
83
85
|
return message unless message.stream_name
|
|
84
86
|
|
|
85
87
|
SerializedMessage.create!(message: message).to_message
|
|
88
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
89
|
+
category = message.category
|
|
90
|
+
raise e unless e.message.include?('no partition of relation')
|
|
91
|
+
raise e unless category || category.is_a?(CategoryWithPartitions)
|
|
92
|
+
|
|
93
|
+
category.add_partition(date: Date.today)
|
|
94
|
+
retry
|
|
86
95
|
end
|
|
87
96
|
end
|
|
88
97
|
end
|
data/lib/messaging/message.rb
CHANGED
data/lib/messaging/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: messaging
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bukowskis
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-01-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -271,6 +271,7 @@ extensions: []
|
|
|
271
271
|
extra_rdoc_files: []
|
|
272
272
|
files:
|
|
273
273
|
- ".circleci/config.yml"
|
|
274
|
+
- ".env"
|
|
274
275
|
- ".gitignore"
|
|
275
276
|
- ".rspec"
|
|
276
277
|
- Gemfile
|
|
@@ -282,6 +283,7 @@ files:
|
|
|
282
283
|
- _layouts/single.html
|
|
283
284
|
- bin/console
|
|
284
285
|
- bin/setup
|
|
286
|
+
- docker-compose.yml
|
|
285
287
|
- docs/adapters.md
|
|
286
288
|
- docs/consuming.md
|
|
287
289
|
- docs/installation.md
|
|
@@ -297,7 +299,9 @@ files:
|
|
|
297
299
|
- lib/messaging/adapters/postgres.rb
|
|
298
300
|
- lib/messaging/adapters/postgres/advisory_transaction_lock.rb
|
|
299
301
|
- lib/messaging/adapters/postgres/categories.rb
|
|
302
|
+
- lib/messaging/adapters/postgres/categories/row.rb
|
|
300
303
|
- lib/messaging/adapters/postgres/category.rb
|
|
304
|
+
- lib/messaging/adapters/postgres/category_with_partitions.rb
|
|
301
305
|
- lib/messaging/adapters/postgres/serialized_message.rb
|
|
302
306
|
- lib/messaging/adapters/postgres/store.rb
|
|
303
307
|
- lib/messaging/adapters/postgres/stream.rb
|