Skip to content

KAFKA-2593 Key value stores can use specified serializers and deserializers#255

Closed
rhauch wants to merge 6 commits into
apache:trunkfrom
rhauch:kafka-2593
Closed

KAFKA-2593 Key value stores can use specified serializers and deserializers#255
rhauch wants to merge 6 commits into
apache:trunkfrom
rhauch:kafka-2593

Conversation

@rhauch

@rhauch rhauch commented Sep 28, 2015

Copy link
Copy Markdown
Contributor

Add support for the key value stores to use specified serializers and deserializers (aka, "serdes"). Prior to this change, the stores were limited to only the default serdes specified in the topology's configuration and exposed to the processors via the ProcessorContext.

Now, using InMemoryKeyValueStore and RocksDBKeyValueStore are similar: both are parameterized on the key and value types, and both have similar multiple static factory methods. The static factory methods either take explicit key and value serdes, take key and value class types so the serdes can be inferred (only for the built-in serdes for string, integer, long, and byte array types), or use the default serdes on the ProcessorContext.

@asfbot

asfbot commented Sep 28, 2015

Copy link
Copy Markdown

kafka-trunk-git-pr #584 FAILURE
Looks like there's a problem with this pull request

@rhauch

rhauch commented Sep 28, 2015

Copy link
Copy Markdown
Contributor Author

I updated the PR to correct the checkstyle errors.

@asfbot

asfbot commented Sep 28, 2015

Copy link
Copy Markdown

kafka-trunk-git-pr #585 SUCCESS
This pull request looks good

@guozhangwang

Copy link
Copy Markdown
Contributor

LGTM overall, one general comment: could we put various create() functions of different store classes into a single sharable support / factory class, otherwise future users would need to create those functions for each store?

@rhauch

rhauch commented Sep 30, 2015

Copy link
Copy Markdown
Contributor Author

@guozhangwang, sure, it would be possible to put all of the create methods in a single place. However, I've never been a fan of centralized factory methods because it makes other implementations second-class citizens, since they can't be created using the factory. (Yes, we could use Java's ServiceLoader mechanism, but that's IMO that's overkill and still doesn't allow customization of create methods in other KeyValueStore implementations.)

Now, I do agree that the 2 existing implementations (or 3 including #256) all have similar factory methods with identical signatures, and that might suggest value to a common way to build them. But perhaps a builder (rather than a factory class) might be more usable by processors and reusable by other KeyValueStore implementations. For example:

InMemoryStore<String,Integer> store = InMemoryStore.create(name,context)
                                                   .withKeySerializer(new StringSerializer())
                                                   .withKeyDeserializer(new StringDeserializer())
                                                   .withValueSerializer(new IntegerSerializer())
                                                   .withValueDeserializer(new IntegerDeserializer())
                                                   .withSystemTime()
                                                   .build();

It would be possible to create a base interface and impl for this builder in KeyValueStore that subclasses could reuse and extend with their own methods. The builder could have overloaded methods, too, without resulting in combinatorial explosion of separate factory methods. For example, the builder could have both withSystemTime() and withTime(Time), and the last one used wins. And it could have multiple withKeySerializer methods that take different things, such as a serializer instance, the serializer class (to automatically create the instance using a no-arg constructor), or the key class (for built-in serdes only). IDEs also make builders pretty convenient to use.

This will expand the Kafka Streams code a bit, but IMO it is worth it because it will make it even easier (albeit a bit more verbose) to create KeyValueStore instances in processors.

I'll prototype something up, but should I do that as part of this PR/issue, or under a separate "Simplified approach to create KeyValueStores" issue?

@rhauch

rhauch commented Sep 30, 2015

Copy link
Copy Markdown
Contributor Author

@guozhangwang, it may be difficult or unattractive to use a builder with the generic key and value types, unless somehow they're specified in the create method or by explicitly typing:

InMemoryKeyValueStore<String, Integer> store = InMemoryKeyValueStore.<String, Integer>create("something", context)
                                               .withKeySerializer(new StringSerializer())
                                               .withKeyDeserializer(new StringDeserializer())
                                               .withValueSerializer(new IntegerSerializer())
                                               .withValueDeserializer(new IntegerDeserializer())
                                               .withSystemTime()
                                               .build();

The good news is that the above code forces the IDE to type-check the with... methods, but it's not pretty. I'll keep trying.

@rhauch

rhauch commented Sep 30, 2015

Copy link
Copy Markdown
Contributor Author

@guozhangwang, there doesn't appear to be a decent way to use a builder while returning a parameterized type. The example mentioned above is pretty verbose, and IMO the static factory methods on each KeyValueStore implementation class is the most pragmatic way to create instances. It's not ideal, because as you pointed out each class has static methods with identical (or at least nearly identical) signatures. OTOH, I'm not really sure how this might be simplified another way.

BTW, I'd like to pull some of the commits from #256 into this PR, which is really where they belong. Specifically, all but the one commit that creates the InMemoryLRUCacheStore and corresponding test class. I've already moved them over on a local branch and ran a build, so if you're okay with it I'll push the additional commits to this PR and also update #256.

@rhauch

rhauch commented Oct 1, 2015

Copy link
Copy Markdown
Contributor Author

@guozhangwang, it occurred to me that your goal might actually be to make the implementations of KeyValueStore largely transparent to users, and instead to emphasize that stores can either keep all entries in-memory (and is thus largely limited by available heap) while the other holds entries outside of the heap and this is largely unlimited. Is this accurate?

If this is true, and since builders are relatively difficult to implement for parameterized stores, then I agree that a factory class might be the best way forward. Do you have time to list what you might have been thinking of for an API? Perhaps given some store defined in a Processor:

private KeyValueStore<String, Integer> myStore;

the store could be created in init(...) with something like:

myStore = Stores.inMemory()
                .create(name,context,...);

or with limited memory (see #256):

myStore = Stores.inMemory()
                .maxCount(4096)
                .create(name,context,...);

or to use the database:

myStore = Stores.offHeap()
                .create(name,context,...);

What do you think? This is somewhat like a builder, but all of the parameters that drive the key and value types have to be passed in at once (e.g., only the create(...) methods would use generics).

@rhauch
rhauch force-pushed the kafka-2593 branch 3 times, most recently from b816811 to 694d6f4 Compare October 7, 2015 15:43
@rhauch

rhauch commented Oct 7, 2015

Copy link
Copy Markdown
Contributor Author

@guozhangwang, rebased this PR. The Jenkins build seemed to have failed on core -- is there a way I can resubmit it?

@rhauch

rhauch commented Oct 7, 2015

Copy link
Copy Markdown
Contributor Author

@guozhangwang, I just added a commit that replaces all of the static factory methods for the different kinds of key-value stores with a simple builder mechanism. Although a fluent API can be used for some parameters, most still have to be passed in to the final 'create(...)' method -- this keeps the API and the implementation very simple. (We could break up more of the parameters into separate fluent-style methods, but this very quickly makes the builder very complicated. See comments above for detail why.)

@rhauch

rhauch commented Oct 8, 2015

Copy link
Copy Markdown
Contributor Author

Once again, the Jenkins test failures seem irrelevant.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the benefits of requiring ProcessorContextImpl implementing the Supplier interface rather than do the casting?

I originally think there will be two categories of users of Kafka Streaming: 1) normal users will only be interfacing the public APIs, 2) some small groups of developers that add windowing def / etc themselves will need to be exposed with the internal implementations in order to code, so it is OK to ask them to know about ProcessorContextImpl. But maybe there are something that I missed here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@guozhangwang: The primary benefit is that we can use these components in unit tests using a mock ProcessorContext. Without this small abstraction, all unit tests would have to use a real ProcessorContextImpl and everything that requires.

@guozhangwang

Copy link
Copy Markdown
Contributor

@rhauch sorry for the late review. I think it's better to keep this PR separate from #256 (but seems it does not contain the commits from that ticket anyways). Overall I think it is good, and I like the Stores.create() mechanism except some minor comment about withTime().

@rhauch
rhauch force-pushed the kafka-2593 branch 2 times, most recently from 75ec501 to 7f6367b Compare October 9, 2015 18:22
@rhauch

rhauch commented Oct 9, 2015

Copy link
Copy Markdown
Contributor Author

@guozhangwang, incorporated your comments, rebased upon trunk, successfully ran build locally, and updated this PR.

Add support for the key value stores to use specified serializers and deserializers (aka, "serdes"). Prior to this change, the stores were limited to only the default serdes specified in the topology's configuration and exposed to the processors via the ProcessorContext.

Now, using InMemoryKeyValueStore and RocksDBKeyValueStore are similar: both are parameterized on the key and value types, and both have similar multiple static factory methods. The static factory methods either take explicit key and value serdes, take key and value class types so the serdes can be inferred (only for the built-in serdes for string, integer, long, and byte array types), or use the default serdes on the ProcessorContext.
…orContextImpl

The cast was used to access ProcessorContextImpl.recordCollector(), and the cast prevent using MeteredKeyValueStore subclasses, SinkNode, and SlidingWindow objects in tests where something other than ProcessorContextImpl was used. This change introduces a RecordCollector.Supplier interface to define this `recordCollector()` method, and changed ProcessorContextImpl and MockProcessorContext to both implement this interface. Now, MeteredKeyValueStore, SinkNode, and SlidingWindow can cast to the interface to access the record collector, making it possible to use them inside unit tests.
Added unit tests for the existing InMemoryKeyValueStore and RocksDBKeyValueStore implementations. A new KeyValueStoreTestDriver class does most of the work and simplifies the tests.
… directory

The ProcessorTopologyTest and AbstractKeyValueStoreTest test classes have methods created a shared directory used by all their tests to store local state. When the computer has multiple processors, the build system executes test in parallel, and they interfere with other tests using the same directory. Solve the problem by adding a utility class to create temporary directories and automatically clean them up.
Added a simple fluent builder API to create key-value stores that use in-memory or a local database. Other kinds of state stores can be added in the future.
@rhauch

rhauch commented Oct 12, 2015

Copy link
Copy Markdown
Contributor Author

@guozhangwang, I've updated the PR to use the Stores.create(...) approach, and the full build passed locally before I pushed. The Jenkins test failure is unrelated to this PR.

@rhauch

rhauch commented Oct 14, 2015

Copy link
Copy Markdown
Contributor Author

@guozhangwang: Is there anything else you need from me before this can be merged? Once it's merged, I can then rebase #256.

@asfgit asfgit closed this in 6e57122 Oct 14, 2015
@guozhangwang

Copy link
Copy Markdown
Contributor

LGTM, thanks @rhauch !

asfgit pushed a commit that referenced this pull request Oct 16, 2015
…nding in-memory stores

Added a new `KeyValueStore` implementation called `InMemoryLRUCacheStore` that keeps a maximum number of entries in-memory, and as the size exceeds the capacity the least-recently used entry is removed from the store and the backing topic. Also added unit tests for this new store and the existing `InMemoryKeyValueStore` and `RocksDBKeyValueStore` implementations. A new `KeyValueStoreTestDriver` class simplifies all of the other tests, and can be used by other libraries to help test their own custom implementations.

This PR depends upon [KAFKA-2593](https://issues.apache.org/jira/browse/KAFKA-2593) and its PR at #255. Once that PR is merged, I can rebase this PR if desired.

Two issues were uncovered when creating these new unit tests, and both are also addressed as separate (small) commits in this PR:
* The `RocksDBKeyValueStore` initialization was not creating the file system directory if missing.
* `MeteredKeyValueStore` was casting to `ProcessorContextImpl` to access the `RecordCollector`, which prevent using `MeteredKeyValueStore` implementations in tests where something other than `ProcessorContextImpl` was used. The fix was to introduce a `RecordCollector.Supplier` interface to define this `recordCollector()` method, and change `ProcessorContextImpl` and `MockProcessorContext` to both implement this interface. Now, `MeteredKeyValueStore` can cast to the new interface to access the record collector rather than to a single concrete implementation, making it possible to use any and all current stores inside unit tests.

Author: Randall Hauch <rhauch@gmail.com>

Reviewers: Edward Ribeiro, Guozhang Wang

Closes #256 from rhauch/kafka-2594
@rhauch
rhauch deleted the kafka-2593 branch October 27, 2015 15:08
jsancio pushed a commit to jsancio/kafka that referenced this pull request Aug 6, 2019
…e#255)

- Pausing within the synchonization will cause metrics collection to be blocked.
- Move pause to outside of loop to reduce CPU burn until TierTopicManager is ready.
blcksrx pushed a commit to blcksrx/kafka that referenced this pull request Feb 15, 2020
…#255)

Reviewers: Manikumar Reddy <manikumar.reddy@gmail.com>
stanislavkozlovski pushed a commit to stanislavkozlovski/kafka that referenced this pull request Feb 18, 2020
…#255)

Reviewers: Manikumar Reddy <manikumar.reddy@gmail.com>
wyuka pushed a commit to wyuka/kafka that referenced this pull request Feb 4, 2022
* Disabled the flaky test testTopicUncleanLeaderElectionEnable and testUncleanLeaderElectionEnabled


Co-authored-by: Hao Geng <hgeng@hgeng-mn1.linkedin.biz>
davide-armand pushed a commit to aiven/kafka that referenced this pull request Dec 1, 2025
jeqo pushed a commit to aiven/kafka that referenced this pull request Jan 16, 2026
traceyyoshima added a commit to traceyyoshima/kafka that referenced this pull request May 17, 2026
…pache#257)

Refreshed snapshot from the kafka format sweep review. Five
language-engine PRs landed in this session:

- PR apache#253 — Annotation paren anchor for continuation slots
- PR apache#254 — StructuralBody2x catalog class for inline lambda bodies
- PR apache#255 — Lambda body as continuation enclosing scope
- PR apache#256 — Inline-lambda body close brace catalog (+0 / +projectDelta)
- PR apache#257 — emittedLineStarts restricted to OWNER's prefix slot

Sweep stats (post-apache#257):
  files scanned : 5,834
  files changed : 1,027 (17.6%)   [initial PR #6: 1,038]
  byte delta    : -10,877          [initial PR #6: -6,661]
  reformat time : 7.2s total, 1.23 ms/file

The larger byte delta vs. the initial sweep reflects PR apache#257
fixing the AsyncKafkaConsumerTest-style "body at col 48" pattern
across multiple files — body statements no longer indent to the
inflated close-paren-line column, so each affected line shrinks
by 30+ characters.

Remaining patterns deferred to subsequent PRs:
  - Chain-dot over-application on Class.STATIC_FIELD / new X()
    (AdminClientConfig L273/L282, WriteTxnMarkersRequest L135,
    TxnOffsetCommitRequest L156/L159)
  - Partial-shift inconsistencies (StreamsResetter, ClientUtilsTest,
    RequestResponseTest, LeaveGroupRequest*, AlterReplicaLogDirs)
  - Trailing-whitespace cleanups on blank lines (incidental noise)
traceyyoshima added a commit to traceyyoshima/kafka that referenced this pull request May 17, 2026
…pache#258)

Refreshed snapshot. Six language-engine PRs landed this session:

- PR apache#253 — Annotation paren anchor for continuation slots
- PR apache#254 — StructuralBody2x catalog class for inline lambda bodies
- PR apache#255 — Lambda body as continuation enclosing scope
- PR apache#256 — Inline-lambda body close brace catalog (+0 / +projectDelta)
- PR apache#257 — emittedLineStarts restricted to OWNER's prefix slot
- PR apache#258 — alignmentTargets restricted to OWNER's prefix slot

Sweep stats (post-apache#258):
  files scanned : 5,834
  files changed : 1,000 (17.1%)   [initial PR #6: 1,038]
  byte delta    : -10,630          [initial PR #6: -6,661]
  reformat time : 7.1s total, 1.22 ms/file

Remaining patterns (deferred):
  - Outer chain-dot receiver-anchor variations (TxnOffsetCommitRequest
    L156/L159, RequestResponseTest L3373+, etc.) — separate path from
    PR apache#258's inner-arg fix
  - Small continuation normalizations of non-standard source
    (StreamsResetter, ClientUtilsTest, LeaveGroupRequestTest)
  - Trailing-whitespace cleanups on blank lines (incidental noise)
traceyyoshima added a commit to traceyyoshima/kafka that referenced this pull request May 17, 2026
…pache#259)

Refreshed snapshot. Seven language-engine PRs landed this session:

- PR apache#253 — Annotation paren anchor for continuation slots
- PR apache#254 — StructuralBody2x catalog class for inline lambda bodies
- PR apache#255 — Lambda body as continuation enclosing scope
- PR apache#256 — Inline-lambda body close brace catalog
- PR apache#257 — emittedLineStarts restricted to OWNER's prefix slot
- PR apache#258 — alignmentTargets restricted to OWNER's prefix slot
- PR apache#259 — Chain wrap preservation when siblings agree on column

Sweep stats (post-apache#259):
  files scanned : 5,834
  files changed : 990 (17.0%)     [initial PR #6: 1,038]
  byte delta    : -10,685          [initial PR #6: -6,661]
  reformat time : ~7s total

Most called-out files from the user's original review now fully
closed:
  - ShareConsumerTest (body + close brace)
  - ProducerIdExpirationTest
  - DeleteAclsResponse
  - CustomQuotaCallbackTest
  - AsyncKafkaConsumerTest (body indent regression)
  - AdminClientConfig L273/L282
  - WriteTxnMarkersRequest L135
  - TxnOffsetCommitRequest L156-159
  - RequestResponseTest chain shifts
  - AlterReplicaLogDirsRequestTest (cascade-closed)

Remaining residuals (~5 files, small):
  - StreamsResetter L580 (single `||` continuation normalisation)
  - ClientUtilsTest L79/L143 (continuation normalisation)
  - LeaveGroupRequestTest L55 (continuation shift)
  - LeaveGroupResponseTest (trailing-ws on blank lines)
  - ReassignPartitionsUnitTest (single line)

These are mostly the formatter normalising slightly non-standard
source — not really bugs.
traceyyoshima added a commit to traceyyoshima/kafka that referenced this pull request May 17, 2026
…pache#260)

Refreshed snapshot. Eight language-engine PRs landed this session:

- PR apache#253 — Annotation paren anchor for continuation slots
- PR apache#254 — StructuralBody2x catalog class for inline lambda bodies
- PR apache#255 — Lambda body as continuation enclosing scope
- PR apache#256 — Inline-lambda body close brace catalog
- PR apache#257 — emittedLineStarts restricted to OWNER's prefix slot
- PR apache#258 — alignmentTargets restricted to OWNER's prefix slot
- PR apache#259 — Chain wrap preservation when siblings agree on column
- PR apache#260 — Binary OPERATOR_PREFIX consults outermost binary's anchor

Sweep stats (post-apache#260):
  files scanned : 5,834
  files changed : 989 (17.0%)     [initial PR #6: 1,038]
  byte delta    : -10,663
  reformat time : ~7s total

Almost all called-out files from the user's original review now
fully closed. Remaining residuals (<5 files) are correct
normalisations of slightly non-standard source or trailing
whitespace inside comment content (intentionally out of scope for
this engine version per user design choice).
traceyyoshima added a commit to traceyyoshima/kafka that referenced this pull request May 17, 2026
…pache#261)

Refreshed snapshot. Nine language-engine PRs landed this session:

- PR apache#253 — Annotation paren anchor for continuation slots
- PR apache#254 — StructuralBody2x catalog class for inline lambda bodies
- PR apache#255 — Lambda body as continuation enclosing scope
- PR apache#256 — Inline-lambda body close brace catalog
- PR apache#257 — emittedLineStarts restricted to OWNER's prefix slot
- PR apache#258 — alignmentTargets restricted to OWNER's prefix slot
- PR apache#259 — Chain wrap preservation when siblings agree on column
- PR apache#260 — Binary OPERATOR_PREFIX consults outermost binary's anchor
- PR apache#261 — Nested continuation slots use innermost enclosing scope

Sweep stats (post-apache#261):
  files scanned : 5,834
  files changed : 986 (16.9%)     [initial PR #6: 1,038]
  byte delta    : -11,137          [initial PR #6: -6,661]
  reformat time : ~7s total

PR #7 review comments status:
  - Comments 1+2 closed (PR apache#261)
  - Comments 3 + 4/5 deferred (workbench-first investigation; diagnosis
    captured in language-engine memory project_pr7_deferred_comments_2026_05_17.md)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants