Skip to content

Commit 2f5a560

Browse files
Merge upstream/users/qiazh/pre-merge-tikv-bugfix into users/zhangt/merge-distributed-to-tikv
Resolve conflicts preserving features from both sides: - HEAD: distributed worker/dispatcher/router, AsyncAppend, MergeAsyncJob(3-arg), m_asyncStatus, headVec layout with metaDataSize prefix, NumSearchDuringInsertThreads - qiazh: VersionReadPolicy, TiKV PutBaseChunkAndCount/AsyncGetPostingCounts batch RPCs, AddIndexAsync* (multi-chunk + single-key), Append RMW data-loss fix (distinguish Key_NotFound from errors), GetCachedPostingCount -1 sentinel, extensive DIAG stats and histograms (reassignJobLatency, queueDepth, splitNewHeadCount, MC histograms), batch barrier wait timing. Build verification: Release/SPTAGTest builds cleanly with -DTIKV=ON -DTBB=ON. Added absl_synchronization to SPTAGTest link deps (qiazh's main.cpp uses absl::SetMutexDeadlockDetectionMode). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents f26c18f + 3442e50 commit 2f5a560

14 files changed

Lines changed: 2290 additions & 268 deletions

AnnService/inc/Core/Common/FineGrainedLock.h

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <vector>
99
#include <mutex>
1010
#include <memory>
11+
#include <unordered_map>
1112

1213
namespace SPTAG
1314
{
@@ -43,27 +44,47 @@ namespace SPTAG
4344
class FineGrainedRWLock {
4445
public:
4546
FineGrainedRWLock() {
46-
m_locks.reset(new std::shared_timed_mutex[PoolSize + 1]);
47+
m_buckets.reset(new Bucket[BucketCount]);
4748
}
4849
~FineGrainedRWLock() {}
4950

5051
std::shared_timed_mutex& operator[](SizeType idx) {
51-
unsigned index = hash_func((unsigned)idx);
52-
return m_locks[index];
52+
return GetLock(idx);
5353
}
5454

5555
const std::shared_timed_mutex& operator[](SizeType idx) const {
56-
unsigned index = hash_func((unsigned)idx);
57-
return m_locks[index];
56+
return GetLock(idx);
5857
}
5958

6059
static inline unsigned hash_func(unsigned idx)
6160
{
62-
return ((unsigned)(idx * 99991) + _rotl(idx, 2) + 101) & PoolSize;
61+
return idx;
6362
}
6463
private:
65-
static const int PoolSize = 32767;
66-
std::unique_ptr<std::shared_timed_mutex[]> m_locks;
64+
struct Bucket {
65+
std::mutex mutex;
66+
std::unordered_map<SizeType, std::unique_ptr<std::shared_timed_mutex>> locks;
67+
};
68+
69+
std::shared_timed_mutex& GetLock(SizeType idx) const {
70+
Bucket& bucket = m_buckets[BucketIndex(idx)];
71+
std::lock_guard<std::mutex> guard(bucket.mutex);
72+
auto iter = bucket.locks.find(idx);
73+
if (iter == bucket.locks.end()) {
74+
iter = bucket.locks.emplace(idx, std::unique_ptr<std::shared_timed_mutex>(new std::shared_timed_mutex())).first;
75+
}
76+
return *iter->second;
77+
}
78+
79+
static inline unsigned BucketIndex(SizeType idx)
80+
{
81+
unsigned key = static_cast<unsigned>(idx);
82+
return ((unsigned)(key * 99991) + _rotl(key, 2) + 101) & BucketMask;
83+
}
84+
85+
static const int BucketMask = 32767;
86+
static const int BucketCount = BucketMask + 1;
87+
mutable std::unique_ptr<Bucket[]> m_buckets;
6788
};
6889
}
6990
}

AnnService/inc/Core/Common/IVersionMap.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ namespace SPTAG
1515
{
1616
namespace COMMON
1717
{
18+
enum class VersionReadPolicy
19+
{
20+
UseCache,
21+
BypassCacheNoFill
22+
};
23+
1824
/// Abstract interface for version map, allowing both local (in-memory)
1925
/// and distributed (TiKV-backed) implementations.
2026
class IVersionMap
@@ -31,9 +37,11 @@ namespace SPTAG
3137
virtual std::uint64_t BufferSize() const = 0;
3238

3339
virtual bool Deleted(const SizeType& key) const = 0;
40+
virtual bool Deleted(const SizeType& key, VersionReadPolicy policy) const { return Deleted(key); }
3441
virtual bool Delete(const SizeType& key) = 0;
3542

3643
virtual uint8_t GetVersion(const SizeType& key) = 0;
44+
virtual uint8_t GetVersion(const SizeType& key, VersionReadPolicy policy) { return GetVersion(key); }
3745
virtual void SetVersion(const SizeType& key, const uint8_t& version) = 0;
3846
/// Increment the version of a VID.
3947
/// @param expectedOld If not 0xff, the caller asserts the current version should be this value.
@@ -55,13 +63,18 @@ namespace SPTAG
5563
/// Returns a vector of versions (0xfe = deleted) in the same order as vids.
5664
/// Default implementation does per-VID lookup.
5765
virtual void BatchGetVersions(const std::vector<SizeType>& vids, std::vector<uint8_t>& versions)
66+
{
67+
BatchGetVersions(vids, versions, VersionReadPolicy::UseCache);
68+
}
69+
70+
virtual void BatchGetVersions(const std::vector<SizeType>& vids, std::vector<uint8_t>& versions, VersionReadPolicy policy)
5871
{
5972
versions.resize(vids.size());
6073
for (size_t i = 0; i < vids.size(); i++) {
6174
if (vids[i] < 0 || vids[i] >= Count()) {
6275
versions[i] = 0xfe;
6376
} else {
64-
versions[i] = GetVersion(vids[i]);
77+
versions[i] = GetVersion(vids[i], policy);
6578
}
6679
}
6780
}

AnnService/inc/Core/Common/TiKVVersionMap.h

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,22 @@ namespace SPTAG
4848
struct CachedChunk {
4949
SizeType chunkId;
5050
std::string data;
51-
std::chrono::steady_clock::time_point fetchTime;
5251
};
5352
using LruList = std::list<CachedChunk>;
5453
mutable std::shared_mutex m_cacheMutex;
5554
mutable LruList m_lruList;
5655
mutable std::unordered_map<SizeType, LruList::iterator> m_cacheMap;
57-
int m_cacheTTLMs{0}; // TTL in ms, 0 = cache disabled
58-
int m_cacheMaxChunks{10000}; // max cached chunks (~40MB at 4096 chunk size)
56+
int m_cacheMaxChunks{10000}; // max cached chunks; <= 0 disables caching
5957

6058
// Insert or update a chunk in the LRU cache. Caller must hold exclusive m_cacheMutex.
61-
void CachePut(SizeType chunkId, const std::string& data, std::chrono::steady_clock::time_point now) const
59+
void CachePut(SizeType chunkId, const std::string& data) const
6260
{
6361
if (m_cacheMaxChunks <= 0) return; // cache disabled
6462

6563
auto it = m_cacheMap.find(chunkId);
6664
if (it != m_cacheMap.end()) {
6765
// Update existing: move to front
6866
it->second->data = data;
69-
it->second->fetchTime = now;
7067
m_lruList.splice(m_lruList.begin(), m_lruList, it->second);
7168
} else {
7269
// Evict LRU entries if at capacity
@@ -75,7 +72,7 @@ namespace SPTAG
7572
m_cacheMap.erase(back.chunkId);
7673
m_lruList.pop_back();
7774
}
78-
m_lruList.push_front({chunkId, data, now});
75+
m_lruList.push_front({chunkId, data});
7976
m_cacheMap[chunkId] = m_lruList.begin();
8077
}
8178
}
@@ -112,7 +109,7 @@ namespace SPTAG
112109
auto ret = m_db->Put(ChunkKey(chunkId), data, MaxTimeout, nullptr);
113110
if (ret == ErrorCode::Success) {
114111
std::unique_lock<std::shared_mutex> lock(m_cacheMutex);
115-
CachePut(chunkId, data, std::chrono::steady_clock::now());
112+
CachePut(chunkId, data);
116113
}
117114
return ret;
118115
}
@@ -136,16 +133,17 @@ namespace SPTAG
136133
std::string data = ReadChunk(chunkId);
137134
if (!data.empty()) {
138135
std::unique_lock<std::shared_mutex> lock(m_cacheMutex);
139-
CachePut(chunkId, data, std::chrono::steady_clock::now());
136+
CachePut(chunkId, data);
140137
}
141138
return data;
142139
}
143140

144-
// Read a single byte for a VID using cache. Returns 0xfe on error/miss.
145-
uint8_t ReadVersionByte(SizeType vid) const
141+
// Read a single byte for a VID. Returns 0xfe on error/miss.
142+
uint8_t ReadVersionByte(SizeType vid, VersionReadPolicy policy = VersionReadPolicy::UseCache) const
146143
{
147144
SizeType cid = ChunkId(vid);
148-
std::string chunk = ReadChunkCached(cid);
145+
std::string chunk = (policy == VersionReadPolicy::BypassCacheNoFill) ?
146+
ReadChunk(cid) : ReadChunkCached(cid);
149147
if (chunk.empty() || (int)chunk.size() <= ChunkOffset(vid)) {
150148
return 0xfe;
151149
}
@@ -190,7 +188,6 @@ namespace SPTAG
190188
void SetDB(std::shared_ptr<Helper::KeyValueIO> db) { m_db = db; }
191189
void SetLayer(int layer) { m_layer = layer; }
192190
void SetChunkSize(int chunkSize) { m_chunkSize = chunkSize; }
193-
void SetCacheTTL(int ttlMs) { m_cacheTTLMs = ttlMs; }
194191
void SetCacheMaxChunks(int maxChunks) { m_cacheMaxChunks = maxChunks; }
195192

196193
std::shared_ptr<Helper::KeyValueIO> GetDB() const { return m_db; }
@@ -293,12 +290,17 @@ namespace SPTAG
293290
std::uint64_t BufferSize() const override { return static_cast<std::uint64_t>(m_count.load()) + sizeof(SizeType); }
294291

295292
bool Deleted(const SizeType& key) const override
293+
{
294+
return Deleted(key, VersionReadPolicy::UseCache);
295+
}
296+
297+
bool Deleted(const SizeType& key, VersionReadPolicy policy) const override
296298
{
297299
if (key < 0 || key >= m_count.load()) {
298300
SPTAGLIB_LOG(Helper::LogLevel::LL_Error, "TiKVVersionMap::Deleted: invalid key %d (max %d)\n", key, m_count.load());
299301
return true;
300302
}
301-
return ReadVersionByte(key) == 0xfe;
303+
return ReadVersionByte(key, policy) == 0xfe;
302304
}
303305

304306
bool Delete(const SizeType& key) override
@@ -323,12 +325,17 @@ namespace SPTAG
323325
}
324326

325327
uint8_t GetVersion(const SizeType& key) override
328+
{
329+
return GetVersion(key, VersionReadPolicy::UseCache);
330+
}
331+
332+
uint8_t GetVersion(const SizeType& key, VersionReadPolicy policy) override
326333
{
327334
if (key < 0 || key >= m_count.load()) {
328335
SPTAGLIB_LOG(Helper::LogLevel::LL_Error, "TiKVVersionMap::GetVersion: invalid key %d (max %d)\n", key, m_count.load());
329336
return 0xfe;
330337
}
331-
return ReadVersionByte(key);
338+
return ReadVersionByte(key, policy);
332339
}
333340

334341
void SetVersion(const SizeType& key, const uint8_t& version) override
@@ -451,13 +458,18 @@ namespace SPTAG
451458
/// Batch version lookup with local cache support.
452459
/// Checks cache first, only fetches misses from TiKV via BatchGet.
453460
void BatchGetVersions(const std::vector<SizeType>& vids, std::vector<uint8_t>& versions) override
461+
{
462+
BatchGetVersions(vids, versions, VersionReadPolicy::UseCache);
463+
}
464+
465+
void BatchGetVersions(const std::vector<SizeType>& vids, std::vector<uint8_t>& versions, VersionReadPolicy policy) override
454466
{
455467
versions.resize(vids.size());
456468
if (vids.empty()) return;
457469

458470
SizeType count = m_count.load();
459-
auto now = std::chrono::steady_clock::now();
460-
bool cacheEnabled = (m_cacheTTLMs > 0);
471+
bool bypassCache = (policy == VersionReadPolicy::BypassCacheNoFill);
472+
bool cacheEnabled = (m_cacheMaxChunks > 0 && !bypassCache);
461473

462474
// Group VIDs by chunk
463475
std::unordered_map<SizeType, std::vector<size_t>> chunkToIndices;
@@ -479,11 +491,8 @@ namespace SPTAG
479491
for (auto& [cid, indices] : chunkToIndices) {
480492
auto it = m_cacheMap.find(cid);
481493
if (it != m_cacheMap.end()) {
482-
auto ageMs = std::chrono::duration_cast<std::chrono::milliseconds>(now - it->second->fetchTime).count();
483-
if (ageMs < m_cacheTTLMs) {
484-
resolvedChunks[cid] = it->second->data; // copy
485-
continue;
486-
}
494+
resolvedChunks[cid] = it->second->data; // copy
495+
continue;
487496
}
488497
missChunkIds.push_back(cid);
489498
}
@@ -522,11 +531,11 @@ namespace SPTAG
522531
m_layer, missingChunks, static_cast<int>(missChunkIds.size()), missChunkIds[0]);
523532
}
524533

525-
// Update LRU cache with fetched chunks (exclusive lock)
534+
// Update LRU cache with fetched chunks (exclusive lock). Bypass reads never fill cache.
526535
if (cacheEnabled && !fetchedChunks.empty()) {
527536
std::unique_lock<std::shared_mutex> lock(m_cacheMutex);
528537
for (auto& [cid, data] : fetchedChunks) {
529-
CachePut(cid, data, now);
538+
CachePut(cid, data);
530539
}
531540
}
532541
}

0 commit comments

Comments
 (0)