Repository navigation
fix: Read each line of a stream only once, which takes about 14 percent less CPU per event. - #11
Merged
Merged
Conversation
… as Java package. Wherever a registry requires a namespace, the client SDKs and the image of EventSourcingDB use thenativeweb: Packagist, Go modules, Docker Hub. Maven requires one, so the artifacts are now io.thenativeweb:eventsourcingdb and io.thenativeweb:eventsourcingdb-testcontainers. The Java packages follow the group ID, as is the convention in Java, and are now io.thenativeweb.eventsourcingdb and its testcontainers subpackage. Nothing has been published yet, so no caller is affected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hc2MPSLu7HDHTm8HiBsrDz
…NullAway. Java can not say whether a value may be null. Both packages are now @NullMarked, so everything is non-null unless it is marked @nullable, as the optional fields of EventCandidate and of the options are. Kotlin reads these annotations, and Spring Boot 4 uses them, too. NullAway checks the code against the annotations as part of the build, and runs as the only check of Error Prone. Making LineStream pass it made explicit that it reads only once it has opened its stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hc2MPSLu7HDHTm8HiBsrDz
…fter 10 seconds by default. The client created an HTTP client of its own that nobody could configure, without a timeout for connecting, a proxy, or TLS. An unreachable server left a request waiting for the timeout of the operating system. A Client now takes ClientOptions, whose HTTP client it sends requests with if one is given. It does not close that HTTP client, since it belongs to the caller. The HTTP client it creates otherwise gives up connecting after 10 seconds. There is deliberately no timeout for whole requests, since it would end every stream that observes events. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hc2MPSLu7HDHTm8HiBsrDz
The client serialized and deserialized the data of events with a JSON mapper of its own, so the configuration of an application, e.g. its modules or its naming strategy, did not apply to its events. ClientOptions now takes a data mapper, which the client uses to write the data of events and in Event.data(Class). It applies to the data of events only: the client keeps reading everything else the server sends with its own mapper, so that the configuration of an application can not break it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hc2MPSLu7HDHTm8HiBsrDz
…ype. runEventQlQuery returned every row as a JsonNode, so callers converted each row themselves. It now also takes the type to deserialize each row into, e.g. a record that matches the projection, and uses the data mapper of the client for that, since rows carry the data of the application. A row that does not match the type ends the stream with the exception of the mapper. Errors that the server reports stay text, as in the other client SDKs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hc2MPSLu7HDHTm8HiBsrDz
…less CPU per event. To keep the data of an event as the server wrote it, the client parsed each line of a stream four times: the line as a tree, the line again to find its payload, the payload as a tree, and the payload again to find its data. It now reads each line in a single pass, which builds the tree and cuts out the text of the data along the way. The same reader handles the events that writing returns. Measured against the same server, with 20,000 events and the median of 15 rounds, the CPU time of the client per event dropped from about 16.0 to 13.7 microseconds. The OpenCQRS client took about 11.6 to 12.2 in the same runs. The throughput stays the same, since the server bounds it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hc2MPSLu7HDHTm8HiBsrDz
main holds the squashed predecessors of this pull request, whose content this branch already contains, so the merge keeps the content of this branch as it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hc2MPSLu7HDHTm8HiBsrDz
A client had no close(), so the threads of its HTTP client lived on until garbage collection, and there was no way to end what it was doing when an application shut down. Client now implements AutoCloseable. Closing it ends requests that are running and streams that are open, whether they have started or not, with a CancellationException, as if they had been aborted. Every request afterwards throws an IllegalStateException without being sent. The client closes the HTTP client it created, but not one it was given, since that one belongs to the caller. A request registers under the same lock under which close() marks the client as closed, so that no request slips through in between. Claude-Session: https://claude.ai/code/session_01Hc2MPSLu7HDHTm8HiBsrDz Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
To keep the data of an event exactly as the server wrote it, which the verification of its hash needs, the client parsed each line of a stream four times: the line as a tree, the line again to find its payload, the payload as a tree, and the payload again to find its data.
What changes:
Measured against the same local server, with 20,000 events, the median of 15 rounds, and old and new code alternating in the same session:
That closes about two thirds of the gap to the OpenCQRS client. The throughput stays the same, about 41,000 to 43,000 events per second for all of them, since the server bounds it.
Part of the series on the API before the first release. Builds on #10.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Hc2MPSLu7HDHTm8HiBsrDz