Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
90d6c96
Propagate 'has_no_state' and 'only_bundle_for_keys' bits from Process…
LEEKYE Feb 23, 2026
4b6d584
Skip StateRequest construction if the bits are true
LEEKYE Feb 23, 2026
708247a
minor fix
LEEKYE Feb 23, 2026
6ef00e8
minor fix
LEEKYE Feb 23, 2026
da95d80
minor fix
LEEKYE Feb 23, 2026
b4836e6
if has_no_state is true, will not load prefetched response
LEEKYE Feb 24, 2026
cfe916c
when has_no_state is true, continuationToken set to null
LEEKYE Feb 24, 2026
1e4fd75
simplify
LEEKYE Feb 24, 2026
d5acb6b
Update sdks/java/harness/src/main/java/org/apache/beam/fn/harness/sta…
LEEKYE Feb 24, 2026
992c3f8
Apply suggestion from @ryan-mbuashundip
LEEKYE Mar 9, 2026
5f03011
fix based on comments
LEEKYE Mar 9, 2026
380d8e2
unit tests
LEEKYE Mar 9, 2026
208e3a2
fix
LEEKYE Mar 9, 2026
657ac08
fix
LEEKYE Mar 9, 2026
26478e4
Merge remote-tracking branch 'origin/stateful_dofn' into stateful_dofn
LEEKYE Mar 9, 2026
1bb60df
hasNoState, onlyBundleForKeys boolean -> Supplier<Boolean>
LEEKYE Mar 18, 2026
bdc6c2d
minor fix
LEEKYE Mar 18, 2026
c1d41ce
define emptyCachingStateIterable method
LEEKYE Mar 19, 2026
07608d3
hasNoState does not affect side input
LEEKYE Mar 19, 2026
5693909
refactor
LEEKYE Mar 19, 2026
e9917d1
fix sideinput test
LEEKYE Mar 19, 2026
e27bdff
spotless check fix
LEEKYE Mar 19, 2026
be0f73e
fixing emptyIterable
LEEKYE Mar 19, 2026
306ccbc
fix unit test
LEEKYE Mar 19, 2026
5da21f2
fix
LEEKYE Mar 19, 2026
a857982
add unit tests
LEEKYE Mar 19, 2026
20f7ab3
pass only boolean hasNoState/onlyBundleForKeys to non-factory class
LEEKYE Mar 20, 2026
213e6ce
fix unit test
LEEKYE Mar 20, 2026
dbb3dce
fix unit tests
LEEKYE Mar 23, 2026
2574cd9
add hasNoState and onlyBundleForKeys to BundleProcess
LEEKYE Mar 25, 2026
4e4ee12
unit test
LEEKYE Mar 25, 2026
8598188
test context
LEEKYE Mar 25, 2026
6e8b2c4
fix
LEEKYE Mar 25, 2026
595177d
Merge branch 'apache:master' into stateful_dofn
LEEKYE Oct 13, 2026
06a1365
Fix backwards compatibility of constructors and remove CachingStateIt…
LEEKYE Oct 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ <T> void addIncomingTimerEndpoint(
* instant provides the timeout on how long the finalization callback is valid for.
*/
DoFn.BundleFinalizer getBundleFinalizer();

/** A supplier containing hasNoState. */
default Supplier<Boolean> getHasNoStateSupplier() {
return () -> false;
}

/** A supplier containing onlyBundleForKeys. */
default Supplier<Boolean> getOnlyBundleForKeysSupplier() {
return () -> false;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,9 @@ private void addRunnerAndConsumersForPTransformRecursively(
BundleFinalizer bundleFinalizer,
Collection<BeamFnDataReadRunner<?>> channelRoots,
Map<ApiServiceDescriptor, BeamFnDataOutboundAggregator> outboundAggregatorMap,
Set<String> runnerCapabilities)
Set<String> runnerCapabilities,
Comment thread
Abacn marked this conversation as resolved.
Supplier<Boolean> hasNoState,
Supplier<Boolean> onlyBundleForKeys)
throws IOException {

// Recursively ensure that all consumers of the output PCollection have been created.
Expand Down Expand Up @@ -277,7 +279,9 @@ private void addRunnerAndConsumersForPTransformRecursively(
bundleFinalizer,
channelRoots,
outboundAggregatorMap,
runnerCapabilities);
runnerCapabilities,
hasNoState,
onlyBundleForKeys);
}
}

Expand Down Expand Up @@ -483,6 +487,16 @@ public BundleSplitListener getSplitListener() {
public BundleFinalizer getBundleFinalizer() {
return bundleFinalizer;
}

@Override
public Supplier<Boolean> getHasNoStateSupplier() {
return hasNoState;
}

@Override
public Supplier<Boolean> getOnlyBundleForKeysSupplier() {
return onlyBundleForKeys;
}
});
processedPTransformIds.add(pTransformId);
}
Expand Down Expand Up @@ -924,7 +938,9 @@ public void afterBundleCommit(Instant callbackExpiry, Callback callback) {
bundleFinalizer,
bundleProcessor.getChannelRoots(),
bundleProcessor.getOutboundAggregators(),
bundleProcessor.getRunnerCapabilities());
bundleProcessor.getRunnerCapabilities(),
bundleProcessor::getHasNoState,
bundleProcessor::getOnlyBundleForKeys);
}
bundleProcessor.finish();

Expand Down Expand Up @@ -1087,6 +1103,8 @@ public static BundleProcessor create(
private String instructionId;
private List<CacheToken> cacheTokens;
private ClearableCache<Object, Object> bundleCache;
private boolean hasNoState = false;
private boolean onlyBundleForKeys = false;

abstract Cache<?, ?> getProcessWideCache();

Expand Down Expand Up @@ -1145,6 +1163,14 @@ synchronized Cache<Object, Object> getBundleCache() {
return this.bundleCache;
}

synchronized boolean getHasNoState() {
return this.hasNoState;
}

synchronized boolean getOnlyBundleForKeys() {
return this.onlyBundleForKeys;
}

private BeamFnDataInboundObserver inboundObserver2;

BeamFnDataInboundObserver getInboundObserver() {
Expand All @@ -1163,13 +1189,17 @@ void finish() {
synchronized void setupForProcessBundleRequest(InstructionRequest request) {
this.instructionId = request.getInstructionId();
this.cacheTokens = request.getProcessBundle().getCacheTokensList();
this.hasNoState = request.getProcessBundle().getHasNoState();
this.onlyBundleForKeys = request.getProcessBundle().getOnlyBundleForKeys();
getMetricsEnvironmentStateForBundle().start(getStateTracker().getMetricsContainer());
}

void reset() throws Exception {
synchronized (this) {
this.instructionId = null;
this.cacheTokens = null;
this.hasNoState = false;
this.onlyBundleForKeys = false;
if (this.bundleCache != null) {
this.bundleCache.clear();
this.bundleCache = null;
Expand All @@ -1191,6 +1221,8 @@ void discard() {
synchronized (this) {
this.instructionId = null;
this.cacheTokens = null;
this.hasNoState = false;
this.onlyBundleForKeys = false;
if (this.bundleCache != null) {
this.bundleCache.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.beam.fn.harness.state;

import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkArgument;
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkNotNull;
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkState;

import java.util.ArrayList;
Expand All @@ -34,6 +35,7 @@
import org.apache.beam.sdk.fn.stream.PrefetchableIterables;
import org.apache.beam.sdk.util.ByteStringOutputStream;
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Iterables;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
* An implementation of a bag user state that utilizes the Beam Fn State API to fetch, clear and
Expand All @@ -50,10 +52,12 @@ public class BagUserState<T> {
private final BeamFnStateClient beamFnStateClient;
private final StateRequest request;
private final Coder<T> valueCoder;
private final CachingStateIterable<T> oldValues;
private final @Nullable CachingStateIterable<T> oldValues;
private List<T> newValues;
private boolean isCleared;
private boolean isClosed;
private final boolean hasNoState;
private final boolean onlyBundleForKeys;

static final int BAG_APPEND_BATCHING_LIMIT = 10 * 1024 * 1024;

Expand All @@ -64,17 +68,33 @@ public BagUserState(
String instructionId,
StateKey stateKey,
Coder<T> valueCoder) {
this(cache, beamFnStateClient, instructionId, stateKey, valueCoder, false, false);
}

/** The cache must be namespaced for this state object accordingly. */
public BagUserState(
Cache<?, ?> cache,
BeamFnStateClient beamFnStateClient,
String instructionId,
StateKey stateKey,
Coder<T> valueCoder,
boolean hasNoState,
boolean onlyBundleForKeys) {
checkArgument(
stateKey.hasBagUserState(), "Expected BagUserState StateKey but received %s.", stateKey);
this.cache = cache;
this.beamFnStateClient = beamFnStateClient;
this.valueCoder = valueCoder;
this.hasNoState = hasNoState;
this.onlyBundleForKeys = onlyBundleForKeys;
this.request =
StateRequest.newBuilder().setInstructionId(instructionId).setStateKey(stateKey).build();

this.oldValues =
StateFetchingIterators.readAllAndDecodeStartingFrom(
this.cache, beamFnStateClient, request, valueCoder);
hasNoState
? null
: StateFetchingIterators.readAllAndDecodeStartingFrom(
this.cache, beamFnStateClient, request, valueCoder);
this.newValues = new ArrayList<>();
}

Expand All @@ -83,15 +103,16 @@ public PrefetchableIterable<T> get() {
!isClosed,
"Bag user state is no longer usable because it is closed for %s",
request.getStateKey());
if (isCleared) {
// If we were cleared we should disregard old values.
if (isCleared || hasNoState) {
// If we were cleared or have no state we should disregard old values.
return PrefetchableIterables.limit(Collections.unmodifiableList(newValues), newValues.size());
} else if (newValues.isEmpty()) {
// If we have no new values then just return the old values.
return oldValues;
return checkNotNull(oldValues);
}
return PrefetchableIterables.concat(
oldValues, Iterables.limit(Collections.unmodifiableList(newValues), newValues.size()));
checkNotNull(oldValues),
Iterables.limit(Collections.unmodifiableList(newValues), newValues.size()));
}

public void append(T t) {
Expand Down Expand Up @@ -120,7 +141,7 @@ public void asyncClose() throws Exception {
"Bag user state is no longer usable because it is closed for %s",
request.getStateKey());
isClosed = true;
if (!isCleared && newValues.isEmpty()) {
if (onlyBundleForKeys || (!isCleared && newValues.isEmpty())) {
return;
}
if (isCleared) {
Expand Down Expand Up @@ -161,12 +182,14 @@ public void asyncClose() throws Exception {
}

// Modify the underlying cached state depending on the mutations performed
if (isCleared) {
// Note this takes ownership of newValues. This object is no longer used after it has been
// closed.
oldValues.clearAndAppend(newValues);
} else {
oldValues.append(newValues);
if (oldValues != null) {
if (isCleared) {
// Note this takes ownership of newValues. This object is no longer used after it has been
// closed.
oldValues.clearAndAppend(newValues);
} else {
oldValues.append(newValues);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ public static class Factory<K> {
private final Map<TupleTag<?>, SideInputSpec> sideInputSpecMap;
private final Coder<K> keyCoder;
private final Coder<BoundedWindow> windowCoder;
private final Supplier<Boolean> hasNoState;
private final Supplier<Boolean> onlyBundleForKeys;

public Factory(
PipelineOptions pipelineOptions,
Expand All @@ -127,6 +129,36 @@ public Factory(
BeamFnStateClient beamFnStateClient,
Coder<K> keyCoder,
Coder<BoundedWindow> windowCoder) {
this(
pipelineOptions,
runnerCapabilities,
ptransformId,
processBundleInstructionId,
cacheTokens,
bundleCache,
processWideCache,
sideInputSpecMap,
beamFnStateClient,
keyCoder,
windowCoder,
() -> false,
() -> false);
}

public Factory(
PipelineOptions pipelineOptions,
Set<String> runnerCapabilities,
String ptransformId,
Supplier<String> processBundleInstructionId,
Supplier<List<CacheToken>> cacheTokens,
Supplier<Cache<?, ?>> bundleCache,
Cache<?, ?> processWideCache,
Map<TupleTag<?>, SideInputSpec> sideInputSpecMap,
BeamFnStateClient beamFnStateClient,
Coder<K> keyCoder,
Coder<BoundedWindow> windowCoder,
Supplier<Boolean> hasNoState,
Supplier<Boolean> onlyBundleForKeys) {
this.pipelineOptions = pipelineOptions;
this.runnerCapabilities = runnerCapabilities;
this.ptransformId = ptransformId;
Expand All @@ -138,6 +170,8 @@ public Factory(
this.beamFnStateClient = beamFnStateClient;
this.keyCoder = keyCoder;
this.windowCoder = windowCoder;
this.hasNoState = hasNoState;
this.onlyBundleForKeys = onlyBundleForKeys;
}

public static <K> Factory<K> factoryForPTransformContext(
Expand Down Expand Up @@ -220,7 +254,9 @@ public static <K> Factory<K> factoryForPTransformContext(
tagToSideInputSpecMap,
context.getBeamFnStateClient(),
keyCoder,
windowCoder);
windowCoder,
context.getHasNoStateSupplier(),
context.getOnlyBundleForKeysSupplier());
}

public FnApiStateAccessor<K> create() {
Expand All @@ -235,7 +271,9 @@ public FnApiStateAccessor<K> create() {
sideInputSpecMap,
beamFnStateClient,
keyCoder,
windowCoder);
windowCoder,
hasNoState,
onlyBundleForKeys);
}
}

Expand All @@ -252,6 +290,8 @@ public FnApiStateAccessor<K> create() {
private final Collection<ThrowingRunnable> stateFinalizers;
private final Coder<K> keyCoder;
private final Coder<BoundedWindow> windowCoder;
private final Supplier<Boolean> hasNoState;
private final Supplier<Boolean> onlyBundleForKeys;

private @Nullable Supplier<BoundedWindow> currentWindowSupplier;
private @Nullable Supplier<ByteString> encodedCurrentKeySupplier;
Expand All @@ -269,6 +309,36 @@ public FnApiStateAccessor(
BeamFnStateClient beamFnStateClient,
Coder<K> keyCoder,
Coder<BoundedWindow> windowCoder) {
this(
pipelineOptions,
runnerCapabilities,
ptransformId,
processBundleInstructionId,
cacheTokens,
bundleCache,
processWideCache,
sideInputSpecMap,
beamFnStateClient,
keyCoder,
windowCoder,
() -> false,
() -> false);
}

public FnApiStateAccessor(
PipelineOptions pipelineOptions,
Set<String> runnerCapabilities,
String ptransformId,
Supplier<String> processBundleInstructionId,
Supplier<List<CacheToken>> cacheTokens,
Supplier<Cache<?, ?>> bundleCache,
Cache<?, ?> processWideCache,
Map<TupleTag<?>, SideInputSpec> sideInputSpecMap,
BeamFnStateClient beamFnStateClient,
Coder<K> keyCoder,
Coder<BoundedWindow> windowCoder,
Supplier<Boolean> hasNoState,
Supplier<Boolean> onlyBundleForKeys) {
this.pipelineOptions = pipelineOptions;
this.runnerCapabilities = runnerCapabilities;
this.stateKeyObjectCache = Maps.newHashMap();
Expand All @@ -282,6 +352,8 @@ public FnApiStateAccessor(
this.keyCoder = keyCoder;
this.windowCoder = windowCoder;
this.stateFinalizers = new ArrayList<>();
this.hasNoState = hasNoState;
this.onlyBundleForKeys = onlyBundleForKeys;
}

public void setKeyAndWindowContext(MutatingStateContext<K, BoundedWindow> keyAndWindowContext) {
Expand Down Expand Up @@ -1201,7 +1273,9 @@ private <T> BagUserState<T> createBagUserState(StateKey stateKey, Coder<T> value
beamFnStateClient,
processBundleInstructionId.get(),
stateKey,
valueCoder);
valueCoder,
hasNoState.get(),
onlyBundleForKeys.get());
stateFinalizers.add(rval::asyncClose);
return rval;
}
Expand Down Expand Up @@ -1283,7 +1357,9 @@ private <KeyT, ValueT> MultimapUserState<KeyT, ValueT> createMultimapUserState(
processBundleInstructionId.get(),
stateKey,
keyCoder,
valueCoder);
valueCoder,
hasNoState.get(),
onlyBundleForKeys.get());
stateFinalizers.add(rval::asyncClose);
return rval;
}
Expand Down Expand Up @@ -1318,7 +1394,9 @@ private <T> OrderedListUserState<T> createOrderedListUserState(
beamFnStateClient,
processBundleInstructionId.get(),
stateKey,
valueCoder);
valueCoder,
hasNoState.get(),
onlyBundleForKeys.get());
stateFinalizers.add(rval::asyncClose);
return rval;
}
Expand Down
Loading
Loading