-
Notifications
You must be signed in to change notification settings - Fork 23
Implement Indexed Priority Queue to improve grouping performance (#251) #299
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
base: main
Are you sure you want to change the base?
Implement Indexed Priority Queue to improve grouping performance (#251) #299
Conversation
a65e75d
to
9502ce3
Compare
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.
@@ -99,7 +99,7 @@ public class TopQueriesService { | |||
/** | |||
* The internal thread-safe store that holds the top n queries insight data | |||
*/ | |||
private final PriorityBlockingQueue<SearchQueryRecord> topQueriesStore; | |||
private final IndexedPriorityQueue<SearchQueryRecord> topQueriesStore; |
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.
We don't do deletion for data stored in the PriorityQueue in TopQueriesService, I think the performance of PriorityBlockingQueue and IndexedPriorityQueue would be the same here.
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.
We’re already handing the very same topQueriesStore into the MinMaxHeapQueryGrouper, so splitting it into two separate queues just adds needless complexity. By switching both service and grouper to use a single IndexedPriorityQueue, we get one shared heap—no extra merge logic, no change in performance characteristics—and all of our inserts, polls, and snapshots happen against the exact same data structure. For those reasons, I’d stick with the IPQ here.
src/main/java/org/opensearch/plugin/insights/core/utils/IndexedPriorityQueue.java
Show resolved
Hide resolved
public class IndexedPriorityQueue<T> { | ||
|
||
private final List<T> heap; | ||
private final Map<T, Integer> indexMap; |
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.
In here, we use T
as the map key, this will require T to have equals
/ hashCode
implemented. Also if T is mutable (which is true for our case), the equals and hashCode will change after the data is inserted, in which case the indexMap will become inconsistent (e.g. contains and remove might fail). Also the order will depend on the comparator T implemented (this should be fine in our case though).
I think we should probably change IndexedPriorityQueue to be IndexedPriorityQueue<K, V>
, and make index map: Map<K, Integer> indexMap;
and heap be List<Entry<K, V>> heap
. We should also pass a comparator in the constructor IMO.
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.
Keys are now of type K, separate from your mutable V values, so you only ever hash or compare on that stable identifier.
this.indexMap = new HashMap<>(initialCapacity); | ||
} | ||
|
||
public synchronized boolean insert(T item) { |
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.
As I think about it more, using synchronized
on each function is a good start to ensure thread safety, but it might be a bottleneck under high contention. I think it makes more sense to use more fine-grained locking mechanisms instead of doing synchronized
for all public functions - the main challenge here should be ensuring data consistency between the heap and the indexMap (e.g. in siftUp
, updating on the heap and indexMap should be atomic).
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.
Implemented separate locks for read and write operations.
Also the unit test You can run |
9502ce3
to
06fd7bc
Compare
…nsearch-project#251) Signed-off-by: Dilaxn <dilaxn@gmail.com>
06fd7bc
to
4909bf7
Compare
Hi @ansjcy I’ve updated the code per your review: |
src/main/java/org/opensearch/plugin/insights/core/utils/IndexedPriorityQueue.java
Show resolved
Hide resolved
As .kkhatua mentioned:
We might need to do some benchmarking tests and CPU profiling to understand the performance gains. We need to:
|
@Dilaxn Hope you are able to make progress on this PR. Please let us know if you need any guidance :) |
@ansjcy @kkhatua Benchmark Setup: Here's a summary of key performance metrics:
Key Takeaways:
Please feel free to correct me if there’s any mistake. Thank you! |
Subject: ✅ Functional Parity & Concurrency Benchmark Validation for IPQ Hi @deshsidd , I've completed a comprehensive validation of our IndexedPriorityQueue (IPQ) against the suggested IndexMinPQ implementation.
⚙️ Concurrency Comparison
For this, I created a separate Gradle project with the help of GPT. The project is : https://github.com/Dilaxn/indexed-priority-queue-compat Thanks again for the guidance! Best regards, |
@Dilaxn This is very impressive, thanks for doing the performance benchmarking! Let me take a final check on the PR. |
Hi @ansjcy, Thanks for the feedback! |
@Dilaxn Thanks for the parity checks and the benchmarks. This is pretty impressive! Let me take a final look today. |
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.
LGTM overall. Thanks again for the change. Some minor comments @Dilaxn
Once addressed we can aim to rebase approve and merge.
src/main/java/org/opensearch/plugin/insights/core/utils/IndexedPriorityQueue.java
Show resolved
Hide resolved
src/main/java/org/opensearch/plugin/insights/core/utils/IndexedPriorityQueue.java
Outdated
Show resolved
Hide resolved
hmm, looking at the full benchmark run results I see latency increases on indexed PQ on almost all search workloads. I want us to do some extra benchmarks and cpu profilings. Let's hold on this PR for now until we fully understand the performance impact. |
Is your feature request related to a problem?
Yes. The current grouping logic uses a standard priority queue, forcing removal operations to run in O(n)O(n). This creates a significant performance bottleneck—especially in scenarios with frequent updates or large data sets.
What solution would you like?
Introduce an Indexed Priority Queue in the query grouping logic. By allowing direct lookup of an element’s position in the heap, arbitrary removals and updates can be done in O(logn) time instead of O(n). This significantly boosts performance for frequent insertions and removals.
What alternatives have you considered?
1.Binary Heap without indexing: Simpler to implement, but removal of an arbitrary element still requires an O(n) scan.
2.Fibonacci Heap or Pairing Heap: While they can provide even better theoretical performance for certain operations, they are more complex and less practical for our specific use case.
Additional context
I have reviewed the existing code and tested how the new Indexed Priority Queue integrates with the grouping logic. Early benchmarks show a noticeable reduction in latency for frequent queue updates. I plan to add thorough test coverage to validate correctness and performance improvements before merging.