Skip to content

Commit 6b95258

Browse files
Jayanta Mondalshuaitian-git
authored andcommitted
Merged PR 1751463: [TTL] use index hints to force ordered index scan on composite TTL indexes
### Does this PR have any customer impact? ### Type (Feature, Refactoring, Bugfix, DevOps, Testing, Perf, etc) ### Does it involve schema level changes? (Table, Column, Index, UDF, etc level changes) ### Are you introducing any new config? If yes, do you have tests with and without them being set? ### ChangeLog (Refer [Template](../oss/CHANGELOG.md)) ### Description In this PR, for TTL indexes, created as a composite index, we force an ordered scan while querying for eligible rows using index hints. Composite indexes support ordered scan on the index allowing the scan to be really efficient on the index. Without composite index, the planner uses a bitmap index scan which requires fetching all index pages and creating a bitmap. If there are a lot of documents to deleted, this can put a lot of pressure on the disk IOPS due to repeated fetching of large amount of index pages. When TTL index is created as a composite index, we effectively bypass the planner via the use of index hints and forces postgres to use the composite index. When index hint is provided the planner pins an ordered scan on the index provided as a hint. Below is an example of how an additional condition is added, as an index hint, to the query for fetching purge eligible rows: ```sql SELECT ctid FROM mongo_data.documents_1963502_19635007 WHERE bson_dollar_lt(document, '{ "ttl" : { "$date" : { "$numberLong" : "1657900030775" } } }'::bson) AND helio_api_internal.bson_dollar_index_hint(document, 'ttl_index'::text, '{"key": {"ttl": 1}}'::bson, isSparse) ```
1 parent 52d51e1 commit 6b95258

8 files changed

Lines changed: 620 additions & 37 deletions

File tree

internal/pg_documentdb_distributed/src/test/regress/bin/expect_normalize.sed

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@
1111
s/^-[+-]{2,}$/---------------------------------------------------------------------/g
1212
# Replace the values of the time system variables ($$NOW) with a constant
1313
s/\"sn\" : \{ \"\$date\" : \{ \"\$numberLong\" : \"[0-9]*\" \} \}/\"sn\" : NOW_SYS_VARIABLE/g
14-
s/\"now\" : \{ \"\$date\" : \{ \"\$numberLong\" : \"[0-9]*\" \} \}/\"now\" : NOW_SYS_VARIABLE/g
14+
s/\"now\" : \{ \"\$date\" : \{ \"\$numberLong\" : \"[0-9]*\" \} \}/\"now\" : NOW_SYS_VARIABLE/g
15+
s/TTL job elapsed time: [+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?ms,/TTL job elapsed time:<redacted>/g
16+
s/expiry_cutoff=[0-9]*,/expiry_cutoff=<redacted>/g

internal/pg_documentdb_distributed/src/test/regress/bin/normalize.sed

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ s/\"sn\" : \{ \"\$date\" : \{ \"\$numberLong\" : \"[0-9]*\" \} \}/\"sn\" : NOW_S
2525
s/documentdb_api_catalog.shard_key_and_document/shard_key_and_document/g
2626
s/documentdb_api_internal.generate_unique_shard_document/generate_unique_shard_document/g
2727
s/documentdb_core.bson/bson/g
28+
s/TTL job elapsed time: [+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?ms,/TTL job elapsed time:<redacted>/g
29+
s/expiry_cutoff=[0-9]*,/expiry_cutoff=<redacted>/g

internal/pg_documentdb_distributed/src/test/regress/expected/commands_create_ttl_indexes.out

Lines changed: 344 additions & 1 deletion
Large diffs are not rendered by default.

internal/pg_documentdb_distributed/src/test/regress/sql/commands_create_ttl_indexes.sql

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,21 @@ AND (dist.shardid = get_shard_id_for_distribution_column(logicalrelid, coll.coll
115115
AND coll.collection_id >= 20000 AND coll.collection_id < 21000 -- added to reduce test flakiness
116116
ORDER BY shardid ASC; -- added to reduce test flakiness
117117

118+
-- Delete all other indexes from previous tests to reduce flakiness
119+
WITH deleted AS (
120+
DELETE FROM documentdb_api_catalog.collection_indexes
121+
WHERE collection_id != 20000
122+
RETURNING 1
123+
) SELECT true FROM deleted UNION ALL SELECT true LIMIT 1;
124+
125+
SELECT
126+
collection_id,
127+
(index_spec).index_key, (index_spec).index_name,
128+
(index_spec).index_expire_after_seconds as ttl_expiry,
129+
(index_spec).index_is_sparse as is_sparse,
130+
(index_spec).index_name as index_name
131+
FROM documentdb_api_catalog.collection_indexes WHERE (index_spec).index_expire_after_seconds > 0;
132+
118133
-- 10.b. Call ttl task procedure with a batch size of 0 --
119134
BEGIN;
120135
Set citus.log_remote_commands to on; -- Will print Citus rewrites of the queries
@@ -311,4 +326,131 @@ CALL documentdb_api_internal.delete_expired_rows(10);
311326
-- 3000 ms does 70 iterations locally. So document count should be well below 9900.
312327
SELECT count(*) < 9900 from documentdb_api.collection('db', 'ttlRepeatedDeletes');
313328
SELECT count(*) < 9900 from documentdb_api.collection('db', 'ttlRepeatedDeletes2');
329+
END;
330+
331+
332+
-- 21. TTL index with forced ordered scan via index hints
333+
334+
set documentdb.enableExtendedExplainPlans to on;
335+
SET documentdb.enableNewCompositeIndexOpClass to on;
336+
set documentdb_rum.preferOrderedIndexScan to on;
337+
338+
-- if documentdb_extended_rum exists, set alternate index handler
339+
SELECT pg_catalog.set_config('documentdb.alternate_index_handler_name', 'extended_rum', false), extname FROM pg_extension WHERE extname = 'documentdb_extended_rum';
340+
341+
-- Delete all other indexes from previous tests to reduce flakiness
342+
SELECT documentdb_api.drop_collection('db', 'ttlcoll'), documentdb_api.drop_collection('db', 'ttlcoll1'), documentdb_api.drop_collection('db', 'ttlcoll2'),
343+
documentdb_api.drop_collection('db', 'ttlcoll3'),documentdb_api.drop_collection('db', 'ttlRepeatedDeletes'),documentdb_api.drop_collection('db', 'ttlRepeatedDeletes2');
344+
345+
-- make sure jobs are scheduled and disable it to avoid flakiness on the test as it could run on its schedule and delete documents before we run our commands in the test
346+
select cron.unschedule(jobid) from cron.job where jobname like '%ttl_task%';
347+
348+
-- 1. Populate collection with a set of documents with different combination of $date fields --
349+
SELECT documentdb_api.insert_one('db','ttlCompositeOrderedScan', '{ "_id" : 0, "ttl" : { "$date": { "$numberLong": "-1000" } } }', NULL);
350+
SELECT documentdb_api.insert_one('db','ttlCompositeOrderedScan', '{ "_id" : 1, "ttl" : { "$date": { "$numberLong": "0" } } }', NULL);
351+
SELECT documentdb_api.insert_one('db','ttlCompositeOrderedScan', '{ "_id" : 2, "ttl" : { "$date": { "$numberLong": "100" } } }', NULL);
352+
-- Documents with date older than when the test was written
353+
SELECT documentdb_api.insert_one('db','ttlCompositeOrderedScan', '{ "_id" : 3, "ttl" : { "$date": { "$numberLong": "1657900030774" } } }', NULL);
354+
-- Documents with date way in future
355+
SELECT documentdb_api.insert_one('db','ttlCompositeOrderedScan', '{ "_id" : 4, "ttl" : { "$date": { "$numberLong": "2657899731608" } } }', NULL);
356+
-- Documents with date array
357+
SELECT documentdb_api.insert_one('db','ttlCompositeOrderedScan', '{ "_id" : 5, "ttl" : [{ "$date": { "$numberLong": "100" }}] }', NULL);
358+
-- Documents with date array, should be deleted based on min timestamp
359+
SELECT documentdb_api.insert_one('db','ttlCompositeOrderedScan', '{ "_id" : 6, "ttl" : [{ "$date": { "$numberLong": "100" }}, { "$date": { "$numberLong": "2657899731608" }}] }', NULL);
360+
SELECT documentdb_api.insert_one('db','ttlCompositeOrderedScan', '{ "_id" : 7, "ttl" : [true, { "$date": { "$numberLong": "100" }}, { "$date": { "$numberLong": "2657899731608" }}] }', NULL);
361+
-- Documents with non-date ttl field
362+
SELECT documentdb_api.insert_one('db','ttlCompositeOrderedScan', '{ "_id" : 8, "ttl" : true }', NULL);
363+
-- Documents with non-date ttl field
364+
SELECT documentdb_api.insert_one('db','ttlCompositeOrderedScan', '{ "_id" : 9, "ttl" : "would not expire" }', NULL);
365+
366+
SELECT COUNT(documentdb_api.insert_one('db', 'ttlCompositeOrderedScan', FORMAT('{ "_id": %s, "ttl": { "$date": { "$numberLong": "1657900030774" } } }', i, i)::documentdb_core.bson)) FROM generate_series(10, 10000) AS i;
367+
368+
-- Create TTL Index --
369+
SET documentdb.enableExtendedExplainPlans to on;
370+
SET documentdb.enableNewCompositeIndexOpClass to on;
371+
SET documentdb.enableIndexOrderbyPushdown to on;
372+
SELECT documentdb_api_internal.create_indexes_non_concurrently('db', '{"createIndexes": "ttlCompositeOrderedScan", "indexes": [{"key": {"ttl": 1}, "enableCompositeTerm": true, "name": "ttl_index", "v" : 1, "expireAfterSeconds": 5, "sparse": true}]}', true);
373+
374+
select
375+
collection_id,
376+
(index_spec).index_key, (index_spec).index_name,
377+
(index_spec).index_expire_after_seconds as ttl_expiry,
378+
(index_spec).index_is_sparse as is_sparse,
379+
(index_spec).index_name as index_name
380+
from documentdb_api_catalog.collection_indexes where (index_spec).index_expire_after_seconds > 0;
381+
382+
\d documentdb_data.documents_20006
383+
384+
-- List All indexes --
385+
SELECT bson_dollar_unwind(cursorpage, '$cursor.firstBatch') FROM documentdb_api.list_indexes_cursor_first_page('db','{ "listIndexes": "ttlCompositeOrderedScan" }') ORDER BY 1;
386+
SELECT count(*) from ( SELECT shard_key_value, object_id, document from documentdb_api.collection('db', 'ttlCompositeOrderedScan') order by object_id) as a;
387+
388+
-- Call ttl purge procedure with a batch size of 100
389+
BEGIN;
390+
SET client_min_messages TO LOG;
391+
SET LOCAL documentdb.logTTLProgressActivity to on;
392+
CALL documentdb_api_internal.delete_expired_rows(100);
393+
RESET client_min_messages;
394+
END;
395+
396+
BEGIN;
397+
SET client_min_messages TO LOG;
398+
SET LOCAL documentdb.useIndexHintsForTTLTask to off;
399+
SET LOCAL documentdb.logTTLProgressActivity to on;
400+
CALL documentdb_api_internal.delete_expired_rows(100);
401+
RESET client_min_messages;
402+
END;
403+
404+
-- Check what documents are left after purging
405+
SELECT count(*) from ( SELECT shard_key_value, object_id, document from documentdb_api.collection('db', 'ttlCompositeOrderedScan') order by object_id) as a;
406+
407+
408+
-- TTL indexes behaves like normal indexes that are used in queries (cx can provide .hint() to force)
409+
BEGIN;
410+
SET LOCAL documentdb.enableIndexOrderbyPushdown to on;
411+
SET LOCAL documentdb.enableNewCompositeIndexOpClass to on;
412+
EXPLAIN(costs off) SELECT object_id FROM documentdb_data.documents_20006
413+
WHERE bson_dollar_eq(document, '{ "ttl" : { "$date" : { "$numberLong" : "100" } } }'::documentdb_core.bson)
414+
LIMIT 100;
415+
END;
416+
417+
-- Check the query to fetch the eligible TTL indexes uses IndexScan.
418+
419+
BEGIN;
420+
SET LOCAL documentdb.enableIndexOrderbyPushdown to on;
421+
SET LOCAL documentdb.enableNewCompositeIndexOpClass to on;
422+
EXPLAIN(analyze on, verbose on, costs off, timing off, summary off) SELECT ctid FROM documentdb_data.documents_20006_2000105
423+
WHERE bson_dollar_lt(document, '{ "ttl" : { "$date" : { "$numberLong" : "1754515365000" } } }'::documentdb_core.bson)
424+
AND documentdb_api_internal.bson_dollar_index_hint(document, 'ttl_index'::text, '{"key": {"ttl": 1}}'::documentdb_core.bson, true)
425+
LIMIT 100;
426+
END;
427+
428+
-- Shard collection
429+
SELECT documentdb_api.shard_collection('db', 'ttlCompositeOrderedScan', '{ "_id": "hashed" }', false);
430+
431+
-- Check TTL deletes work on sharded (should delete 800 docs, 100 for each shard)
432+
SELECT count(*) from ( SELECT shard_key_value, object_id, document from documentdb_api.collection('db', 'ttlCompositeOrderedScan') order by object_id) as a;
433+
CALL documentdb_api_internal.delete_expired_rows(100);
434+
SELECT count(*) from ( SELECT shard_key_value, object_id, document from documentdb_api.collection('db', 'ttlCompositeOrderedScan') order by object_id) as a;
435+
436+
437+
-- Check for Ordered Indes Scan on the ttl index
438+
439+
BEGIN;
440+
SET LOCAL documentdb.enableIndexOrderbyPushdown to on;
441+
SET LOCAL documentdb.enableNewCompositeIndexOpClass to on;
442+
EXPLAIN(analyze on, verbose on, costs off, timing off, summary off) SELECT ctid FROM documentdb_data.documents_20006_2000124
443+
WHERE bson_dollar_lt(document, '{ "ttl" : { "$date" : { "$numberLong" : "1657900030775" } } }'::documentdb_core.bson)
444+
AND documentdb_api_internal.bson_dollar_index_hint(document, 'ttl_index'::text, '{"key": {"ttl": 1}}'::documentdb_core.bson, true)
445+
LIMIT 100;
446+
END;
447+
448+
BEGIN;
449+
SET LOCAL documentdb.enableIndexOrderbyPushdown to on;
450+
SET LOCAL documentdb.enableNewCompositeIndexOpClass to on;
451+
SET client_min_messages TO INFO;
452+
EXPLAIN(COSTS OFF, ANALYZE ON, SUMMARY OFF, TIMING OFF) SELECT ctid FROM documentdb_data.documents_20006_2000122
453+
WHERE bson_dollar_lt(document, '{ "ttl" : { "$date" : { "$numberLong" : "1657900030775" } } }'::documentdb_core.bson)
454+
AND documentdb_api_internal.bson_dollar_index_hint(document, 'ttl_index'::text, '{"key": {"ttl": 1}}'::documentdb_core.bson, true)
455+
LIMIT 100;
314456
END;

pg_documentdb/include/commands/create_indexes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "operators/bson_expression.h"
2020
#include "vector/vector_spec.h"
2121

22+
#define MAX_INDEX_OPTIONS_LENGTH 1500
23+
2224
/*
2325
* Used with the ERRCODE_DOCUMENTDB_INDEXBUILDABORTED error code.
2426
*/

pg_documentdb/src/commands/create_indexes.c

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,6 @@
6565
#include "vector/vector_utilities.h"
6666
#include "index_am/index_am_utils.h"
6767

68-
69-
#define MAX_INDEX_OPTIONS_LENGTH 1500
70-
71-
7268
/* Return value of TryCreateCollectionIndexes */
7369
typedef struct
7470
{

pg_documentdb/src/configs/background_job_configs.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ bool LogTTLProgressActivity = DEFAULT_LOG_TTL_PROGRESS_ACTIVITY;
6868
#define DEFAULT_FORCE_INDEX_SCAN_TTL_TASK true
6969
bool ForceIndexScanForTTLTask = DEFAULT_FORCE_INDEX_SCAN_TTL_TASK;
7070

71+
#define DEFAULT_USE_INDEX_HINTS_TTL_TASK true
72+
bool UseIndexHintsForTTLTask = DEFAULT_USE_INDEX_HINTS_TTL_TASK;
73+
7174
void
7275
InitializeBackgroundJobConfigurations(const char *prefix, const char *newGucPrefix)
7376
{
@@ -96,6 +99,13 @@ InitializeBackgroundJobConfigurations(const char *prefix, const char *newGucPref
9699
NULL, &ForceIndexScanForTTLTask, DEFAULT_FORCE_INDEX_SCAN_TTL_TASK,
97100
PGC_USERSET, 0, NULL, NULL, NULL);
98101

102+
DefineCustomBoolVariable(
103+
psprintf("%s.useIndexHintsForTTLTask", prefix),
104+
gettext_noop(
105+
"Whether to force ordered Index Scan via Index Hints for TTL task"),
106+
NULL, &UseIndexHintsForTTLTask, DEFAULT_USE_INDEX_HINTS_TTL_TASK,
107+
PGC_USERSET, 0, NULL, NULL, NULL);
108+
99109
DefineCustomIntVariable(
100110
psprintf("%s.TTLPurgerStatementTimeout", prefix),
101111
gettext_noop(
@@ -122,7 +132,7 @@ InitializeBackgroundJobConfigurations(const char *prefix, const char *newGucPref
122132
DefineCustomBoolVariable(
123133
psprintf("%s.repeatPurgeIndexesForTTLTask", newGucPrefix),
124134
gettext_noop(
125-
"Wether to keep deleting documents in batches until `TTLTaskMaxRunTimeInMS` is reach per TTL task invocation."),
135+
"Whether to keep deleting documents in batches until `TTLTaskMaxRunTimeInMS` is reach per TTL task invocation."),
126136
NULL,
127137
&RepeatPurgeIndexesForTTLTask,
128138
DEFAULT_REPEAT_PURGE_INDEXES_FOR_TTL_TASK,

0 commit comments

Comments
 (0)