Skip to content

Commit 63058b4

Browse files
authored
Merge pull request ClickHouse#80247 from jiebinn/QueryConditionCache
Reduce lock contention in QueryConditionCache
2 parents 593ee4f + 5eec057 commit 63058b4

File tree

2 files changed

+27
-4
lines changed

2 files changed

+27
-4
lines changed

src/Interpreters/Cache/QueryConditionCache.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,29 @@ void QueryConditionCache::write(
2727
auto load_func = [&](){ return std::make_shared<Entry>(marks_count); };
2828
auto [entry, inserted] = cache.getOrSet(key, load_func);
2929

30-
std::lock_guard lock(entry->mutex);
30+
/// Try to avoid acquiring the RW lock below (*) by early-ing out. Matters for systems with lots of cores.
31+
{
32+
std::shared_lock shared_lock(entry->mutex); /// cheap
33+
34+
bool need_not_update_marks = true;
35+
for (const auto & mark_range : mark_ranges)
36+
{
37+
/// If the bits are already in the desired state (false), we don't need to update them.
38+
need_not_update_marks = std::all_of(entry->matching_marks.begin() + mark_range.begin,
39+
entry->matching_marks.begin() + mark_range.end,
40+
[](auto b) { return b == false; });
41+
if (!need_not_update_marks)
42+
break;
43+
}
44+
45+
/// Do we either have no final mark or final mark is already in the desired state?
46+
bool need_not_update_final_mark = !has_final_mark || entry->matching_marks[marks_count - 1] == false;
47+
48+
if (need_not_update_marks && need_not_update_final_mark)
49+
return;
50+
}
51+
52+
std::lock_guard lock(entry->mutex); /// (*)
3153

3254
chassert(marks_count == entry->matching_marks.size());
3355

@@ -132,4 +154,5 @@ size_t QueryConditionCache::QueryConditionCacheEntryWeight::operator()(const Ent
132154
size_t memory = (entry.matching_marks.capacity() + 7) / 8; /// round up to bytes.
133155
return memory + sizeof(decltype(entry.matching_marks));
134156
}
157+
135158
}

src/Interpreters/Cache/QueryConditionCache.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace DB
1616
///
1717
/// Note: The cache may store more than the minimal number of matching marks.
1818
/// For example, assume a very selective predicate that matches just a single row in a single mark.
19-
/// One would expect that the cache records just the single mark as potentially matching:
19+
/// One would expect that the cache records just a single mark as potentially matching:
2020
/// 000000010000000000000000000
2121
/// But it is equally correct for the cache to store this: (it is just less efficient for pruning)
2222
/// 000001111111110000000000000
@@ -51,14 +51,13 @@ class QueryConditionCache
5151

5252
/// (*) You might wonder why Entry has its own mutex considering that CacheBase locks internally already.
5353
/// The reason is that ClickHouse scans ranges within the same part in parallel. The first scan creates
54-
/// and inserts a new Key + Entry into the cache, the 2nd ... Nth scan find the existing Key and update
54+
/// and inserts a new Key + Entry into the cache, the 2nd ... Nth scans find the existing Key and update
5555
/// its Entry for the new ranges. This can only be done safely in a synchronized fashion.
5656

5757
/// (**) About error handling: There could be an exception after the i-th scan and cache entries could
5858
/// (theoretically) be left in a corrupt state. If we are not careful, future scans queries could then
5959
/// skip too many ranges. To prevent this, it is important to initialize all marks of each entry as
6060
/// non-matching. In case of an exception, future scans will then not skip them.
61-
6261
};
6362

6463
struct KeyHasher
@@ -71,6 +70,7 @@ class QueryConditionCache
7170
size_t operator()(const Entry & entry) const;
7271
};
7372

73+
7474
public:
7575
using Cache = CacheBase<Key, Entry, KeyHasher, QueryConditionCacheEntryWeight>;
7676

0 commit comments

Comments
 (0)