After our discussion, we discovered something intriguing. The definitions for the range and reverseRange methods in the ReadOnlyKeyValueStore are as follows: /** * Get an iterator over a given range of keys. This iterator must be closed after use. * The returned iterator must be safe from {@link java.util.ConcurrentModificationException}s * and must not return null values. ** Order is not guaranteed as bytes lexicographical ordering might not represent key order.* * * @param from The first key that could be in the range, where iteration starts from. * A null value indicates that the range starts with the first element in the store. * @param to The last key that could be in the range, where iteration ends. * A null value indicates that the range ends with the last element in the store. * @return The iterator for this range, from smallest to largest bytes. * @throws InvalidStateStoreException if the store is not initialized */ KeyValueIterator<K, V> range(K from, K to);
/** * Get a reverse iterator over a given range of keys. This iterator must be closed after use. * The returned iterator must be safe from {@link java.util.ConcurrentModificationException}s * and must not return null values. * *Order is not guaranteed as bytes lexicographical ordering might not represent key order.* * * @param from The first key that could be in the range, where iteration ends. * A null value indicates that the range starts with the first element in the store. * @param to The last key that could be in the range, where iteration starts from. * A null value indicates that the range ends with the last element in the store. * @return The reverse iterator for this range, from largest to smallest key bytes. * @throws InvalidStateStoreException if the store is not initialized */ default KeyValueIterator<K, V> reverseRange(K from, K to) { throw new UnsupportedOperationException(); } The query methods of RangeQuery ultimately invoke either the range method or the reverseRange method. However, as per the JavaDoc: the order is not guaranteed, since byte lexicographical ordering may not correspond to the actual key order. Sincerely, Hanyu On Fri, Oct 6, 2023 at 10:00 AM Hanyu (Peter) Zheng <pzh...@confluent.io> wrote: > Thank you, Matthias, for the detailed implementation and explanation. As > of now, our capability is limited to executing interactive queries on > individual partitions. To illustrate: > > Consider the IQv2StoreIntegrationTest: > > We have two partitions: > Partition0 contains key-value pairs: <0,0> and <2,2>. > Partition1 contains key-value pairs: <1,1> and <3,3>. > When executing RangeQuery.withRange(1,3), the results are: > > Partition0: [2] > Partition1: [1, 3] > To support functionalities like reverseRange and reverseAll, we can > introduce the withDescendingKeys() method. For instance, using > RangeQuery.withRange(1,3).withDescendingKeys(), the anticipated results are: > > Partition0: [2] > Partition1: [3, 1] > > In response to Hao's inquiry about the boundary issue, please refer to the > StoreQueryUtils class. The code snippet: > > iterator = kvStore.range(lowerRange.orElse(null), upperRange.orElse(null)); > indicates that when implementing range in each store, it's structured like: > > @Override > public KeyValueIterator<Bytes, byte[]> range(final Bytes from, final Bytes > to) { > if (from != null && to != null && from.compareTo(to) > 0) { > This section performs the necessary checks. > > Sincerely, > Hanyu > > On Thu, Oct 5, 2023 at 9:52 AM Hanyu (Peter) Zheng <pzh...@confluent.io> > wrote: > >> Hi, Hao, >> >> In this case, it will return an empty set or list in the end. >> >> Sincerely, >> Hanyu >> >> On Wed, Oct 4, 2023 at 10:29 PM Matthias J. Sax <mj...@apache.org> wrote: >> >>> Great discussion! >>> >>> It seems the only open question might be about ordering guarantees? >>> IIRC, we had a discussion about this in the past. >>> >>> >>> Technically (at least from my POV), existing `RangeQuery` does not have >>> a guarantee that data is return in any specific order (not even on a per >>> partitions bases). It just happens that RocksDB (and as pointed out by >>> Hanyu already, also the built-in in-memory store that is base on a >>> tree-map) allows us to return data ordered by key; as mentioned already, >>> this guarantee is limited on a per partition basis. >>> >>> If there would be custom store base on a hashed key-value store, this >>> store could implement RangeQuery and return data (even for a single >>> partition) with no ordering, without violating the contract. >>> >>> >>> >>> Thus, it could actually make sense, to extend `RangeQuery` and allow >>> three options: no-order, ascending, descending. For our existing >>> Rocks/InMemory implementations, no-order could be equal to ascending and >>> nothing changes effectively, but it might be a better API contract? -- >>> If we assume that there might be a custom hash-based store, such a store >>> could reject a query if "ascending" is required, or might need to do >>> more work to implement it (up to the store maintainer). This is actually >>> the beauty of IQv2 that different stores can pick what queries they want >>> to support. >>> >>> From an API contract point of view, it seems confusing to say: >>> specifying nothing means no guarantee (or ascending if the store can >>> offer it), but descending can we explicitly request. Thus, a hash-based >>> store, might be able to accept "order not specified query", but would >>> reject "descending". This seems to be somewhat unbalanced? >>> >>> Thus, I am wondering if we should actually add `withAscendingKeys()`, >>> too, even if it won't impact our current RocksDB/In-Memory >>> implementations? >>> >>> >>> The second question is about per-partition or across-partition ordering: >>> it's not possible right now to actually offer across-partition ordering >>> the way IQv2 is setup. The reason is, that the store that implements a >>> query type, is always a single shard. Thus, the implementation does not >>> have access to other shards. It's hard-coded inside Kafka Streams, to >>> query each shared, and to "accumulate" partial results, and return the >>> back to the user. Note that the API is: >>> >>> >>> > StateQueryResult<R> result = KafkaStreams.query(...); >>> > Map<Integer, QueryResult<R>> resultPerPartitions = >>> result.getPartitionResults(); >>> >>> >>> Thus, if we would want to offer across-partition ordering, we cannot do >>> it right now, because Kafka Streams does not know anything about the >>> semantics of the query it distributes... -- the result is an unknown >>> type <R>. We would need to extend IQv2 with an additional mechanism, >>> that allows users to plug in more custom code to "merge" multiple >>> partitions result into a "global result". This is clearly out-of-scope >>> for this KIP and would require a new KIP by itself. >>> >>> I seems that this contract, which is independent of the query type is >>> not well understood, and thus a big +1 to fix the documentation. I don't >>> think that this KIP must "define" anything, but it might of course be >>> worth to add the explanation why the KIP cannot even offer >>> global-ordering, as it's defined/limited by the IQv2 "framework" itself, >>> not the individual queries. >>> >>> >>> >>> -Matthias >>> >>> >>> >>> >>> On 10/4/23 4:38 PM, Hao Li wrote: >>> > Hi Hanyu, >>> > >>> > Thanks for the KIP! Seems there are already a lot of good discussions. >>> I >>> > only have two comments: >>> > >>> > 1. Please make it clear in >>> > ``` >>> > /** >>> > * Interactive range query using a lower and upper bound to >>> filter the >>> > keys returned. >>> > * @param lower The key that specifies the lower bound of the >>> range >>> > * @param upper The key that specifies the upper bound of the >>> range >>> > * @param <K> The key type >>> > * @param <V> The value type >>> > */ >>> > public static <K, V> RangeQuery<K, V> withRange(final K lower, >>> final K >>> > upper) { >>> > return new RangeQuery<>(Optional.ofNullable(lower), >>> > Optional.ofNullable(upper), true); >>> > } >>> > ``` >>> > that a `null` in lower or upper parameter means it's unbounded. >>> > 2. What's the behavior if lower is 3 and upper is 1? Is it >>> IllegalArgument >>> > or will this return an empty result? Maybe also clarify this in the >>> > document. >>> > >>> > Thanks, >>> > Hao >>> > >>> > >>> > On Wed, Oct 4, 2023 at 9:27 AM Hanyu (Peter) Zheng >>> > <pzh...@confluent.io.invalid> wrote: >>> > >>> >> For testing purposes, we previously used a Set to record the results >>> in >>> >> IQv2StoreIntegrationTest. Let's take an example where we now have two >>> >> partitions and four key-value pairs: <0,0> in p0, <1,1> in p1, <2,2> >>> in p0, >>> >> and <3,3> in p1. >>> >> >>> >> If we execute withRange(1,3), it will return a Set of <1, 2, 3>. >>> However, >>> >> if we run withRange(1,3).withDescendingKeys(), and still use a Set, >>> the >>> >> result will again be a Set of <1,2,3>. This means we won't be able to >>> >> determine whether the results have been reversed. >>> >> >>> >> To resolve this ambiguity, I've switched to using a List to record the >>> >> results, ensuring the order of retrieval from partitions p0 and p1. >>> So, >>> >> withRange(1,3) would yield a List of [2, 1, 3], whereas >>> >> withRange(1,3).withDescendingKeys() would produce a List of [2,3,1]. >>> >> >>> >> This ordering makes sense since RocksDB sorts its keys, and >>> InMemoryStore >>> >> uses a TreeMap structure, which means the keys are already sorted. >>> >> >>> >> Sincerely, >>> >> Hanyu >>> >> >>> >> On Wed, Oct 4, 2023 at 9:25 AM Hanyu (Peter) Zheng < >>> pzh...@confluent.io> >>> >> wrote: >>> >> >>> >>> Hi, Bruno >>> >>> >>> >>> Thank you for your suggestions, I will update them soon. >>> >>> Sincerely, >>> >>> >>> >>> Hanyu >>> >>> >>> >>> On Wed, Oct 4, 2023 at 9:25 AM Hanyu (Peter) Zheng < >>> pzh...@confluent.io> >>> >>> wrote: >>> >>> >>> >>>> Hi, Lucas, >>> >>>> >>> >>>> Thank you for your suggestions. >>> >>>> I will update the KIP and code together. >>> >>>> >>> >>>> Sincerely, >>> >>>> Hanyu >>> >>>> >>> >>>> On Tue, Oct 3, 2023 at 8:16 PM Hanyu (Peter) Zheng < >>> pzh...@confluent.io >>> >>> >>> >>>> wrote: >>> >>>> >>> >>>>> If we use WithDescendingKeys() to generate a RangeQuery to do the >>> >>>>> reveseQuery, how do we achieve the methods like withRange, >>> >> withUpperBound, >>> >>>>> and withLowerBound only in this method? >>> >>>>> >>> >>>>> On Tue, Oct 3, 2023 at 8:01 PM Hanyu (Peter) Zheng < >>> >> pzh...@confluent.io> >>> >>>>> wrote: >>> >>>>> >>> >>>>>> I believe there's no need to introduce a method like >>> >>>>>> WithDescendingKeys(). Instead, we can simply add a reverse flag to >>> >>>>>> RangeQuery. Each method within RangeQuery would then accept an >>> >> additional >>> >>>>>> parameter. If the reverse is set to true, it would indicate the >>> >> results >>> >>>>>> should be reversed. >>> >>>>>> >>> >>>>>> Initially, I introduced a reverse variable. When set to false, the >>> >>>>>> RangeQuery class behaves normally. However, when reverse is set to >>> >> true, >>> >>>>>> the RangeQuery essentially takes on the functionality of >>> >> ReverseRangeQuery. >>> >>>>>> Further details can be found in the "Rejected Alternatives" >>> section. >>> >>>>>> >>> >>>>>> In my perspective, RangeQuery is a class responsible for creating >>> a >>> >>>>>> series of RangeQuery objects. It offers methods such as withRange, >>> >>>>>> withUpperBound, and withLowerBound, allowing us to generate >>> objects >>> >>>>>> representing different queries. I'm unsure how adding a >>> >>>>>> withDescendingOrder() method would be compatible with the other >>> >> methods, >>> >>>>>> especially considering that, based on KIP 969, >>> WithDescendingKeys() >>> >> doesn't >>> >>>>>> appear to take any input variables. And if withDescendingOrder() >>> >> doesn't >>> >>>>>> accept any input, how does it return a RangeQuery? >>> >>>>>> >>> >>>>>> On Tue, Oct 3, 2023 at 4:37 PM Hanyu (Peter) Zheng < >>> >> pzh...@confluent.io> >>> >>>>>> wrote: >>> >>>>>> >>> >>>>>>> Hi, Colt, >>> >>>>>>> The underlying structure of inMemoryKeyValueStore is treeMap. >>> >>>>>>> Sincerely, >>> >>>>>>> Hanyu >>> >>>>>>> >>> >>>>>>> On Tue, Oct 3, 2023 at 4:34 PM Hanyu (Peter) Zheng < >>> >>>>>>> pzh...@confluent.io> wrote: >>> >>>>>>> >>> >>>>>>>> Hi Bill, >>> >>>>>>>> 1. I will update the KIP in accordance with the PR and >>> synchronize >>> >>>>>>>> their future updates. >>> >>>>>>>> 2. I will use that name. >>> >>>>>>>> 3. you mean add something about ordering at the motivation >>> section? >>> >>>>>>>> >>> >>>>>>>> Sincerely, >>> >>>>>>>> Hanyu >>> >>>>>>>> >>> >>>>>>>> >>> >>>>>>>> On Tue, Oct 3, 2023 at 4:29 PM Hanyu (Peter) Zheng < >>> >>>>>>>> pzh...@confluent.io> wrote: >>> >>>>>>>> >>> >>>>>>>>> Hi, Walker, >>> >>>>>>>>> >>> >>>>>>>>> 1. I will update the KIP in accordance with the PR and >>> synchronize >>> >>>>>>>>> their future updates. >>> >>>>>>>>> 2. I will use that name. >>> >>>>>>>>> 3. I'll provide additional details in that section. >>> >>>>>>>>> 4. I intend to utilize rangeQuery to achieve what we're >>> referring >>> >> to >>> >>>>>>>>> as reverseQuery. In essence, reverseQuery is merely a term. To >>> >> clear up any >>> >>>>>>>>> ambiguity, I'll make necessary adjustments to the KIP. >>> >>>>>>>>> >>> >>>>>>>>> Sincerely, >>> >>>>>>>>> Hanyu >>> >>>>>>>>> >>> >>>>>>>>> >>> >>>>>>>>> >>> >>>>>>>>> On Tue, Oct 3, 2023 at 4:09 PM Hanyu (Peter) Zheng < >>> >>>>>>>>> pzh...@confluent.io> wrote: >>> >>>>>>>>> >>> >>>>>>>>>> Ok, I will change it back to following the code, and update >>> them >>> >>>>>>>>>> together. >>> >>>>>>>>>> >>> >>>>>>>>>> On Tue, Oct 3, 2023 at 2:27 PM Walker Carlson >>> >>>>>>>>>> <wcarl...@confluent.io.invalid> wrote: >>> >>>>>>>>>> >>> >>>>>>>>>>> Hello Hanyu, >>> >>>>>>>>>>> >>> >>>>>>>>>>> Looking over your kip things mostly make sense but I have a >>> >> couple >>> >>>>>>>>>>> of >>> >>>>>>>>>>> comments. >>> >>>>>>>>>>> >>> >>>>>>>>>>> >>> >>>>>>>>>>> 1. You have "withDescandingOrder()". I think you mean >>> >>>>>>>>>>> "descending" :) >>> >>>>>>>>>>> Also there are still a few places in the do where its >>> called >>> >>>>>>>>>>> "setReverse" >>> >>>>>>>>>>> 2. Also I like "WithDescendingKeys()" better >>> >>>>>>>>>>> 3. I'm not sure of what ordering guarantees we are >>> offering. >>> >>>>>>>>>>> Perhaps we >>> >>>>>>>>>>> can add a section to the motivation clearly spelling out >>> the >>> >>>>>>>>>>> current >>> >>>>>>>>>>> ordering and the new offering? >>> >>>>>>>>>>> 4. When you say "use unbounded reverseQuery to achieve >>> >>>>>>>>>>> reverseAll" do >>> >>>>>>>>>>> you mean "use unbounded RangeQuery to achieve >>> reverseAll"? as >>> >>>>>>>>>>> far as I can >>> >>>>>>>>>>> tell we don't have a reverseQuery as a named object? >>> >>>>>>>>>>> >>> >>>>>>>>>>> >>> >>>>>>>>>>> Looking good so far >>> >>>>>>>>>>> >>> >>>>>>>>>>> best, >>> >>>>>>>>>>> Walker >>> >>>>>>>>>>> >>> >>>>>>>>>>> On Tue, Oct 3, 2023 at 2:13 PM Colt McNealy < >>> c...@littlehorse.io >>> >>> >>> >>>>>>>>>>> wrote: >>> >>>>>>>>>>> >>> >>>>>>>>>>>> Hello Hanyu, >>> >>>>>>>>>>>> >>> >>>>>>>>>>>> Thank you for the KIP. I agree with Matthias' proposal to >>> keep >>> >>>>>>>>>>> the naming >>> >>>>>>>>>>>> convention consistent with KIP-969. I favor the >>> >>>>>>>>>>> `.withDescendingKeys()` >>> >>>>>>>>>>>> name. >>> >>>>>>>>>>>> >>> >>>>>>>>>>>> I am curious about one thing. RocksDB guarantees that >>> records >>> >>>>>>>>>>> returned >>> >>>>>>>>>>>> during a range scan are lexicographically ordered by the >>> bytes >>> >>>>>>>>>>> of the keys >>> >>>>>>>>>>>> (either ascending or descending order, as specified in the >>> >>>>>>>>>>> query). This >>> >>>>>>>>>>>> means that results within a single partition are indeed >>> >>>>>>>>>>> ordered.** My >>> >>>>>>>>>>>> reading of KIP-805 suggests to me that you don't need to >>> >> specify >>> >>>>>>>>>>> the >>> >>>>>>>>>>>> partition number you are querying in IQv2, which means that >>> you >>> >>>>>>>>>>> can have a >>> >>>>>>>>>>>> valid reversed RangeQuery over a store with "multiple >>> >>>>>>>>>>> partitions" in it. >>> >>>>>>>>>>>> >>> >>>>>>>>>>>> Currently, IQv1 does not guarantee order of keys in this >>> >>>>>>>>>>> scenario. Does >>> >>>>>>>>>>>> IQv2 support ordering across partitions? Such an >>> implementation >>> >>>>>>>>>>> would >>> >>>>>>>>>>>> require opening a rocksdb range scan** on multiple rocksdb >>> >>>>>>>>>>> instances (one >>> >>>>>>>>>>>> per partition), and polling the first key of each. Whether >>> or >>> >>>>>>>>>>> not this is >>> >>>>>>>>>>>> ordered, could we please add that to the documentation? >>> >>>>>>>>>>>> >>> >>>>>>>>>>>> **(How is this implemented/guaranteed in an >>> >>>>>>>>>>> `inMemoryKeyValueStore`? I >>> >>>>>>>>>>>> don't know about that implementation). >>> >>>>>>>>>>>> >>> >>>>>>>>>>>> Colt McNealy >>> >>>>>>>>>>>> >>> >>>>>>>>>>>> *Founder, LittleHorse.dev* >>> >>>>>>>>>>>> >>> >>>>>>>>>>>> >>> >>>>>>>>>>>> On Tue, Oct 3, 2023 at 1:35 PM Hanyu (Peter) Zheng >>> >>>>>>>>>>>> <pzh...@confluent.io.invalid> wrote: >>> >>>>>>>>>>>> >>> >>>>>>>>>>>>> ok, I will update it. Thank you Matthias >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>>> Sincerely, >>> >>>>>>>>>>>>> Hanyu >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>>> On Tue, Oct 3, 2023 at 11:23 AM Matthias J. Sax < >>> >>>>>>>>>>> mj...@apache.org> >>> >>>>>>>>>>>> wrote: >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>>>> Thanks for the KIP Hanyu! >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> I took a quick look and it think the proposal makes sense >>> >>>>>>>>>>> overall. >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> A few comments about how to structure the KIP. >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> As you propose to not add `ReverseRangQuery` class, the >>> >> code >>> >>>>>>>>>>> example >>> >>>>>>>>>>>>>> should go into "Rejected Alternatives" section, not in the >>> >>>>>>>>>>> "Proposed >>> >>>>>>>>>>>>>> Changes" section. >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> For the `RangeQuery` code example, please omit all >>> existing >>> >>>>>>>>>>> methods >>> >>>>>>>>>>>> etc, >>> >>>>>>>>>>>>>> and only include what will be added/changed. This make it >>> >>>>>>>>>>> simpler to >>> >>>>>>>>>>>>>> read the KIP. >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> nit: typo >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>>> the fault value is false >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> Should be "the default value is false". >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> Not sure if `setReverse()` is the best name. Maybe >>> >>>>>>>>>>>> `withDescandingOrder` >>> >>>>>>>>>>>>>> (or similar, I guess `withReverseOrder` would also work) >>> >>>>>>>>>>> might be >>> >>>>>>>>>>>>>> better? Would be good to align to KIP-969 proposal that >>> >>>>>>>>>>> suggest do use >>> >>>>>>>>>>>>>> `withDescendingKeys` methods for "reverse key-range"; if >>> we >>> >>>>>>>>>>> go with >>> >>>>>>>>>>>>>> `withReverseOrder` we should change KIP-969 accordingly. >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> Curious to hear what others think about naming this >>> >>>>>>>>>>> consistently across >>> >>>>>>>>>>>>>> both KIPs. >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> -Matthias >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> On 10/3/23 9:17 AM, Hanyu (Peter) Zheng wrote: >>> >>>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>> >>> >>>>>>>>>>> >>> >> >>> https://cwiki.apache.org/confluence/display/KAFKA/KIP-985%3A+Add+reverseRange+and+reverseAll+query+over+kv-store+in+IQv2 >>> >>>>>>>>>>>>>>> >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>>> -- >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>>> [image: Confluent] <https://www.confluent.io> >>> >>>>>>>>>>>>> Hanyu (Peter) Zheng he/him/his >>> >>>>>>>>>>>>> Software Engineer Intern >>> >>>>>>>>>>>>> +1 (213) 431-7193 <+1+(213)+431-7193> >>> >>>>>>>>>>>>> Follow us: [image: Blog] >>> >>>>>>>>>>>>> < >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>> >>> >>>>>>>>>>> >>> >> >>> https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog >>> >>>>>>>>>>>>>> [image: >>> >>>>>>>>>>>>> Twitter] <https://twitter.com/ConfluentInc>[image: >>> LinkedIn] >>> >>>>>>>>>>>>> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: >>> >> Slack] >>> >>>>>>>>>>>>> <https://slackpass.io/confluentcommunity>[image: YouTube] >>> >>>>>>>>>>>>> <https://youtube.com/confluent> >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>>> [image: Try Confluent Cloud for Free] >>> >>>>>>>>>>>>> < >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>> >>> >>>>>>>>>>> >>> >> >>> https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic >>> >>>>>>>>>>>>>> >>> >>>>>>>>>>>>> >>> >>>>>>>>>>>> >>> >>>>>>>>>>> >>> >>>>>>>>>> >>> >>>>>>>>>> >>> >>>>>>>>>> -- >>> >>>>>>>>>> >>> >>>>>>>>>> [image: Confluent] <https://www.confluent.io> >>> >>>>>>>>>> Hanyu (Peter) Zheng he/him/his >>> >>>>>>>>>> Software Engineer Intern >>> >>>>>>>>>> +1 (213) 431-7193 <+1+(213)+431-7193> >>> >>>>>>>>>> Follow us: [image: Blog] >>> >>>>>>>>>> < >>> >> >>> https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog >>> >>> [image: >>> >>>>>>>>>> Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] >>> >>>>>>>>>> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: >>> Slack] >>> >>>>>>>>>> <https://slackpass.io/confluentcommunity>[image: YouTube] >>> >>>>>>>>>> <https://youtube.com/confluent> >>> >>>>>>>>>> >>> >>>>>>>>>> [image: Try Confluent Cloud for Free] >>> >>>>>>>>>> < >>> >> >>> https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic >>> >>> >>> >>>>>>>>>> >>> >>>>>>>>> >>> >>>>>>>>> >>> >>>>>>>>> -- >>> >>>>>>>>> >>> >>>>>>>>> [image: Confluent] <https://www.confluent.io> >>> >>>>>>>>> Hanyu (Peter) Zheng he/him/his >>> >>>>>>>>> Software Engineer Intern >>> >>>>>>>>> +1 (213) 431-7193 <+1+(213)+431-7193> >>> >>>>>>>>> Follow us: [image: Blog] >>> >>>>>>>>> < >>> >> >>> https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog >>> >>> [image: >>> >>>>>>>>> Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] >>> >>>>>>>>> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] >>> >>>>>>>>> <https://slackpass.io/confluentcommunity>[image: YouTube] >>> >>>>>>>>> <https://youtube.com/confluent> >>> >>>>>>>>> >>> >>>>>>>>> [image: Try Confluent Cloud for Free] >>> >>>>>>>>> < >>> >> >>> https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic >>> >>> >>> >>>>>>>>> >>> >>>>>>>> >>> >>>>>>>> >>> >>>>>>>> -- >>> >>>>>>>> >>> >>>>>>>> [image: Confluent] <https://www.confluent.io> >>> >>>>>>>> Hanyu (Peter) Zheng he/him/his >>> >>>>>>>> Software Engineer Intern >>> >>>>>>>> +1 (213) 431-7193 <+1+(213)+431-7193> >>> >>>>>>>> Follow us: [image: Blog] >>> >>>>>>>> < >>> >> >>> https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog >>> >>> [image: >>> >>>>>>>> Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] >>> >>>>>>>> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] >>> >>>>>>>> <https://slackpass.io/confluentcommunity>[image: YouTube] >>> >>>>>>>> <https://youtube.com/confluent> >>> >>>>>>>> >>> >>>>>>>> [image: Try Confluent Cloud for Free] >>> >>>>>>>> < >>> >> >>> https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic >>> >>> >>> >>>>>>>> >>> >>>>>>> >>> >>>>>>> >>> >>>>>>> -- >>> >>>>>>> >>> >>>>>>> [image: Confluent] <https://www.confluent.io> >>> >>>>>>> Hanyu (Peter) Zheng he/him/his >>> >>>>>>> Software Engineer Intern >>> >>>>>>> +1 (213) 431-7193 <+1+(213)+431-7193> >>> >>>>>>> Follow us: [image: Blog] >>> >>>>>>> < >>> >> >>> https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog >>> >>> [image: >>> >>>>>>> Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] >>> >>>>>>> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] >>> >>>>>>> <https://slackpass.io/confluentcommunity>[image: YouTube] >>> >>>>>>> <https://youtube.com/confluent> >>> >>>>>>> >>> >>>>>>> [image: Try Confluent Cloud for Free] >>> >>>>>>> < >>> >> >>> https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic >>> >>> >>> >>>>>>> >>> >>>>>> >>> >>>>>> >>> >>>>>> -- >>> >>>>>> >>> >>>>>> [image: Confluent] <https://www.confluent.io> >>> >>>>>> Hanyu (Peter) Zheng he/him/his >>> >>>>>> Software Engineer Intern >>> >>>>>> +1 (213) 431-7193 <+1+(213)+431-7193> >>> >>>>>> Follow us: [image: Blog] >>> >>>>>> < >>> >> >>> https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog >>> >>> [image: >>> >>>>>> Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] >>> >>>>>> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] >>> >>>>>> <https://slackpass.io/confluentcommunity>[image: YouTube] >>> >>>>>> <https://youtube.com/confluent> >>> >>>>>> >>> >>>>>> [image: Try Confluent Cloud for Free] >>> >>>>>> < >>> >> >>> https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic >>> >>> >>> >>>>>> >>> >>>>> >>> >>>>> >>> >>>>> -- >>> >>>>> >>> >>>>> [image: Confluent] <https://www.confluent.io> >>> >>>>> Hanyu (Peter) Zheng he/him/his >>> >>>>> Software Engineer Intern >>> >>>>> +1 (213) 431-7193 <+1+(213)+431-7193> >>> >>>>> Follow us: [image: Blog] >>> >>>>> < >>> >> >>> https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog >>> >>> [image: >>> >>>>> Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] >>> >>>>> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] >>> >>>>> <https://slackpass.io/confluentcommunity>[image: YouTube] >>> >>>>> <https://youtube.com/confluent> >>> >>>>> >>> >>>>> [image: Try Confluent Cloud for Free] >>> >>>>> < >>> >> >>> https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic >>> >>> >>> >>>>> >>> >>>> >>> >>>> >>> >>>> -- >>> >>>> >>> >>>> [image: Confluent] <https://www.confluent.io> >>> >>>> Hanyu (Peter) Zheng he/him/his >>> >>>> Software Engineer Intern >>> >>>> +1 (213) 431-7193 <+1+(213)+431-7193> >>> >>>> Follow us: [image: Blog] >>> >>>> < >>> >> >>> https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog >>> >>> [image: >>> >>>> Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] >>> >>>> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] >>> >>>> <https://slackpass.io/confluentcommunity>[image: YouTube] >>> >>>> <https://youtube.com/confluent> >>> >>>> >>> >>>> [image: Try Confluent Cloud for Free] >>> >>>> < >>> >> >>> https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic >>> >>> >>> >>>> >>> >>> >>> >>> >>> >>> -- >>> >>> >>> >>> [image: Confluent] <https://www.confluent.io> >>> >>> Hanyu (Peter) Zheng he/him/his >>> >>> Software Engineer Intern >>> >>> +1 (213) 431-7193 <+1+(213)+431-7193> >>> >>> Follow us: [image: Blog] >>> >>> < >>> >> >>> https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog >>> >>> [image: >>> >>> Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] >>> >>> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] >>> >>> <https://slackpass.io/confluentcommunity>[image: YouTube] >>> >>> <https://youtube.com/confluent> >>> >>> >>> >>> [image: Try Confluent Cloud for Free] >>> >>> < >>> >> >>> https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic >>> >>> >>> >>> >>> >> >>> >> >>> >> -- >>> >> >>> >> [image: Confluent] <https://www.confluent.io> >>> >> Hanyu (Peter) Zheng he/him/his >>> >> Software Engineer Intern >>> >> +1 (213) 431-7193 <+1+(213)+431-7193> >>> >> Follow us: [image: Blog] >>> >> < >>> >> >>> https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog >>> >>> [image: >>> >> Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] >>> >> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] >>> >> <https://slackpass.io/confluentcommunity>[image: YouTube] >>> >> <https://youtube.com/confluent> >>> >> >>> >> [image: Try Confluent Cloud for Free] >>> >> < >>> >> >>> https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic >>> >>> >>> >> >>> > >>> >> >> >> -- >> >> [image: Confluent] <https://www.confluent.io> >> Hanyu (Peter) Zheng he/him/his >> Software Engineer Intern >> +1 (213) 431-7193 <+1+(213)+431-7193> >> Follow us: [image: Blog] >> <https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog>[image: >> Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] >> <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] >> <https://slackpass.io/confluentcommunity>[image: YouTube] >> <https://youtube.com/confluent> >> >> [image: Try Confluent Cloud for Free] >> <https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic> >> > > > -- > > [image: Confluent] <https://www.confluent.io> > Hanyu (Peter) Zheng he/him/his > Software Engineer Intern > +1 (213) 431-7193 <+1+(213)+431-7193> > Follow us: [image: Blog] > <https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog>[image: > Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] > <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] > <https://slackpass.io/confluentcommunity>[image: YouTube] > <https://youtube.com/confluent> > > [image: Try Confluent Cloud for Free] > <https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic> > -- [image: Confluent] <https://www.confluent.io> Hanyu (Peter) Zheng he/him/his Software Engineer Intern +1 (213) 431-7193 <+1+(213)+431-7193> Follow us: [image: Blog] <https://www.confluent.io/blog?utm_source=footer&utm_medium=email&utm_campaign=ch.email-signature_type.community_content.blog>[image: Twitter] <https://twitter.com/ConfluentInc>[image: LinkedIn] <https://www.linkedin.com/in/hanyu-peter-zheng/>[image: Slack] <https://slackpass.io/confluentcommunity>[image: YouTube] <https://youtube.com/confluent> [image: Try Confluent Cloud for Free] <https://www.confluent.io/get-started?utm_campaign=tm.fm-apac_cd.inbound&utm_source=gmail&utm_medium=organic>