5
5
*/
6
6
7
7
#include <postgres.h>
8
+ #include "debug_point.h"
8
9
#include <parser/parse_coerce.h>
9
10
#include <parser/parse_relation.h>
10
11
#include <utils/inval.h>
28
29
#include "ts_catalog/chunk_column_stats.h"
29
30
#include "ts_catalog/compression_settings.h"
30
31
32
+ /*
33
+ * Timing parameters for spin locking heuristics.
34
+ * These are the same as used by Postgres for truncate locking during lazy vacuum.
35
+ * https://github.com/postgres/postgres/blob/4a0650d359c5981270039eeb634c3b7427aa0af5/src/backend/access/heap/vacuumlazy.c#L82
36
+ */
37
+ #define RECOMPRESS_EXCLUSIVE_LOCK_WAIT_INTERVAL 50 /* ms */
38
+ #define RECOMPRESS_EXCLUSIVE_LOCK_TIMEOUT 5000 /* ms */
39
+
31
40
static bool fetch_uncompressed_chunk_into_tuplesort (Tuplesortstate * tuplesortstate ,
32
41
Relation uncompressed_chunk_rel ,
33
42
Snapshot snapshot );
@@ -50,6 +59,8 @@ static bool check_changed_group(CompressedSegmentInfo *current_segment, TupleTab
50
59
int nsegmentby_cols );
51
60
static void recompress_segment (Tuplesortstate * tuplesortstate , Relation compressed_chunk_rel ,
52
61
RowCompressor * row_compressor );
62
+ static void try_updating_chunk_status (Chunk * uncompressed_chunk , Relation uncompressed_chunk_rel );
63
+
53
64
/*
54
65
* Recompress an existing chunk by decompressing the batches
55
66
* that are affected by the addition of newer data. The existing
@@ -533,38 +544,7 @@ recompress_chunk_segmentwise_impl(Chunk *uncompressed_chunk)
533
544
*/
534
545
if (ConditionalLockRelation (uncompressed_chunk_rel , ExclusiveLock ))
535
546
{
536
- TableScanDesc scan = table_beginscan (uncompressed_chunk_rel , GetLatestSnapshot (), 0 , 0 );
537
- hypercore_scan_set_skip_compressed (scan , true);
538
- ScanDirection scan_dir = uncompressed_chunk_rel -> rd_tableam == hypercore_routine () ?
539
- ForwardScanDirection :
540
- BackwardScanDirection ;
541
- TupleTableSlot * slot = table_slot_create (uncompressed_chunk_rel , NULL );
542
-
543
- /* Doing a backwards scan with assumption that newly inserted tuples
544
- * are most likely at the end of the heap.
545
- */
546
- bool has_tuples = false;
547
- if (table_scan_getnextslot (scan , scan_dir , slot ))
548
- {
549
- has_tuples = true;
550
- }
551
-
552
- ExecDropSingleTupleTableSlot (slot );
553
- table_endscan (scan );
554
-
555
- if (!has_tuples )
556
- {
557
- if (ts_chunk_clear_status (uncompressed_chunk ,
558
- CHUNK_STATUS_COMPRESSED_UNORDERED |
559
- CHUNK_STATUS_COMPRESSED_PARTIAL ))
560
- ereport (DEBUG1 ,
561
- (errmsg ("cleared chunk status for recompression: \"%s.%s\"" ,
562
- NameStr (uncompressed_chunk -> fd .schema_name ),
563
- NameStr (uncompressed_chunk -> fd .table_name ))));
564
-
565
- /* changed chunk status, so invalidate any plans involving this chunk */
566
- CacheInvalidateRelcacheByRelid (uncompressed_chunk_id );
567
- }
547
+ try_updating_chunk_status (uncompressed_chunk , uncompressed_chunk_rel );
568
548
}
569
549
else if (has_unique_constraints )
570
550
{
@@ -575,13 +555,46 @@ recompress_chunk_segmentwise_impl(Chunk *uncompressed_chunk)
575
555
* and speculative insertion could potentially cause false negatives during
576
556
* constraint checking. For now, our best option here is to bail.
577
557
*
578
- * This can be improved by using a spin lock to wait for the ExclusiveLock
579
- * or bail out if we can't get it in time.
558
+ * We use a spin lock to wait for the ExclusiveLock or bail out if we can't get it in time.
580
559
*/
581
- ereport (ERROR ,
582
- (errcode (ERRCODE_T_R_SERIALIZATION_FAILURE ),
583
- errmsg ("aborting recompression due to concurrent DML on uncompressed "
584
- "data, retrying with next policy run" )));
560
+
561
+ int lock_retry = 0 ;
562
+ while (true)
563
+ {
564
+ if (ConditionalLockRelation (uncompressed_chunk_rel , ExclusiveLock ))
565
+ {
566
+ try_updating_chunk_status (uncompressed_chunk , uncompressed_chunk_rel );
567
+ break ;
568
+ }
569
+
570
+ /*
571
+ * Check for interrupts while trying to (re-)acquire the exclusive
572
+ * lock.
573
+ */
574
+ CHECK_FOR_INTERRUPTS ();
575
+
576
+ if (++ lock_retry >
577
+ (RECOMPRESS_EXCLUSIVE_LOCK_TIMEOUT / RECOMPRESS_EXCLUSIVE_LOCK_WAIT_INTERVAL ))
578
+ {
579
+ /*
580
+ * We failed to establish the lock in the specified number of
581
+ * retries. This means we give up trying to get the exclusive lock are abort the
582
+ * recompression operation
583
+ */
584
+ ereport (ERROR ,
585
+ (errcode (ERRCODE_T_R_SERIALIZATION_FAILURE ),
586
+ errmsg ("aborting recompression due to concurrent DML on uncompressed "
587
+ "data, retrying with next policy run" )));
588
+ break ;
589
+ }
590
+
591
+ (void ) WaitLatch (MyLatch ,
592
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH ,
593
+ RECOMPRESS_EXCLUSIVE_LOCK_WAIT_INTERVAL ,
594
+ WAIT_EVENT_VACUUM_TRUNCATE );
595
+ ResetLatch (MyLatch );
596
+ DEBUG_WAITPOINT ("chunk_recompress_after_latch" );
597
+ }
585
598
}
586
599
587
600
table_close (uncompressed_chunk_rel , NoLock );
@@ -866,3 +879,47 @@ delete_tuple_for_recompression(Relation rel, ItemPointer tid, Snapshot snapshot)
866
879
867
880
return result == TM_Ok ;
868
881
}
882
+
883
+ /* Check if we can update the chunk status to fully compressed after segmentwise recompression
884
+ * We can only do this if there were no concurrent DML operations, so we check to see if there are
885
+ * any uncompressed tuples in the chunk after compression.
886
+ * If there aren't, we can update the chunk status
887
+ *
888
+ * Note: Caller is expected to have an ExclusiveLock on the uncompressed_chunk
889
+ */
890
+ static void
891
+ try_updating_chunk_status (Chunk * uncompressed_chunk , Relation uncompressed_chunk_rel )
892
+ {
893
+ TableScanDesc scan = table_beginscan (uncompressed_chunk_rel , GetLatestSnapshot (), 0 , 0 );
894
+ hypercore_scan_set_skip_compressed (scan , true);
895
+ ScanDirection scan_dir = uncompressed_chunk_rel -> rd_tableam == hypercore_routine () ?
896
+ ForwardScanDirection :
897
+ BackwardScanDirection ;
898
+ TupleTableSlot * slot = table_slot_create (uncompressed_chunk_rel , NULL );
899
+
900
+ /* Doing a backwards scan with assumption that newly inserted tuples
901
+ * are most likely at the end of the heap.
902
+ */
903
+ bool has_tuples = false;
904
+ if (table_scan_getnextslot (scan , scan_dir , slot ))
905
+ {
906
+ has_tuples = true;
907
+ }
908
+
909
+ ExecDropSingleTupleTableSlot (slot );
910
+ table_endscan (scan );
911
+
912
+ if (!has_tuples )
913
+ {
914
+ if (ts_chunk_clear_status (uncompressed_chunk ,
915
+ CHUNK_STATUS_COMPRESSED_UNORDERED |
916
+ CHUNK_STATUS_COMPRESSED_PARTIAL ))
917
+ ereport (DEBUG1 ,
918
+ (errmsg ("cleared chunk status for recompression: \"%s.%s\"" ,
919
+ NameStr (uncompressed_chunk -> fd .schema_name ),
920
+ NameStr (uncompressed_chunk -> fd .table_name ))));
921
+
922
+ /* changed chunk status, so invalidate any plans involving this chunk */
923
+ CacheInvalidateRelcacheByRelid (uncompressed_chunk -> table_id );
924
+ }
925
+ }
0 commit comments