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
KAFKA-13494: WindowKeyQuery and WindowRangeQuery #11567
Conversation
fd081af
to
9003a77
Compare
1416b91
to
88aa972
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.
Hey @patrickstuedi , it looks like your PR is still in progress, so I just gave it a quick pass. I hope this helps!
streams/src/main/java/org/apache/kafka/streams/query/WindowKeyQuery.java
Show resolved
Hide resolved
streams/src/main/java/org/apache/kafka/streams/query/WindowKeyQuery.java
Outdated
Show resolved
Hide resolved
streams/src/main/java/org/apache/kafka/streams/query/WindowRangeQuery.java
Show resolved
Hide resolved
if (query instanceof WindowRangeQuery) { | ||
@SuppressWarnings("unchecked") final WindowRangeQuery<Bytes, byte[]> windowRangeQuery = (WindowRangeQuery<Bytes, byte[]>) query; | ||
if (windowRangeQuery.getKey().isPresent()) { | ||
final Bytes key = windowRangeQuery.getKey().get(); | ||
final KeyValueIterator<Windowed<Bytes>, byte[]> keyValueIterator = this.fetch(key); | ||
@SuppressWarnings("unchecked") final R result = (R) keyValueIterator; | ||
final QueryResult<R> queryResult = QueryResult.forResult(result); | ||
return queryResult; | ||
} | ||
} | ||
|
||
return null; | ||
|
||
/* | ||
return StoreQueryUtils.handleBasicQueries( | ||
query, | ||
positionBound, | ||
collectExecutionInfo, | ||
this, | ||
position, | ||
context.taskId().partition() | ||
query, | ||
positionBound, | ||
collectExecutionInfo, | ||
this, | ||
position, | ||
context.taskId().partition() | ||
); | ||
|
||
*/ |
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.
I assume that the commented code is just a temporary state while you're finalizing the PR.
Since executing these queries doesn't require any specialization to the concrete store type, just the WindowStore and SessionStore interfaces, I think you might want to move the logic into StoreQueryUtils, but I've been thinking that, rather than having a universal method to handleBasicQueries
on any state store, the code will be cleaner with less type-checks and casts if you instead add a new handleWindowQueries
and handleSessionQueries
.
What do you think?
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.
I was looking at your merged PR and coded the store query routines in a similar spirit. For instance, I follow your practice in the MeteredKeyValue store and applied it to MeteredWindow/Session stores. Similarly for the InMemoryKeyValue store I noticed you handle range queries in place, so I did the same for the InMemoryWindowStore. Generally if you feel we should handle queries in the util class (as some are) then probably we should be consistent across the stores and remove the local handlers and instead delegate to the store utils? Or alternatively pass the per store handlers to the StoreQueryUtils.
streams/src/main/java/org/apache/kafka/streams/state/internals/InMemorySessionStore.java
Outdated
Show resolved
Hide resolved
if (windowRangeQuery.getTimeFrom().isPresent() && | ||
windowRangeQuery.getTimeTo().isPresent()) { |
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.
It looks like this method will return "unknown query" if the window bounds aren't specified. Can we make sure there's a test case for WindowRangeQuery#withKey
?
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.
Ah, I was previously mistaken about this. My prior comment was made with your KIP-763 (https://cwiki.apache.org/confluence/display/KAFKA/KIP-763%3A+Range+queries+with+open+endpoints) in mind.
Upon looking at the WindowStore code and the KIP, I now see that we don't support open ranges on window stores, and that the KIP was only about KeyValue range queries.
I'm also just now catching on that you're using Optional.of
there, not Optional.ofNullable
, so it's always the case that we either have both bounds and no key or a key with no bounds.
I had a second concern that the window store is not handling the case of a range query with a key and no bounds, but upon examination of the WindowStore, I can see that there is no method to handle that case, so it makes sense to return "unknown query" for it (since the objective at this moment is parity). It might be nice if we additionally explain that the error is because the store can't handle the parameterization rather than the query itself, though.
And finally, just to clarify, I do think we should consolidate on handling the queries in StoreQueryUtils
rather than in the stores themselves, so these comments are meant to apply to the handler in that class, and we should just delete the code in this class.
streams/src/test/java/org/apache/kafka/streams/integration/IQv2StoreIntegrationTest.java
Show resolved
Hide resolved
streams/src/test/java/org/apache/kafka/streams/integration/IQv2StoreIntegrationTest.java
Outdated
Show resolved
Hide resolved
streams/src/test/java/org/apache/kafka/streams/integration/IQv2StoreIntegrationTest.java
Outdated
Show resolved
Hide resolved
20966d6
to
cb77187
Compare
bf4d070
to
05625f7
Compare
…Query.java Co-authored-by: John Roesler <vvcephei@users.noreply.github.com>
…Query.java Co-authored-by: John Roesler <vvcephei@users.noreply.github.com>
c281005
to
b4671ff
Compare
Hey @patrickstuedi , thanks for the last batch of updates. Aside from the build failure, I think these are my last comments:
Once those points are addressed, we should be ready to merge! |
b4671ff
to
fc613ff
Compare
064b3ec
to
5d0e5c5
Compare
5d0e5c5
to
a193058
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.
Hey @patrickstuedi , I really hope you don't mind, but this PR has been dragging out for a long time, so I thought you might appreciate some help in getting it closed out.
I left a bunch of comments to explain the changes.
import java.time.Instant; | ||
import java.util.Optional; | ||
|
||
public class WindowRangeQuery<K, V> implements Query<KeyValueIterator<Windowed<K>, V>> { |
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.
public class WindowRangeQuery<K, V> implements Query<KeyValueIterator<Windowed<K>, V>> { | |
@Evolving | |
public class WindowRangeQuery<K, V> implements Query<KeyValueIterator<Windowed<K>, V>> { |
@@ -317,13 +318,25 @@ public boolean isOpen() { | |||
@Override | |||
public <R> QueryResult<R> query(final Query<R> query, final PositionBound positionBound, | |||
final boolean collectExecutionInfo) { | |||
|
|||
if (query instanceof WindowRangeQuery) { |
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.
Hey @patrickstuedi , Github won't let me comment on the prior conversation, but thanks for pointing out that oversight in the in-memory key-value store! I went ahead and removed it as well.
if (windowRangeQuery.getTimeFrom().isPresent() && | ||
windowRangeQuery.getTimeTo().isPresent()) { |
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.
Ah, I was previously mistaken about this. My prior comment was made with your KIP-763 (https://cwiki.apache.org/confluence/display/KAFKA/KIP-763%3A+Range+queries+with+open+endpoints) in mind.
Upon looking at the WindowStore code and the KIP, I now see that we don't support open ranges on window stores, and that the KIP was only about KeyValue range queries.
I'm also just now catching on that you're using Optional.of
there, not Optional.ofNullable
, so it's always the case that we either have both bounds and no key or a key with no bounds.
I had a second concern that the window store is not handling the case of a range query with a key and no bounds, but upon examination of the WindowStore, I can see that there is no method to handle that case, so it makes sense to return "unknown query" for it (since the objective at this moment is parity). It might be nice if we additionally explain that the error is because the store can't handle the parameterization rather than the query itself, though.
And finally, just to clarify, I do think we should consolidate on handling the queries in StoreQueryUtils
rather than in the stores themselves, so these comments are meant to apply to the handler in that class, and we should just delete the code in this class.
@@ -58,7 +58,7 @@ | |||
final Query<R> query, | |||
final StateStore store) { | |||
|
|||
return new FailedQueryResult<>( | |||
return forFailure( |
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.
This just made it easier to inline the "unknown query" message.
private WindowKeyQuery(final K key, | ||
final Optional<Instant> timeTo, | ||
final Optional<Instant> timeFrom) { |
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.
Since I was fixing stuff anyway, I went ahead and fixed a bunch of formatting issues that I didn't bother mentioning before.
Instant.ofEpochMilli(windowStart), | ||
Instant.ofEpochMilli(windowStart), | ||
extractor, | ||
mkSet(2) |
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.
Since we're specifying the key, we expect only to get back windows with the value for that key. The aggregation we specified is to sum all values for the key, and it comes out to 2
because we only write one value for each key; namely, the value is the same number as the key.
// miss the window start range | ||
shouldHandleWindowKeyQuery( | ||
2, | ||
Instant.ofEpochMilli(windowStart - 1), | ||
Instant.ofEpochMilli(windowStart - 1), | ||
extractor, | ||
mkSet() | ||
); | ||
|
||
// miss the key | ||
shouldHandleWindowKeyQuery( | ||
999, | ||
Instant.ofEpochMilli(windowStart), | ||
Instant.ofEpochMilli(windowStart), | ||
extractor, | ||
mkSet() | ||
); | ||
|
||
// miss both | ||
shouldHandleWindowKeyQuery( | ||
999, | ||
Instant.ofEpochMilli(windowStart - 1), | ||
Instant.ofEpochMilli(windowStart - 1), | ||
extractor, | ||
mkSet() | ||
); |
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.
It seemed like a good idea to check a few other query configurations, but none of them showed any problems.
try (final WindowStoreIterator<V> iterator = queryResult.get(partition).getResult()) { | ||
while (iterator.hasNext()) { | ||
actualValue.add(valueExtactor.apply(iterator.next().value)); | ||
} |
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.
These iterators need to be closed or they'll leak resources (it's the same for IQv1 as well).
try (final KeyValueIterator<Windowed<Integer>, V> iterator = queryResult.get(partition).getResult()) { | ||
while (iterator.hasNext()) { | ||
actualValue.add((Integer) iterator.next().value); | ||
} |
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.
Huh, looks like @vpapavas and I missed the need to close the iterator before.
); | ||
|
||
// Should fail to execute this query on a WindowStore. |
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.
I also added some cases to test the specialized failure messages.
The test failures were unrelated:
|
No description provided.