Skip to content

perf(broadcast): skip a node's own stream events before decoding them - #348

Merged
cosmin-staicu merged 1 commit into
mainfrom
feat/skip-own-events
Nov 3, 2026
Merged

cosmin-staicu merged 1 commit into
mainfrom
feat/skip-own-events

Conversation

@cosmin-staicu

Copy link
Copy Markdown
Member

Summary

Every node reads every Redis Streams broadcast event, its own included, and decoded each one in full before dropping its own. A writer now adds the event's source as a separate src field; readers compare it first and skip their own entries without decoding.

 XADD <stream> MAXLEN ~ N *
   event  <payload>            // unchanged, field name = FieldName (default "event")
+  src    <source uri text>    // the form SameSource compares

 reader, per entry
-  parse reply -> decode payload (System.Text.Json) -> SameSource? drop
+  src present and equal to this node's source -> skip decode, ACK, advance position
+  src absent or different -> decode -> SameSource? as before
  • Mixed versions keep working. Old readers (2.1, 3.0) take the payload with @event[FieldName], a lookup by name that ignores the extra field; a test pins that expression over the new field layout, and a Redis integration test reads a published entry back with the client. Entries from old writers have no src field and are decoded and compared as before.
  • ACKs, the stream position, gap and loss detection and ordering are unchanged; skipping a decode skips no bookkeeping. Both read paths (the Redis 7+ script and the Redis below 7 pipelined read) handle both formats.
  • The topic name used for the read metric is built once instead of per event.
  • The fast path is off while EmitStreamReceivedEvent is on (default off), so that receipt keeps the event id. A topic whose FieldName is src writes no source field and keeps the decode path.

Evidence

Own-event backlog of 1000 entries, in-memory stream, script path: 2835 -> 1072 bytes per own entry, 0 decodes. An old-format own entry still costs 2654 bytes (decoded, as compatibility requires). Each reader now parses two more values per entry, about 100-130 bytes.

New tests: own-entry skip on both read paths (in-memory and real Redis, including a forced below-7 connector), ACK and position advance, old-format own and foreign entries, a case-only source difference, diagnostic-on decode, the field layout, and the allocation probe. net10.0 4327 passed, net8.0 4285 passed.

Merge Danger

Door: two-way for the code; the extra src field is written to shared streams, which older readers ignore.

Blast Radius: broadcast readers and writers on the Redis Streams transport. Own entries no longer log the Debug "event received" line on the fast path.

@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from 2015211 to 50a160e Compare November 2, 2026 09:39
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from 50a160e to a9c1f24 Compare November 2, 2026 11:17
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from a9c1f24 to 6e2ed16 Compare November 2, 2026 11:40
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from 6e2ed16 to 1287c77 Compare November 2, 2026 11:59
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from 1287c77 to f4481bd Compare November 2, 2026 12:37
@cosmin-staicu
cosmin-staicu force-pushed the feat/skip-own-events branch 2 times, most recently from 6a137fd to bcbc5b5 Compare November 2, 2026 14:58
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch 2 times, most recently from c035850 to b68b5a8 Compare November 2, 2026 15:39
@cosmin-staicu
cosmin-staicu force-pushed the feat/skip-own-events branch 2 times, most recently from 6638c9a to 96714f8 Compare November 2, 2026 15:46
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch 2 times, most recently from 921507f to aafb8c5 Compare November 2, 2026 16:02
@cosmin-staicu
cosmin-staicu force-pushed the feat/skip-own-events branch 2 times, most recently from b333496 to 3d3cf27 Compare November 2, 2026 16:29
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch 2 times, most recently from 32df3a0 to 44380fc Compare November 2, 2026 16:53
@cosmin-staicu
cosmin-staicu force-pushed the feat/skip-own-events branch 2 times, most recently from 5ac6cae to 84b75bb Compare November 2, 2026 17:17
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from 44380fc to 615aeac Compare November 2, 2026 17:17
@cosmin-staicu
cosmin-staicu force-pushed the feat/skip-own-events branch 2 times, most recently from 4855562 to 9c972f0 Compare November 3, 2026 07:49
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch 2 times, most recently from d539561 to da6f59b Compare November 3, 2026 09:13
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from da6f59b to 1f431eb Compare November 3, 2026 09:34
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from 1f431eb to 924fc35 Compare November 3, 2026 09:55
@cosmin-staicu
cosmin-staicu force-pushed the feat/skip-own-events branch 2 times, most recently from 292dd31 to 978f2a8 Compare November 3, 2026 10:13
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from 924fc35 to ab1e44b Compare November 3, 2026 10:13
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from ab1e44b to 8b3b24e Compare November 3, 2026 10:56
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from 8b3b24e to 30da9ae Compare November 3, 2026 11:24
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch 2 times, most recently from 46aa3fc to c4180bb Compare November 3, 2026 13:06
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from c4180bb to aace4a3 Compare November 3, 2026 13:33
@cosmin-staicu
cosmin-staicu force-pushed the feat/skip-own-events branch 2 times, most recently from 8f93501 to f003b9b Compare November 3, 2026 13:53
@cosmin-staicu
cosmin-staicu force-pushed the chore/stackexchange-redis-3.4.29 branch from aace4a3 to e84941d Compare November 3, 2026 13:53
Base automatically changed from chore/stackexchange-redis-3.4.29 to main November 3, 2026 15:42
Every node's consumer group reads every entry of its topic's stream, its own
included. An own entry went through the full path, reply parse, System.Text.Json
decode, Uri and dictionary allocations, and was then dropped by SameSource:
about 2.2-2.6 KB per entry and about 4.5 MB per periodic backlog read in the
load test, with the read metric's topic name built per entry on top.

The topic now writes the event's source as a second field, src, after the
payload. The reader compares that field with its own source, as UTF-8 bytes,
before anything is decoded, and an own entry is acknowledged and moves the
group on exactly as before; the gap check, loss reporting and ordering are
untouched. The same shortcut applies to the entries a group made at the head
is checked against. The topic name for the read metric is built once.

Mixed versions share a stream during a rolling deploy:
- A 3.0 reader takes the payload by its field name (entry[FieldName], at
  v2.1.4, v3.0.0 and main) and ignores the extra field; an integration test
  reads a published entry back with the same client call and decodes it.
- A 3.1 reader decodes an entry without the field, a 3.0 writer's, and
  compares the source as before; an entry whose source matches only
  case-insensitively is also decoded, so the shortcut never drops more than
  SameSource did.
- With EmitStreamReceivedEvent on, own entries are still decoded so the
  receipt carries the event id.

Both the Redis 7 script read and the script-free read below 7 pass the
entries through unchanged, so both skip the own entry; tests cover both on
the in-memory stream and on Redis.

An allocation probe over a 1000-entry own backlog on the in-memory stream
goes from 2835 bytes per entry (decoded) to 1072 (parse and acknowledge
only); an old-format own entry still costs 2654.

Signed-off-by: Cosmin Staicu <cosmin.staicu@uipath.com>
@sonarqubecloud

sonarqubecloud Bot commented Nov 3, 2026

Copy link
Copy Markdown

@cosmin-staicu
cosmin-staicu merged commit 87787ef into main Nov 3, 2026
11 checks passed
@cosmin-staicu
cosmin-staicu deleted the feat/skip-own-events branch November 3, 2026 15:57
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.

2 participants