Skip to content

Commit 977c5fa

Browse files
committed
Add a function to lock OSM chunk's dimension slice
1 parent 19d418b commit 977c5fa

9 files changed

Lines changed: 146 additions & 41 deletions

File tree

.unreleased/pr_9964

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implements: #9964 Add a function to lock OSM chunk's dimension slice

sql/osm_api.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,12 @@ CREATE OR REPLACE FUNCTION _timescaledb_functions.hypertable_osm_range_update(
1212
empty BOOL = false
1313
) RETURNS BOOL AS '@MODULE_PATHNAME@',
1414
'ts_hypertable_osm_range_update' LANGUAGE C VOLATILE;
15+
16+
-- Acquires a FOR UPDATE row lock on the dimension slice tuple belonging to the
17+
-- OSM chunk of the given hypertable. There is exactly one OSM chunk per
18+
-- hypertable, so this locks its single dimension_slice entry. The lock is held
19+
-- until the end of the current transaction; nothing is returned.
20+
CREATE OR REPLACE FUNCTION _timescaledb_functions.lock_osm_chunk_dimension_slice(
21+
htoid REGCLASS
22+
) RETURNS VOID AS '@MODULE_PATHNAME@',
23+
'ts_lock_osm_chunk_dimension_slice' LANGUAGE C VOLATILE;

sql/updates/reverse-dev.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
DROP VIEW IF EXISTS _timescaledb_catalog.chunk_constraint;
33
DROP FUNCTION IF EXISTS _timescaledb_functions.chunk_constraint_add_table_constraint( integer, name, name);
44

5+
DROP FUNCTION IF EXISTS _timescaledb_functions.lock_osm_chunk_dimension_slice(regclass);
6+
57
-- Recreate the chunk_constraint catalog table.
68
CREATE TABLE _timescaledb_catalog.chunk_constraint (
79
chunk_id integer NOT NULL,

src/hypertable.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2690,6 +2690,69 @@ ts_hypertable_osm_range_update(PG_FUNCTION_ARGS)
26902690
PG_RETURN_BOOL(overlap);
26912691
}
26922692

2693+
/*
2694+
* lock_osm_chunk_dimension_slice
2695+
* 0 hypertable REGCLASS
2696+
*
2697+
* Acquires a FOR UPDATE row lock on the dimension slice tuple belonging to the
2698+
* OSM chunk of the given hypertable. There is exactly one OSM chunk per
2699+
* hypertable, so this locks its single dimension slice entry. The lock is held
2700+
* until the end of the current transaction. Returns void.
2701+
*
2702+
* Like hypertable_osm_range_update this is meant to be used by OSM to
2703+
* coordinate access to the OSM chunk's dimension slice; it is not meant to run
2704+
* on a read-only secondary.
2705+
*/
2706+
TS_FUNCTION_INFO_V1(ts_lock_osm_chunk_dimension_slice);
2707+
Datum
2708+
ts_lock_osm_chunk_dimension_slice(PG_FUNCTION_ARGS)
2709+
{
2710+
Oid relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
2711+
Hypertable *ht;
2712+
const Dimension *time_dim;
2713+
Cache *hcache;
2714+
2715+
Assert(!RecoveryInProgress());
2716+
2717+
hcache = ts_hypertable_cache_pin();
2718+
ht = ts_resolve_hypertable_from_table_or_cagg(hcache, relid, true);
2719+
Assert(ht != NULL);
2720+
time_dim = hyperspace_get_open_dimension(ht->space, 0);
2721+
2722+
Ensure(time_dim != NULL,
2723+
"could not find time dimension for hypertable %s.%s",
2724+
quote_identifier(NameStr(ht->fd.schema_name)),
2725+
quote_identifier(NameStr(ht->fd.table_name)));
2726+
2727+
int32 osm_chunk_id = ts_chunk_get_osm_chunk_id(ht->fd.id);
2728+
if (osm_chunk_id == INVALID_CHUNK_ID)
2729+
{
2730+
ereport(ERROR,
2731+
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
2732+
errmsg("no OSM chunk found for hypertable %s.%s",
2733+
quote_identifier(NameStr(ht->fd.schema_name)),
2734+
quote_identifier(NameStr(ht->fd.table_name))));
2735+
}
2736+
2737+
/*
2738+
* Lock the OSM chunk's dimension slice tuple FOR UPDATE. The row lock is
2739+
* held until the end of the current transaction.
2740+
*/
2741+
DimensionSlice *slice = ts_chunk_get_osm_slice_and_lock(osm_chunk_id,
2742+
time_dim->fd.id,
2743+
LockTupleExclusive,
2744+
RowShareLock);
2745+
2746+
if (!slice)
2747+
{
2748+
ereport(ERROR, errmsg("could not find time dimension slice for chunk %d", osm_chunk_id));
2749+
}
2750+
2751+
ts_cache_release(&hcache);
2752+
2753+
PG_RETURN_VOID();
2754+
}
2755+
26932756
TSDLLEXPORT bool
26942757
ts_hypertable_has_continuous_aggregates(int32 hypertable_id)
26952758
{

tsl/test/expected/chunk_utils_internal.out

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,11 +498,18 @@ SELECT status FROM _timescaledb_catalog.hypertable WHERE table_name = 'ht_try';
498498
3
499499

500500
-- must also update the range since the created chunk contains data
501+
BEGIN;
502+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('ht_try');
503+
lock_osm_chunk_dimension_slice
504+
--------------------------------
505+
506+
501507
SELECT _timescaledb_functions.hypertable_osm_range_update('ht_try', '2020-01-01'::timestamptz, '2020-01-02');
502508
hypertable_osm_range_update
503509
-----------------------------
504510
f
505511

512+
COMMIT;
506513
-- OSM chunk is not visible in chunks view
507514
SELECT chunk_name, range_start, range_end
508515
FROM timescaledb_information.chunks
@@ -659,6 +666,11 @@ SELECT * FROM hypertable_approximate_size('ht_try');
659666
-----------------------------
660667
32768
661668

669+
\set ON_ERROR_STOP 0
670+
-- Error for a hypertable that has no OSM chunk
671+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('test1.hyper1');
672+
ERROR: no OSM chunk found for hypertable test1.hyper1
673+
\set ON_ERROR_STOP 1
662674
--TEST GUC variable to enable/disable OSM chunk
663675
SET timescaledb.enable_tiered_reads=false;
664676
:EXPLAIN SELECT * from ht_try;

tsl/test/isolation/expected/osm_range_updates_iso.out

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,34 @@
1-
Parsed test spec with 10 sessions
1+
Parsed test spec with 11 sessions
2+
3+
starting permutation: LockDimSliceTuple LockDimSliceTuple2 UnlockDimSliceTuple UnlockDimSliceTuple2
4+
step LockDimSliceTuple:
5+
BEGIN;
6+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('osm_test');
7+
8+
lock_osm_chunk_dimension_slice
9+
------------------------------
10+
11+
12+
step LockDimSliceTuple2:
13+
BEGIN;
14+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('osm_test');
15+
<waiting ...>
16+
step UnlockDimSliceTuple: ROLLBACK;
17+
step LockDimSliceTuple2: <... completed>
18+
lock_osm_chunk_dimension_slice
19+
------------------------------
20+
21+
22+
step UnlockDimSliceTuple2: ROLLBACK;
223

324
starting permutation: LockDimSliceTuple UR1b UR1u UR2b UR2u UnlockDimSliceTuple UR1c UR2c
425
step LockDimSliceTuple:
526
BEGIN;
6-
SELECT range_start, range_end FROM _timescaledb_catalog.dimension_slice
7-
WHERE id IN ( SELECT ds.id FROM
8-
_timescaledb_catalog.chunk ch,
9-
_timescaledb_catalog.dimension_slice ds, _timescaledb_catalog.hypertable ht
10-
WHERE ht.table_name like 'osm_test' AND ds.chunk_id = ch.id AND ht.id = ch.hypertable_id
11-
AND ch.osm_chunk = true
12-
) FOR UPDATE;
13-
14-
range_start| range_end
15-
-------------------+-------------------
16-
9223372036854775806|9223372036854775807
27+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('osm_test');
28+
29+
lock_osm_chunk_dimension_slice
30+
------------------------------
31+
1732

1833
step UR1b: BEGIN;
1934
step UR1u: SELECT _timescaledb_functions.hypertable_osm_range_update('osm_test', 0, 10); <waiting ...>
@@ -36,17 +51,11 @@ step UR2c: COMMIT;
3651
starting permutation: LockDimSliceTuple DTb UR1b DropOsmChunk UR1u UnlockDimSliceTuple DTc UR1c
3752
step LockDimSliceTuple:
3853
BEGIN;
39-
SELECT range_start, range_end FROM _timescaledb_catalog.dimension_slice
40-
WHERE id IN ( SELECT ds.id FROM
41-
_timescaledb_catalog.chunk ch,
42-
_timescaledb_catalog.dimension_slice ds, _timescaledb_catalog.hypertable ht
43-
WHERE ht.table_name like 'osm_test' AND ds.chunk_id = ch.id AND ht.id = ch.hypertable_id
44-
AND ch.osm_chunk = true
45-
) FOR UPDATE;
46-
47-
range_start| range_end
48-
-------------------+-------------------
49-
9223372036854775806|9223372036854775807
54+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('osm_test');
55+
56+
lock_osm_chunk_dimension_slice
57+
------------------------------
58+
5059

5160
step DTb: BEGIN;
5261
step UR1b: BEGIN;
@@ -73,17 +82,11 @@ step UR1c: COMMIT;
7382
starting permutation: LockDimSliceTuple DTb UR1b UR1u DropOsmChunk UnlockDimSliceTuple UR1c DTc
7483
step LockDimSliceTuple:
7584
BEGIN;
76-
SELECT range_start, range_end FROM _timescaledb_catalog.dimension_slice
77-
WHERE id IN ( SELECT ds.id FROM
78-
_timescaledb_catalog.chunk ch,
79-
_timescaledb_catalog.dimension_slice ds, _timescaledb_catalog.hypertable ht
80-
WHERE ht.table_name like 'osm_test' AND ds.chunk_id = ch.id AND ht.id = ch.hypertable_id
81-
AND ch.osm_chunk = true
82-
) FOR UPDATE;
83-
84-
range_start| range_end
85-
-------------------+-------------------
86-
9223372036854775806|9223372036854775807
85+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('osm_test');
86+
87+
lock_osm_chunk_dimension_slice
88+
------------------------------
89+
8790

8891
step DTb: BEGIN;
8992
step UR1b: BEGIN;

tsl/test/isolation/specs/osm_range_updates_iso.spec

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,18 @@ step "UR2c" { COMMIT; }
4545
session "LDST"
4646
step "LockDimSliceTuple" {
4747
BEGIN;
48-
SELECT range_start, range_end FROM _timescaledb_catalog.dimension_slice
49-
WHERE id IN ( SELECT ds.id FROM
50-
_timescaledb_catalog.chunk ch,
51-
_timescaledb_catalog.dimension_slice ds, _timescaledb_catalog.hypertable ht
52-
WHERE ht.table_name like 'osm_test' AND ds.chunk_id = ch.id AND ht.id = ch.hypertable_id
53-
AND ch.osm_chunk = true
54-
) FOR UPDATE;
48+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('osm_test');
5549
}
5650
step "UnlockDimSliceTuple" { ROLLBACK; }
5751

52+
# second session that locks the same dimension_slice tuple
53+
session "LDST2"
54+
step "LockDimSliceTuple2" {
55+
BEGIN;
56+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('osm_test');
57+
}
58+
step "UnlockDimSliceTuple2" { ROLLBACK; }
59+
5860
session "DT"
5961
step "DTb" { BEGIN; }
6062
step "DropOsmChunk" {
@@ -102,6 +104,10 @@ step DR2b { BEGIN; }
102104
step DR2drop { SELECT _timescaledb_functions.drop_osm_chunk('test_drop'); }
103105
step DR2c { COMMIT; }
104106

107+
# Two concurrent locks on the same OSM chunk dimension_slice tuple block one
108+
# another. The second lock can only be acquired after the first session rolls
109+
# back and releases its lock.
110+
permutation "LockDimSliceTuple" "LockDimSliceTuple2" "UnlockDimSliceTuple" "UnlockDimSliceTuple2"
105111
# Concurrent updates will block one another
106112
# this previously deadlocked one of the two transactions
107113
permutation "LockDimSliceTuple" "UR1b" "UR1u" "UR2b" "UR2u" "UnlockDimSliceTuple" "UR1c" "UR2c"

tsl/test/shared/expected/extension.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ ORDER BY pronamespace::regnamespace::text COLLATE "C", p.oid::regprocedure::text
9191
_timescaledb_functions.jsonb_get_matching_index_entry(jsonb,text,text)
9292
_timescaledb_functions.last_combinefunc(internal,internal)
9393
_timescaledb_functions.last_sfunc(internal,anyelement,"any")
94+
_timescaledb_functions.lock_osm_chunk_dimension_slice(regclass)
9495
_timescaledb_functions.make_multirange_from_internal_time(tsrange,bigint,bigint)
9596
_timescaledb_functions.make_multirange_from_internal_time(tstzrange,bigint,bigint)
9697
_timescaledb_functions.make_range_from_internal_time(anyrange,anyelement,anyelement)

tsl/test/sql/chunk_utils_internal.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,10 @@ SELECT _timescaledb_functions.attach_osm_table_chunk('ht_try', 'child_fdw_table'
339339
-- check hypertable status
340340
SELECT status FROM _timescaledb_catalog.hypertable WHERE table_name = 'ht_try';
341341
-- must also update the range since the created chunk contains data
342+
BEGIN;
343+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('ht_try');
342344
SELECT _timescaledb_functions.hypertable_osm_range_update('ht_try', '2020-01-01'::timestamptz, '2020-01-02');
345+
COMMIT;
343346

344347
-- OSM chunk is not visible in chunks view
345348
SELECT chunk_name, range_start, range_end
@@ -406,6 +409,11 @@ SELECT _timescaledb_functions.hypertable_osm_range_update('ht_try', '2022-05-05
406409
-- test that approximate size function works when a osm chunk is present
407410
SELECT * FROM hypertable_approximate_size('ht_try');
408411

412+
\set ON_ERROR_STOP 0
413+
-- Error for a hypertable that has no OSM chunk
414+
SELECT _timescaledb_functions.lock_osm_chunk_dimension_slice('test1.hyper1');
415+
\set ON_ERROR_STOP 1
416+
409417
--TEST GUC variable to enable/disable OSM chunk
410418
SET timescaledb.enable_tiered_reads=false;
411419
:EXPLAIN SELECT * from ht_try;

0 commit comments

Comments
 (0)