@haathie/pgmb 0.2.13 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haathie/pgmb",
3
- "version": "0.2.13",
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 src/benchmark/run.ts",
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', '120 minutes'),
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', '*/30 * * * *');
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
- -- go from now to future_interval
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
- RAISE NOTICE 'creating partition "%". start: %, end: %',
267
- pt_name, lower(cur_range), upper(cur_range);
268
-
269
- EXECUTE FORMAT(
270
- 'CREATE TABLE %I PARTITION OF %I FOR VALUES FROM (%L) TO (%L)',
271
- pt_name, table_id, start_ev_id, end_ev_id
272
- );
273
-
274
- IF additional_sql IS NOT NULL THEN
275
- EXECUTE REPLACE(additional_sql, '$1', pt_name);
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