33
44#include " duckdb.hpp"
55
6+ #include < condition_variable>
67#include < mutex>
78#include < unordered_map>
89
@@ -13,9 +14,26 @@ namespace duckdb {
1314// Per-view mutex: prevents two concurrent refreshes of the same MV (which would
1415// cause write-write conflicts on the MV table).
1516//
16- // Per-delta-table mutex: serializes the refresh's "read deltas + set last_update"
17- // critical section with the insert rule's delta row writes. This closes the window
18- // where concurrent DML deltas could be permanently skipped.
17+ // Per-delta-table mutex: serializes refreshes that consume the same delta table.
18+ //
19+ // Per-catalog phase gate: allows concurrent DML writers and concurrent refreshes, but
20+ // prevents those two phases from overlapping. This closes the window where refresh
21+ // could advance a watermark past a delta row that commits afterward.
22+ class DeltaCatalogPhaseGate {
23+ public:
24+ void EnterWrite ();
25+ void ExitWrite ();
26+ void EnterRefresh ();
27+ void ExitRefresh ();
28+
29+ private:
30+ mutex lock;
31+ std::condition_variable condition;
32+ idx_t active_writers = 0 ;
33+ idx_t active_refreshes = 0 ;
34+ idx_t waiting_refreshes = 0 ;
35+ };
36+
1937class RefreshLocks {
2038public:
2139 // --- View-level locks (prevent concurrent refresh of same MV) ---
@@ -29,47 +47,61 @@ class RefreshLocks {
2947
3048 static void UnlockView (const string &view_name);
3149
32- // --- Delta-table-level locks (serialize delta reads/writes ) ---
50+ // --- Delta-table-level locks (serialize overlapping refreshes ) ---
3351
34- // Blocking lock — held briefly by both refresh (read + timestamp update)
35- // and insert rule (delta row write).
52+ // Blocking lock held by refresh while consuming and checkpointing one delta table.
3653 static void LockDelta (const string &delta_table_name);
3754
3855 static void UnlockDelta (const string &delta_table_name);
3956
40- // Serializes refresh with transactions that write native delta tables in the same catalog.
41- // The writer holds this lock until transaction commit/rollback so a refresh cannot advance
42- // its timestamp cursor past an uncommitted delta row.
43- static void LockDeltaCatalog (const string &catalog_name);
44-
45- static void UnlockDeltaCatalog (const string &catalog_name);
57+ static void EnterDeltaWrite (Catalog &catalog);
58+ static void ExitDeltaWrite (Catalog &catalog);
59+ static void EnterDeltaRefresh (Catalog &catalog);
60+ static void ExitDeltaRefresh (Catalog &catalog);
4661
4762private:
4863 static std::mutex &GetViewMutex (const string &view_name);
4964 static std::mutex &GetDeltaMutex (const string &delta_table_name);
50- static std::mutex & GetDeltaCatalogMutex ( const string &catalog_name );
65+ static DeltaCatalogPhaseGate & GetDeltaCatalogGate (Catalog &catalog );
5166
5267 static std::mutex map_mutex_;
5368 static std::unordered_map<string, unique_ptr<std::mutex>> view_mutexes_;
5469 static std::unordered_map<string, unique_ptr<std::mutex>> delta_mutexes_;
55- static std::unordered_map<string , unique_ptr<std::mutex >> delta_catalog_mutexes_ ;
70+ static std::unordered_map<const Catalog * , unique_ptr<DeltaCatalogPhaseGate >> delta_catalog_gates_ ;
5671};
5772
58- // RAII guard for catalog-level delta transaction locks.
59- class DeltaCatalogLockGuard {
60- string name_;
73+ // Write guards can be retained by transaction state and released from the commit thread;
74+ // unlike std::mutex ownership, phase-gate membership is not tied to one OS thread.
75+ class DeltaCatalogWriteGuard {
76+ Catalog *catalog;
77+
78+ public:
79+ explicit DeltaCatalogWriteGuard (Catalog &catalog_p) : catalog(&catalog_p) {
80+ RefreshLocks::EnterDeltaWrite (*catalog);
81+ }
82+ ~DeltaCatalogWriteGuard () {
83+ RefreshLocks::ExitDeltaWrite (*catalog);
84+ }
85+ DeltaCatalogWriteGuard (const DeltaCatalogWriteGuard &) = delete ;
86+ DeltaCatalogWriteGuard &operator =(const DeltaCatalogWriteGuard &) = delete ;
87+ DeltaCatalogWriteGuard (DeltaCatalogWriteGuard &&) = delete ;
88+ DeltaCatalogWriteGuard &operator =(DeltaCatalogWriteGuard &&) = delete ;
89+ };
90+
91+ class DeltaCatalogRefreshGuard {
92+ Catalog *catalog;
6193
6294public:
63- explicit DeltaCatalogLockGuard ( const string &catalog_name ) : name_(catalog_name ) {
64- RefreshLocks::LockDeltaCatalog (name_ );
95+ explicit DeltaCatalogRefreshGuard (Catalog &catalog_p ) : catalog(&catalog_p ) {
96+ RefreshLocks::EnterDeltaRefresh (*catalog );
6597 }
66- ~DeltaCatalogLockGuard () {
67- RefreshLocks::UnlockDeltaCatalog (name_ );
98+ ~DeltaCatalogRefreshGuard () {
99+ RefreshLocks::ExitDeltaRefresh (*catalog );
68100 }
69- DeltaCatalogLockGuard (const DeltaCatalogLockGuard &) = delete ;
70- DeltaCatalogLockGuard &operator =(const DeltaCatalogLockGuard &) = delete ;
71- DeltaCatalogLockGuard (DeltaCatalogLockGuard &&) = delete ;
72- DeltaCatalogLockGuard &operator =(DeltaCatalogLockGuard &&) = delete ;
101+ DeltaCatalogRefreshGuard (const DeltaCatalogRefreshGuard &) = delete ;
102+ DeltaCatalogRefreshGuard &operator =(const DeltaCatalogRefreshGuard &) = delete ;
103+ DeltaCatalogRefreshGuard (DeltaCatalogRefreshGuard &&) = delete ;
104+ DeltaCatalogRefreshGuard &operator =(DeltaCatalogRefreshGuard &&) = delete ;
73105};
74106
75107// RAII guard for delta-table locks. Automatically unlocks on scope exit (including exceptions).
0 commit comments