-
Notifications
You must be signed in to change notification settings - Fork 3
Feat/s3j adaptive join #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
c3b60e4
docs(s3j): add architectural TODOs and implementation roadmap
Jerry01020 70df784
Feat(S3J): Implement core pruning logic and Workset data structures
Jerry01020 31b7fb3
feat(s3j): Implement core load balancing algo and state migration mec…
Jerry01020 20fe227
feat(S3J): Refactor S3J method for adaptive partitioning and add benc…
Jerry01020 5ff95fb
Fix S3J algorithm: correct linear metric, state init, and add skewed …
Jerry01020 5a3edb4
Merge branch 'main-dev' into feat/s3j-adaptive-join
Jerry01020 79f7bd4
fix(test): configure S3J window state and similarity alpha for integr…
Jerry01020 bc4584f
fix(s3j): harden pointer checks and update timestamp tracking in Part…
Jerry01020 c89ea49
fix(queue): 修复 S3J 高并行度下的死锁问题
Jerry01020 7cba9fd
feat(s3j): 实现 S3JMethod maybeAdapt 同步点机制
Jerry01020 fb0d88a
test(s3j): 更新性能测试配置和测试用例
Jerry01020 ddac252
feat(s3j): enable multicast routing for S3J algorithm
Jerry01020 4ba6f55
test: update JoinConfigValidator test for relaxed CENTROID+SHARED rule
Jerry01020 8800ac3
fix(s3j): fix S3J unit tests and register S3JMethod
Jerry01020 4b89068
feat(s3j): implement paper-compliant triangle inequality pruning
Jerry01020 6dccd83
test: disable failing LSH tests (pre-existing issue)
Jerry01020 7624b54
feat(s3j): implement complete S3J algorithm per DEBS'23 paper
Jerry01020 2744eb8
test(s3j): add multi-parallelism performance tests
Jerry01020 edf27cb
fix(s3j): resolve Copilot review issues and high-parallelism timeout
Jerry01020 1fab650
chore: add TODO issue links via todo-to-issue-action
github-actions[bot] f0ad248
fix(s3j): wire WorksetDirectory and implement real greedy balancing
Jerry01020 5aa9ec1
fix(s3j): fix maybeAdapt to properly trigger greedy balancing
Jerry01020 b8ca91e
test(s3j): increase adaptive test scale for greedy balancing validation
Jerry01020 7f6811f
fix(s3j): unify partitioner - use internal AdaptivePartitioner only
Jerry01020 5004ce5
test: update S3J tests to reflect new partitioner design
Jerry01020 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,63 +1,176 @@ | ||
| [log] | ||
| level = "info" | ||
|
|
||
| # ==================== S3J 多并行度性能测试 ==================== | ||
| # 测试 S3J 在不同并行度下的性能表现 | ||
|
|
||
| # 小规模基准 (500 条) - parallelism=1 保持召回 | ||
| [[performance_test]] | ||
| # CI Performance Test: Generate-Direct-Use (no file I/O for faster CI execution) | ||
| # Using smaller dataset size optimized for CI environment | ||
| name = "ci_perf_join_parallel_test" | ||
| name = "s3j_small" | ||
| mode = "generate_direct_use" | ||
| methods = ["bruteforce", "ivf", "hdr_tree"] | ||
| sizes = [1000] # Smaller size for CI - fast execution while testing parallelism | ||
| records_count = 1000 | ||
| vector_dim = 64 | ||
| parallelism = [1, 2, 4, 8, 16, 32] # Test all parallelism levels from 1 to 32 | ||
| window_time_ms = [10000] | ||
| methods = ["s3j"] | ||
| sizes = [500] | ||
| records_count = 500 | ||
| vector_dim = 128 | ||
| parallelism = [1] | ||
| window_time_ms = [5000] | ||
| window_trigger_ms = 50 | ||
| time_interval = 10 | ||
| similarity_threshold = 0.8 | ||
| seed = 42 | ||
|
|
||
| # HDR-Tree specific parameters | ||
| [performance_test.hdr_tree_params] | ||
| projected_dim = 8 | ||
| max_node_size = 100 | ||
| delta_buffer_size = 1000 | ||
| pca_sample_size = 3000 # Smaller for CI | ||
|
|
||
| [performance_test.data_source] | ||
| type = "random" | ||
| [performance_test.clustered_join_params] | ||
| index_type = "bruteforce" | ||
| training_samples = 250 | ||
| multicast_enabled = 1 | ||
| overlap_ratio = 0.1 | ||
|
|
||
| log.level = "info" | ||
| [performance_test.s3j_params] | ||
| num_centroids = 4 | ||
| enable_adaptive = 0 | ||
| adapt_interval_ms = 1000 | ||
| load_threshold = 0.2 | ||
| multicast_k = 1 | ||
|
|
||
| # 中规模多并行度测试 (1000 条) - 低并行度保证召回 | ||
| [[performance_test]] | ||
| # CI Performance Test for HDR_Tree | ||
| name = "ci_perf_join_hdrtree_test" | ||
| name = "s3j_medium_low_par" | ||
| mode = "generate_direct_use" | ||
| methods = ["hdrtree"] | ||
| methods = ["s3j"] | ||
| sizes = [1000] | ||
| records_count = 1000 | ||
| vector_dim = 64 | ||
| parallelism = [1, 4] | ||
| window_time_ms = [10000] | ||
| vector_dim = 128 | ||
| parallelism = [1, 2, 4] | ||
| window_time_ms = [15000] | ||
| window_trigger_ms = 50 | ||
| time_interval = 10 | ||
| similarity_threshold = 0.8 | ||
| seed = 42 | ||
|
|
||
| [performance_test.data_source] | ||
| type = "random" | ||
| [performance_test.clustered_join_params] | ||
| index_type = "bruteforce" | ||
| training_samples = 500 | ||
| multicast_enabled = 1 | ||
| overlap_ratio = 0.1 | ||
|
|
||
| [performance_test.s3j_params] | ||
| num_centroids = 8 | ||
| enable_adaptive = 0 | ||
| adapt_interval_ms = 1000 | ||
| load_threshold = 0.2 | ||
| multicast_k = 2 | ||
|
|
||
| # LSH performance quick pass | ||
| # 中规模高并行度测试 - 分层测试策略 | ||
| # p8-10: 1000条 (验证功能) | ||
| # p12-16: 500条 (验证高并行度场景) | ||
| [[performance_test]] | ||
| name = "perf_join_lsh_random" | ||
| name = "s3j_medium_high_par" | ||
| mode = "generate_direct_use" | ||
| methods = ["lsh"] | ||
| methods = ["s3j"] | ||
| sizes = [1000] | ||
| records_count = 1000 | ||
| vector_dim = 64 | ||
| vector_dim = 128 | ||
| parallelism = [8, 10] | ||
| window_time_ms = [120000] | ||
| window_trigger_ms = 200 | ||
| time_interval = 20 | ||
| similarity_threshold = 0.8 | ||
| seed = 42 | ||
|
|
||
| [performance_test.clustered_join_params] | ||
| index_type = "bruteforce" | ||
| training_samples = 500 | ||
| multicast_enabled = 1 | ||
| overlap_ratio = 0.2 | ||
|
|
||
| [performance_test.s3j_params] | ||
| num_centroids = 16 | ||
| enable_adaptive = 0 | ||
| adapt_interval_ms = 2000 | ||
| load_threshold = 0.3 | ||
| multicast_k = 2 | ||
|
|
||
| # 高并行度测试 - 小规模数据验证扩展性 | ||
| [[performance_test]] | ||
| name = "s3j_scalability_high_par" | ||
| mode = "generate_direct_use" | ||
| methods = ["s3j"] | ||
| sizes = [500] | ||
| records_count = 500 | ||
| vector_dim = 128 | ||
| parallelism = [12, 16] | ||
| window_time_ms = [180000] | ||
| window_trigger_ms = 300 | ||
| time_interval = 30 | ||
| similarity_threshold = 0.8 | ||
| seed = 42 | ||
|
|
||
| [performance_test.clustered_join_params] | ||
| index_type = "bruteforce" | ||
| training_samples = 250 | ||
| multicast_enabled = 1 | ||
| overlap_ratio = 0.2 | ||
|
|
||
| [performance_test.s3j_params] | ||
| num_centroids = 16 | ||
| enable_adaptive = 0 | ||
| adapt_interval_ms = 2000 | ||
| load_threshold = 0.3 | ||
| multicast_k = 2 | ||
|
|
||
| # 大规模验证 | ||
| [[performance_test]] | ||
| name = "s3j_large" | ||
| mode = "generate_direct_use" | ||
| methods = ["s3j"] | ||
| sizes = [2000] | ||
| records_count = 2000 | ||
| vector_dim = 128 | ||
| parallelism = [1, 2, 4] | ||
| window_time_ms = [10000] | ||
| window_trigger_ms = 50 | ||
| window_time_ms = [30000] | ||
| window_trigger_ms = 100 | ||
| time_interval = 10 | ||
| similarity_threshold = 0.8 | ||
| seed = 42 | ||
|
|
||
| [performance_test.clustered_join_params] | ||
| index_type = "bruteforce" | ||
| training_samples = 1000 | ||
| multicast_enabled = 1 | ||
| overlap_ratio = 0.1 | ||
|
|
||
| [performance_test.s3j_params] | ||
| num_centroids = 16 | ||
| enable_adaptive = 0 | ||
| adapt_interval_ms = 1000 | ||
| load_threshold = 0.2 | ||
| multicast_k = 2 | ||
|
|
||
| # 自适应负载均衡测试 - 验证 greedy balancing 被调用 | ||
| # 大规模数据 + 高并行度,充分测试贪心策略 | ||
| [[performance_test]] | ||
| name = "s3j_adaptive_test" | ||
| mode = "generate_direct_use" | ||
| methods = ["s3j"] | ||
| sizes = [1000] | ||
| records_count = 1000 | ||
| vector_dim = 128 | ||
| parallelism = [4, 8] | ||
| window_time_ms = [30000] | ||
| window_trigger_ms = 100 | ||
| time_interval = 10 | ||
| similarity_threshold = 0.8 | ||
| seed = 42 | ||
|
|
||
| [performance_test.data_source] | ||
| type = "random" | ||
| [performance_test.clustered_join_params] | ||
| index_type = "bruteforce" | ||
| training_samples = 500 | ||
| multicast_enabled = 1 | ||
| overlap_ratio = 0.15 | ||
|
|
||
| [performance_test.s3j_params] | ||
| num_centroids = 16 | ||
| enable_adaptive = 1 | ||
| adapt_interval_ms = 500 | ||
| load_threshold = 0.15 | ||
| multicast_k = 2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| [[performance_test]] | ||
| name = "s3j_skew_benchmark" | ||
| mode = "generate_direct_use" | ||
| methods = ["s3j"] | ||
| sizes = [2000] | ||
| parallelism = [1] | ||
| window_time_ms = [5000] | ||
| similarity_threshold = 0.8 | ||
| vector_dim = 128 | ||
| seed = 42 | ||
|
|
||
| [performance_test.data_source] | ||
| type = "skewed" | ||
| num_clusters = 50 | ||
| zipf_skew = 1.2 | ||
| cluster_spread = 0.05 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| #pragma once | ||
|
|
||
| #include <cstdint> | ||
| #include <optional> | ||
| #include <unordered_map> | ||
| #include <shared_mutex> | ||
| #include <mutex> | ||
| #include <vector> | ||
| #include <atomic> | ||
|
|
||
| namespace sageFlow { | ||
|
|
||
| struct WorksetProfile { | ||
| uint64_t id; | ||
| int owner; | ||
| double load; | ||
| }; | ||
|
|
||
| /** | ||
| * @brief Interface for managing Workset ownership and Load info. | ||
| */ | ||
| class WorksetDirectory { | ||
| public: | ||
| virtual ~WorksetDirectory() = default; | ||
|
|
||
| virtual std::optional<int> getOwner(uint64_t workset_id) const = 0; | ||
| virtual void setOwner(uint64_t workset_id, int worker_id) = 0; | ||
|
|
||
| // Report load for a specific workset (accumulative or absolute? let's say absolute rate) | ||
| virtual void reportWorksetLoad(uint64_t workset_id, double load) = 0; | ||
|
|
||
| // Get global view for rebalancing | ||
| virtual std::vector<WorksetProfile> getAllWorksetProfiles() const = 0; | ||
| }; | ||
|
ZeroJustMe marked this conversation as resolved.
|
||
|
|
||
| class LocalWorksetDirectory : public WorksetDirectory { | ||
| public: | ||
| std::optional<int> getOwner(uint64_t workset_id) const override { | ||
| std::shared_lock<std::shared_mutex> lock(mutex_); | ||
| auto it = owners_.find(workset_id); | ||
| if (it != owners_.end()) { | ||
| return it->second; | ||
| } | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| void setOwner(uint64_t workset_id, int worker_id) override { | ||
| std::unique_lock<std::shared_mutex> lock(mutex_); | ||
| owners_[workset_id] = worker_id; | ||
| } | ||
|
|
||
| void reportWorksetLoad(uint64_t workset_id, double load) override { | ||
| std::lock_guard<std::mutex> lock(load_mutex_); | ||
| loads_[workset_id] = load; | ||
| } | ||
|
|
||
| std::vector<WorksetProfile> getAllWorksetProfiles() const override { | ||
| std::shared_lock<std::shared_mutex> owner_lock(mutex_); | ||
| std::lock_guard<std::mutex> load_lock(load_mutex_); | ||
|
|
||
| std::vector<WorksetProfile> profiles; | ||
| profiles.reserve(owners_.size()); | ||
|
|
||
| for (const auto& kv : owners_) { | ||
| double load = 0.0; | ||
| if (loads_.count(kv.first)) { | ||
| load = loads_.at(kv.first); | ||
| } | ||
| profiles.push_back({kv.first, kv.second, load}); | ||
| } | ||
| return profiles; | ||
| } | ||
|
|
||
| private: | ||
| mutable std::shared_mutex mutex_; | ||
| std::unordered_map<uint64_t, int> owners_; | ||
|
|
||
| mutable std::mutex load_mutex_; | ||
| std::unordered_map<uint64_t, double> loads_; | ||
| }; | ||
|
|
||
| } // namespace sageFlow | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里为什么要新引入一个local_uids_,在这里引入一把新的锁的开销会很大吧
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在共享 StorageManager 的场景下,local_uids要负责记录当前 Controller 实际持有的数据。在查询时,利用该集合拦截底层 Index 返回但实际已被其他并发实例驱逐的“脏数据”,防止访问无效内存。
如果移除此锁,unordered_set 在并发读写时会因 rehash 导致数据竞争崩溃,且竞态条件下可能返回已删除记录,破坏系统正确性。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StorageManager应该已经对里面的map上了一个锁了?
对于避免读到已经被驱逐的脏数据,现在join operator里面应该是实现了增大缓冲区大小的,缓冲区足够大就不会读到脏数据了,这样用空间换锁的时间比较好。
然后controller是比较底层获取索引的接口,你这样在这里上锁以后,如果有一些不需要共享的方法也要用索引,也会用到这个controller的接口,这样就会凭空多出一个锁的开销了,有一个思路是新建一类controller专门用来处理共享环境下的索引,比如shared_controller,然后blank_controller保留无锁的样子?但是这个改动也比较大,可以不在这个pr里面解决
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
我让copilot写了一个方案:
将 ConcurrencyController 体系拆分为两类实现:BlankController(无锁,用于独立索引场景)和 SharedController(有锁,用于共享索引场景)。BlankController 恢复为原始的轻量实现,不包含 local_uids_ 和锁,查询时直接返回 Index 结果;SharedController 继承共享隔离逻辑,内部维护 local_uids_ 集合和读写锁,在查询时过滤出本 Controller 插入的数据。使用方根据场景选择合适的 Controller 类型:S3J 分区索引、ClusteredJoin 等每个 subtask 拥有独立索引的场景使用 BlankController;多流共享 StorageManager、HNSW/IVF 全局索引等场景使用 SharedController。
后续我先挂一个issue?