@haathie/pgmb 0.2.12 → 0.2.14
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.
- package/package.json +2 -2
- package/sql/pgmb-0.2.8-0.2.14.sql +113 -0
- package/sql/pgmb.sql +39 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haathie/pgmb",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "PG message broker, with a type-safe typescript client with built-in webhook & SSE support.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"build": "tsc -p tsconfig.build.json",
|
|
16
16
|
"lint": "eslint ./ --ext .js,.ts,.jsx,.tsx",
|
|
17
17
|
"lint:fix": "eslint ./ --fix --ext .js,.ts,.jsx,.tsx",
|
|
18
|
-
"benchmark": "TZ=utc node --env-file ./.env.test
|
|
18
|
+
"benchmark": "TZ=utc node --env-file ./.env.test benchmark/run.ts",
|
|
19
19
|
"pg:typegen": "pgtyped --config ./pgtyped.config.json"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
SET search_path TO pgmb;
|
|
2
|
+
|
|
3
|
+
-- Partition maintenance function for events table. Creates partitions for
|
|
4
|
+
-- the current and next interval. Deletes partitions that are older than the
|
|
5
|
+
-- configured time interval.
|
|
6
|
+
-- Exact partition size and oldest partition interval can be configured
|
|
7
|
+
-- using the "subscriptions_config" table.
|
|
8
|
+
CREATE OR REPLACE FUNCTION maintain_time_partitions_using_event_id(
|
|
9
|
+
table_id regclass,
|
|
10
|
+
partition_interval INTERVAL,
|
|
11
|
+
future_interval INTERVAL,
|
|
12
|
+
retention_period INTERVAL,
|
|
13
|
+
additional_sql TEXT DEFAULT NULL,
|
|
14
|
+
current_ts timestamptz DEFAULT NOW()
|
|
15
|
+
)
|
|
16
|
+
RETURNS void AS $$
|
|
17
|
+
DECLARE
|
|
18
|
+
ts_trunc timestamptz := date_bin(partition_interval, current_ts, '2000-1-1');
|
|
19
|
+
oldest_pt_to_keep text := pgmb
|
|
20
|
+
.get_time_partition_name(table_id, ts_trunc - retention_period);
|
|
21
|
+
p_info RECORD;
|
|
22
|
+
lock_key CONSTANT BIGINT :=
|
|
23
|
+
hashtext('pgmb.maintain_tp.' || table_id::text);
|
|
24
|
+
ranges_to_create tstzrange[];
|
|
25
|
+
cur_range tstzrange;
|
|
26
|
+
max_retries constant int = 50;
|
|
27
|
+
BEGIN
|
|
28
|
+
ASSERT partition_interval >= interval '1 minute',
|
|
29
|
+
'partition_interval must be at least 1 minute';
|
|
30
|
+
ASSERT future_interval >= partition_interval,
|
|
31
|
+
'future_interval must be at least as large as partition_interval';
|
|
32
|
+
|
|
33
|
+
IF NOT pg_try_advisory_xact_lock(lock_key) THEN
|
|
34
|
+
-- another process is already maintaining partitions for this table
|
|
35
|
+
RETURN;
|
|
36
|
+
END IF;
|
|
37
|
+
|
|
38
|
+
-- find all intervals we need to create partitions for
|
|
39
|
+
WITH existing_part_ranges AS (
|
|
40
|
+
SELECT
|
|
41
|
+
tstzrange(
|
|
42
|
+
extract_date_from_event_id(lower_bound),
|
|
43
|
+
extract_date_from_event_id(upper_bound),
|
|
44
|
+
'[]'
|
|
45
|
+
) as range
|
|
46
|
+
FROM pgmb.get_partitions_and_bounds(table_id)
|
|
47
|
+
),
|
|
48
|
+
future_tzs AS (
|
|
49
|
+
SELECT
|
|
50
|
+
tstzrange(dt, dt + partition_interval, '[]') AS range
|
|
51
|
+
FROM generate_series(
|
|
52
|
+
ts_trunc,
|
|
53
|
+
ts_trunc + future_interval,
|
|
54
|
+
partition_interval
|
|
55
|
+
) AS gs(dt)
|
|
56
|
+
),
|
|
57
|
+
diffs AS (
|
|
58
|
+
SELECT
|
|
59
|
+
CASE WHEN epr.range IS NOT NULL
|
|
60
|
+
THEN (ftz.range::tstzmultirange - epr.range::tstzmultirange)
|
|
61
|
+
ELSE ftz.range::tstzmultirange
|
|
62
|
+
END AS ranges
|
|
63
|
+
FROM future_tzs ftz
|
|
64
|
+
LEFT JOIN existing_part_ranges epr ON ftz.range && epr.range
|
|
65
|
+
)
|
|
66
|
+
select ARRAY_AGG(u.range) FROM diffs
|
|
67
|
+
CROSS JOIN LATERAL unnest(diffs.ranges) AS u(range)
|
|
68
|
+
INTO ranges_to_create;
|
|
69
|
+
|
|
70
|
+
ranges_to_create := COALESCE(ranges_to_create, ARRAY[]::tstzrange[]);
|
|
71
|
+
|
|
72
|
+
FOR i IN 1..max_retries LOOP
|
|
73
|
+
BEGIN
|
|
74
|
+
-- go from now to future_interval
|
|
75
|
+
FOREACH cur_range IN ARRAY ranges_to_create LOOP
|
|
76
|
+
DECLARE
|
|
77
|
+
start_ev_id event_id := pgmb.create_event_id(lower(cur_range), 0);
|
|
78
|
+
end_ev_id event_id := pgmb.create_event_id(upper(cur_range), 0);
|
|
79
|
+
pt_name TEXT := pgmb.get_time_partition_name(table_id, lower(cur_range));
|
|
80
|
+
BEGIN
|
|
81
|
+
RAISE NOTICE 'creating partition "%". start: %, end: %',
|
|
82
|
+
pt_name, lower(cur_range), upper(cur_range);
|
|
83
|
+
|
|
84
|
+
EXECUTE FORMAT(
|
|
85
|
+
'CREATE TABLE %I PARTITION OF %I FOR VALUES FROM (%L) TO (%L)',
|
|
86
|
+
pt_name, table_id, start_ev_id, end_ev_id
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
IF additional_sql IS NOT NULL THEN
|
|
90
|
+
EXECUTE REPLACE(additional_sql, '$1', pt_name);
|
|
91
|
+
END IF;
|
|
92
|
+
END;
|
|
93
|
+
END LOOP;
|
|
94
|
+
|
|
95
|
+
-- Drop old partitions
|
|
96
|
+
FOR p_info IN (
|
|
97
|
+
SELECT inhrelid::regclass AS child
|
|
98
|
+
FROM pg_catalog.pg_inherits
|
|
99
|
+
WHERE inhparent = table_id
|
|
100
|
+
AND inhrelid::regclass::text < oldest_pt_to_keep
|
|
101
|
+
) LOOP
|
|
102
|
+
EXECUTE format('DROP TABLE %I', p_info.child);
|
|
103
|
+
END LOOP;
|
|
104
|
+
EXIT;
|
|
105
|
+
EXCEPTION WHEN lock_not_available OR deadlock_detected THEN
|
|
106
|
+
IF i = max_retries THEN
|
|
107
|
+
RAISE;
|
|
108
|
+
END IF;
|
|
109
|
+
PERFORM pg_sleep(1);
|
|
110
|
+
END;
|
|
111
|
+
END LOOP;
|
|
112
|
+
END;
|
|
113
|
+
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE SECURITY DEFINER;
|
package/sql/pgmb.sql
CHANGED
|
@@ -43,12 +43,12 @@ $$ LANGUAGE sql STRICT STABLE PARALLEL SAFE SET SEARCH_PATH TO pgmb;
|
|
|
43
43
|
INSERT INTO config(id, value) VALUES
|
|
44
44
|
('plugin_version', '0.2.0'),
|
|
45
45
|
('partition_retention_period', '60 minutes'),
|
|
46
|
-
('future_intervals_to_create', '
|
|
46
|
+
('future_intervals_to_create', '3 hours'),
|
|
47
47
|
('partition_interval', '30 minutes'),
|
|
48
48
|
('poll_chunk_size', '10000'),
|
|
49
49
|
('pg_cron_poll_for_events_cron', '1 second'),
|
|
50
50
|
-- every 30 minutes
|
|
51
|
-
('pg_cron_partition_maintenance_cron', '
|
|
51
|
+
('pg_cron_partition_maintenance_cron', '0 * * * *');
|
|
52
52
|
|
|
53
53
|
-- we'll create the events table next & its functions ---------------
|
|
54
54
|
|
|
@@ -211,6 +211,7 @@ DECLARE
|
|
|
211
211
|
hashtext('pgmb.maintain_tp.' || table_id::text);
|
|
212
212
|
ranges_to_create tstzrange[];
|
|
213
213
|
cur_range tstzrange;
|
|
214
|
+
max_retries constant int = 50;
|
|
214
215
|
BEGIN
|
|
215
216
|
ASSERT partition_interval >= interval '1 minute',
|
|
216
217
|
'partition_interval must be at least 1 minute';
|
|
@@ -256,36 +257,46 @@ BEGIN
|
|
|
256
257
|
|
|
257
258
|
ranges_to_create := COALESCE(ranges_to_create, ARRAY[]::tstzrange[]);
|
|
258
259
|
|
|
259
|
-
|
|
260
|
-
FOREACH cur_range IN ARRAY ranges_to_create LOOP
|
|
261
|
-
DECLARE
|
|
262
|
-
start_ev_id event_id := pgmb.create_event_id(lower(cur_range), 0);
|
|
263
|
-
end_ev_id event_id := pgmb.create_event_id(upper(cur_range), 0);
|
|
264
|
-
pt_name TEXT := pgmb.get_time_partition_name(table_id, lower(cur_range));
|
|
260
|
+
FOR i IN 1..max_retries LOOP
|
|
265
261
|
BEGIN
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
262
|
+
-- go from now to future_interval
|
|
263
|
+
FOREACH cur_range IN ARRAY ranges_to_create LOOP
|
|
264
|
+
DECLARE
|
|
265
|
+
start_ev_id event_id := pgmb.create_event_id(lower(cur_range), 0);
|
|
266
|
+
end_ev_id event_id := pgmb.create_event_id(upper(cur_range), 0);
|
|
267
|
+
pt_name TEXT := pgmb.get_time_partition_name(table_id, lower(cur_range));
|
|
268
|
+
BEGIN
|
|
269
|
+
RAISE NOTICE 'creating partition "%". start: %, end: %',
|
|
270
|
+
pt_name, lower(cur_range), upper(cur_range);
|
|
271
|
+
|
|
272
|
+
EXECUTE FORMAT(
|
|
273
|
+
'CREATE TABLE %I PARTITION OF %I FOR VALUES FROM (%L) TO (%L)',
|
|
274
|
+
pt_name, table_id, start_ev_id, end_ev_id
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
IF additional_sql IS NOT NULL THEN
|
|
278
|
+
EXECUTE REPLACE(additional_sql, '$1', pt_name);
|
|
279
|
+
END IF;
|
|
280
|
+
END;
|
|
281
|
+
END LOOP;
|
|
282
|
+
|
|
283
|
+
-- Drop old partitions
|
|
284
|
+
FOR p_info IN (
|
|
285
|
+
SELECT inhrelid::regclass AS child
|
|
286
|
+
FROM pg_catalog.pg_inherits
|
|
287
|
+
WHERE inhparent = table_id
|
|
288
|
+
AND inhrelid::regclass::text < oldest_pt_to_keep
|
|
289
|
+
) LOOP
|
|
290
|
+
EXECUTE format('DROP TABLE %I', p_info.child);
|
|
291
|
+
END LOOP;
|
|
292
|
+
EXIT;
|
|
293
|
+
EXCEPTION WHEN lock_not_available OR deadlock_detected THEN
|
|
294
|
+
IF i = max_retries THEN
|
|
295
|
+
RAISE;
|
|
276
296
|
END IF;
|
|
297
|
+
PERFORM pg_sleep(1);
|
|
277
298
|
END;
|
|
278
299
|
END LOOP;
|
|
279
|
-
|
|
280
|
-
-- Drop old partitions
|
|
281
|
-
FOR p_info IN (
|
|
282
|
-
SELECT inhrelid::regclass AS child
|
|
283
|
-
FROM pg_catalog.pg_inherits
|
|
284
|
-
WHERE inhparent = table_id
|
|
285
|
-
AND inhrelid::regclass::text < oldest_pt_to_keep
|
|
286
|
-
) LOOP
|
|
287
|
-
EXECUTE format('DROP TABLE %I', p_info.child);
|
|
288
|
-
END LOOP;
|
|
289
300
|
END;
|
|
290
301
|
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE SECURITY DEFINER;
|
|
291
302
|
|