Skip to content

[experimental] ScanTxManager - #260

Open
lavrukov wants to merge 1 commit into
mainfrom
scan_tx_manager
Open

lavrukov wants to merge 1 commit into
mainfrom
scan_tx_manager

Conversation

@lavrukov

@lavrukov lavrukov commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

The idea is to address the ScanBuilderImpl problem while preserving backward compatibility and keeping scan-specific configuration available only after calling scan().

This keeps scan-specific logic out of StdTxManager and StdTxOptions, apart from the scan() entry point. Everything else lives in ScanTxManager and ScanTxOptions, which are now simple wrappers that delegate to the underlying transaction manager and its options.

As a bonus, TxManager now has 15 (!!) fewer methods that implementations must provide, making it much easier to write wrappers.

If we agree on this change, readOnly() will be next.

@lavrukov
lavrukov force-pushed the scan_tx_manager branch 2 times, most recently from b25b098 to ac7c920 Compare November 6, 2026 11:15
@nvamelichev

Copy link
Copy Markdown
Collaborator

❌ Tests failed:

[ERROR] Failures: 
[ERROR]   TxOptionsTest.testTimeoutOptionsCantAcceptNullTimeout Expected exception: java.lang.NullPointerException
[ERROR]   TxOptionsTest.testTimeoutOptionsCantBeChangedToNull Expected exception: java.lang.NullPointerException

@nvamelichev nvamelichev left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This solution looks weirdly elegant... I have some minor suggestions though. And we need to write some tests for ScanTxManager with typical wrapping transaction managers that, primarily, do extra metrics/tracing/instrumentation - to make sure that this logic is still invoked and works properly. Especially important is the case of tracing: we need to write a "scan_query" tag into the trace indicating that this was a scan query.

Currently we do something like that (greatly simplified):

public final class TracingTxManager extends DelegatingTxManager {
    // <...>

    @Override
    protected <T> Supplier<T> wrapTxBody(Supplier<T> supplier) {
        return () -> runWithTracing(supplier);
    }

    private <T> T runWithTracing(Supplier<T> supplier) {
        var tx = Tx.Current.get();
        var opts = tx.getRepositoryTransaction().getOptions();

        try (var span = // ...
            .setTag("tx.isolation_level", opts.isScan() ? "SCAN" : opts.getIsolationLevel().name())
            .setTag("tx.scan", String.valueOf(opts.isScan()))
            .startSpan()
        ) {
            return supplier.get();
        }
    }

TxOptions.isScan() must return the correct thing here (true) when someone calls TracingTxManager.scan().run(...)!

void tx(Runnable runnable);


// @deprecated Use tx() instead.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

To use a Markdown-style doc comment, write ///, not // :-)

* Scan-query limits combined with the general settings of the underlying transaction manager.
*/
@Value
class ScanTxOptions implements TxOptions {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Naming: Maybe TxOptions.DataQuery for ordinary data query options and TxOptions.ScanQuery for scan query options?

private static final Duration DEFAULT_TIMEOUT = Duration.ofMinutes(5);

@With(AccessLevel.PRIVATE)
TxOptions delegate;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can delegate be anything else than StdTxOptions?

@Override
public <PARAMS, RESULT> Stream<RESULT> executeScanQuery(Statement<PARAMS, RESULT> statement, PARAMS params) {
if (!options.isScan()) {
if (!(options instanceof TxOptions.ScanTxOptions scanOptions)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Style: Preconditions.checkState(options instanceof TxOptions.ScanQuery scanOptions, "error msg...") reads quite a bit better than !(options instanceof ...). I'd use Preconditions.checkState() if it also does the pattern-matching magic materializing an already-casted scanOptions variable.

if (newOptions instanceof TxOptions.ScanTxOptions configured) {
scanOptions = configured;
} else {
scanOptions = new TxOptions.ScanTxOptions(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

maybe something like scanOptions = options.withDelegate(newOptions); where withDelegate() is a package-private method unavailable to library users?


@Override
public ScanTxManager scan() {
throw new UnsupportedOperationException("ScanTxManager do not have to call scan twice");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe just return this;? I can imagine some generic code handling TxManager and wanting to ensure that a scan query is performed by calling scan() without checking instanceof (an instanceof check would be incomplete anyway; there are lots of DelegatingTxManager implementations out there, sometimes multi-wrapping each other).

public ScanBuilder scan() {
return new ScanBuilderImpl(TxOptions.ScanOptions.DEFAULT);
public ScanTxManager scan() {
TxOptions.ScanTxOptions scanOptions = options instanceof TxOptions.ScanTxOptions configured

@nvamelichev nvamelichev Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's maybe move this check-and-cast into ScanTxManager's constructor?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

experimental feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants