You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by da...@apache.org on 2026/02/02 23:09:55 UTC
(beam) branch master updated: Add LRU cache eviction to CombinePerKeyPrecombineOperator (#37466)
This is an automated email from the ASF dual-hosted git repository.
damccorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new e4cd040199d Add LRU cache eviction to CombinePerKeyPrecombineOperator (#37466)
e4cd040199d is described below
commit e4cd040199dfb7ac7e1f19c5237e3cd58a9815d9
Author: M Junaid Shaukat <15...@users.noreply.github.com>
AuthorDate: Tue Feb 3 04:09:49 2026 +0500
Add LRU cache eviction to CombinePerKeyPrecombineOperator (#37466)
* [TypeScript] Add LRU cache eviction to CombinePerKeyPrecombineOperator
- Added touchKey() method to move accessed keys to end (most recently used)
- Added flushLRU() method to evict entries from front (least recently used)
- Updated process() to track key usage for proper LRU ordering
JavaScript Maps preserve insertion order, so we implement LRU by:
- On access: delete and re-insert the key (moves to end)
- On eviction: iterate from front (oldest entries first)
Fixes #37465
* Address review comments: remove flush, inline touchKey
---
.../typescript/src/apache_beam/worker/operators.ts | 44 +++++++++++++++-------
1 file changed, 30 insertions(+), 14 deletions(-)
diff --git a/sdks/typescript/src/apache_beam/worker/operators.ts b/sdks/typescript/src/apache_beam/worker/operators.ts
index 941ad62ef2d..9de10afdeeb 100644
--- a/sdks/typescript/src/apache_beam/worker/operators.ts
+++ b/sdks/typescript/src/apache_beam/worker/operators.ts
@@ -427,6 +427,8 @@ export class CombinePerKeyPrecombineOperator<I, A, O>
keyCoder: Coder<unknown>;
windowCoder: Coder<Window>;
+ // Use a Map for LRU ordering: JavaScript Maps preserve insertion order,
+ // so we can implement LRU by deleting and re-inserting on access.
groups: Map<string, A>;
maxKeys: number = 10000;
@@ -474,18 +476,25 @@ export class CombinePerKeyPrecombineOperator<I, A, O>
encodeToBase64(window, this.windowCoder) +
" " +
encodeToBase64(wvalue.value.key, this.keyCoder);
- if (!this.groups.has(wkey)) {
- this.groups.set(wkey, this.combineFn.createAccumulator());
+ const existingAccumulator = this.groups.get(wkey);
+ const newAccumulator =
+ existingAccumulator !== undefined
+ ? this.combineFn.addInput(existingAccumulator, wvalue.value.value)
+ : this.combineFn.addInput(
+ this.combineFn.createAccumulator(),
+ wvalue.value.value,
+ );
+ // Move to end (most recently used) by delete + set
+ // Only delete if the key already exists (avoids unnecessary operation for new keys)
+ if (existingAccumulator !== undefined) {
+ this.groups.delete(wkey);
}
- this.groups.set(
- wkey,
- this.combineFn.addInput(this.groups.get(wkey), wvalue.value.value),
- );
+ this.groups.set(wkey, newAccumulator);
}
if (this.groups.size > this.maxKeys) {
- // Flush a random 10% of the map to make more room.
- // TODO: Tune this, or better use LRU or ARC for this cache.
- return this.flush(this.maxKeys * 0.9);
+ // Flush the least recently used entries (at the front of the Map)
+ // until we're back under maxKeys.
+ return this.flushLRU(this.maxKeys);
} else {
return NonPromise;
}
@@ -495,10 +504,19 @@ export class CombinePerKeyPrecombineOperator<I, A, O>
this.groups = new Map();
}
- flush(target: number): ProcessResult {
+ /**
+ * Flushes entries from the cache using LRU eviction.
+ * Evicts the least recently used entries (from the front of the Map)
+ * until the cache size is at or below the target.
+ */
+ flushLRU(target: number): ProcessResult {
const result = new ProcessResultBuilder();
const toDelete: string[] = [];
+ // Iterate from the front (oldest/least recently used entries)
for (const [wkey, values] of this.groups) {
+ if (this.groups.size - toDelete.length <= target) {
+ break;
+ }
const parts = wkey.split(" ");
const encodedWindow = parts[0];
const encodedKey = parts[1];
@@ -515,9 +533,6 @@ export class CombinePerKeyPrecombineOperator<I, A, O>
}),
);
toDelete.push(wkey);
- if (this.groups.size - toDelete.length <= target) {
- break;
- }
}
for (const wkey of toDelete) {
this.groups.delete(wkey);
@@ -526,7 +541,8 @@ export class CombinePerKeyPrecombineOperator<I, A, O>
}
async finishBundle() {
- const maybePromise = this.flush(0);
+ // Flush all remaining entries using flushLRU(0)
+ const maybePromise = this.flushLRU(0);
if (maybePromise !== NonPromise) {
await maybePromise;
}