Skip to content

perf: reduce EventSourceStream parser allocations#5032

Open
trivikr wants to merge 2 commits intonodejs:mainfrom
trivikr:eventsource-stream-2
Open

perf: reduce EventSourceStream parser allocations#5032
trivikr wants to merge 2 commits intonodejs:mainfrom
trivikr:eventsource-stream-2

Conversation

@trivikr
Copy link
Copy Markdown
Member

@trivikr trivikr commented Apr 15, 2026

This relates to...

Fixes: #2630

Rationale

EventSourceStream was doing avoidable work on long-lived SSE streams by concatenating the buffered input for every chunk, allocating strings for every parsed field name, and replacing the internal event object after each dispatch. That creates unnecessary allocation churn in the hot path, especially when servers deliver heavily fragmented chunks.

Changes

  • Replaced the single-buffer Buffer.concat([buffer, chunk]) approach with chunk-list buffering plus cursors.
  • Kept line parsing lazy so buffers are only concatenated when a logical line spans multiple chunks.
  • Matched data, event, id, and retry field names from bytes instead of decoding field strings first.
  • Validated retry and id directly from bytes before decoding their values.
  • Reset the parser event state in place instead of allocating a fresh event object on every event boundary.
  • Added focused EventSourceStream benchmarks covering realistic payloads, fragmented chunking, and BOM-prefixed input.

Features

N/A

Bug Fixes

Reduced allocation pressure in the EventSourceStream parser hot path for fragmented SSE streams.

Breaking Changes and Deprecations

  • No public API changes.
  • Internally, processEvent(event) now receives a reused parser event object instead of a freshly allocated object each time.

Status

trivikr added 2 commits April 14, 2026 23:14
Assisted-by: OpenAI:gpt-5.4
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
@trivikr
Copy link
Copy Markdown
Member Author

trivikr commented Apr 15, 2026

Benchmarks

Before

$ node benchmarks/eventsource/eventsource-stream.mjs
clk: ~3.18 GHz
cpu: Apple M1 Pro
runtime: node 24.14.0 (arm64-darwin)

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
• EventSourceStream parsing (250 events)
------------------------------------------- -------------------------------
single chunk                 523.13 µs/iter 517.21 µs █                    
                    (493.29 µs … 853.88 µs) 731.96 µs █                    
                    (167.25 kb …   1.18 mb) 659.19 kb █▆▂▁▁▂▃▃▂▂▁▁▁▁▁▁▁▁▁▁▁

256-byte chunks              544.53 µs/iter 538.29 µs █                    
                      (519.42 µs … 1.71 ms) 749.08 µs █▆                   
                    (110.61 kb …   2.11 mb) 711.91 kb ██▆▂▂▂▂▂▂▁▁▂▁▂▁▁▁▁▁▁▁

64-byte chunks               597.89 µs/iter 600.50 µs  █                   
                    (580.54 µs … 844.83 µs) 726.08 µs ▆█                   
                    (148.83 kb …   1.41 mb) 868.50 kb ██▇▇▄▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁

8-byte chunks                  1.14 ms/iter   1.14 ms  █ ▃                 
                        (1.11 ms … 1.30 ms)   1.27 ms  █▇█▅                
                    (659.22 kb …   2.29 mb)   2.29 mb ▆████▆▃▂▂▂▂▁▁▁▁▂▂▃▂▁▂

1-byte chunks                  5.49 ms/iter   5.56 ms ▃█▆▇                 
                        (5.20 ms … 6.90 ms)   6.63 ms █████▃▂              
                    ( 13.86 mb …  13.86 mb)  13.86 mb ███████▆▂█▁▁▃▁▂▁▂▁▁▂▂

• EventSourceStream parsing with BOM
------------------------------------------- -------------------------------
BOM + 8-byte chunks            1.16 ms/iter   1.17 ms  █                   
                        (1.13 ms … 1.39 ms)   1.34 ms  █ ▄                 
                    (  2.29 mb …   2.30 mb)   2.29 mb ▆███▅▅▂▂▂▁▁▁▁▁▁▁▁▁▁▂▁

BOM + 1-byte chunks            5.42 ms/iter   5.49 ms  █▂                  
                        (5.32 ms … 5.74 ms)   5.70 ms  ██                  
                    ( 13.86 mb …  13.87 mb)  13.86 mb ▃██▆▂▆▄▂▂██▂▃▅▁▁▂▁▂▂▂

After

$ node benchmarks/eventsource/eventsource-stream.mjs
clk: ~3.14 GHz
cpu: Apple M1 Pro
runtime: node 24.14.0 (arm64-darwin)

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
• EventSourceStream parsing (250 events)
------------------------------------------- -------------------------------
single chunk                 356.42 µs/iter 333.46 µs █▇                   
                      (320.71 µs … 6.80 ms) 661.83 µs ██                   
                    ( 10.12 kb …   1.12 mb) 210.89 kb ██▃▄▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

256-byte chunks              364.56 µs/iter 363.38 µs   █                  
                    (352.96 µs … 517.63 µs) 443.25 µs ███                  
                    ( 62.13 kb …   1.30 mb) 301.20 kb ███▆▂▂▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁

64-byte chunks               465.56 µs/iter 463.33 µs  █▅                  
                    (445.42 µs … 650.08 µs) 588.88 µs ▅██                  
                    ( 14.05 kb …   1.09 mb) 552.85 kb ███▆▄▂▂▂▂▂▂▁▂▁▁▁▁▁▁▁▁

8-byte chunks                  1.01 ms/iter   1.00 ms    █                 
                      (976.13 µs … 1.22 ms)   1.13 ms  ▆▄█                 
                    (  1.08 mb …   2.11 mb)   1.83 mb ▆████▄▃▂▂▂▁▁▁▁▁▁▂▂▂▂▁

1-byte chunks                  5.10 ms/iter   5.10 ms  █                   
                        (4.84 ms … 6.46 ms)   6.43 ms  █ ▃                 
                    ( 11.93 mb …  11.93 mb)  11.93 mb ▄███▄▃▁▁▂▁▁▁▂▁▁▁▂▁▂▁▁

• EventSourceStream parsing with BOM
------------------------------------------- -------------------------------
BOM + 8-byte chunks            1.02 ms/iter   1.02 ms   █                  
                      (993.00 µs … 1.33 ms)   1.19 ms   █                  
                    (  1.84 mb …   1.88 mb)   1.84 mb ▄▃██▃▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁

BOM + 1-byte chunks            5.13 ms/iter   5.20 ms     ▆█               
                        (4.96 ms … 5.37 ms)   5.36 ms     ██▄ ▄   ▃        
                    ( 11.93 mb …  11.93 mb)  11.93 mb ▅██▅█████▇█▆█▆▇▇▅▅▅▅▂

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.05%. Comparing base (0860142) to head (21710c4).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5032      +/-   ##
==========================================
+ Coverage   93.02%   93.05%   +0.02%     
==========================================
  Files         110      110              
  Lines       35793    35888      +95     
==========================================
+ Hits        33298    33396      +98     
+ Misses       2495     2492       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@metcoder95 metcoder95 requested a review from Uzlopak April 15, 2026 09:24
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.

Reduce Buffer operations in EventSource

2 participants