Skip to content

Coalesce socket write when sending data with HTTP/2 - #1722

Open
nickva wants to merge 1 commit into
ninenines:masterfrom
nickva:performance-optimizations
Open

Coalesce socket write when sending data with HTTP/2#1722
nickva wants to merge 1 commit into
ninenines:masterfrom
nickva:performance-optimizations

Conversation

@nickva

@nickva nickva commented Aug 31, 2026

Copy link
Copy Markdown

Previously we wrote each element of the accumulator separately with one send() call for every DATA frame header with the file bytes in between and in ranch ssl sendfile fallback we read the file in 8KB chunks and called ssl:send() on each so for a 100KB file it might call ssl:send() about 30 or so times.

To speed things up gather up to 64KB chunks before calling Transport:send(). Also, avoid re-opening and closing the file repeatedly, instead cache the opened Fds. If the file cannot be opened, read or get truncated unexpectedly we terminate the connection with a GOWAY INTERNAL_ERROR.

If there is TCP sendfile support then we don't do any of this and let sendfile handle it with zero-copy support.

This was inspired from the Erlang forums post [1] comparing a variety of web servers and noticing the the same optimization could apply to cowboy as well.

With h2load on macos (intel), otp27 with 8 connections, 32 streams, 3s runs: got a decent improvemnt in file uploads:

    100KB:  1014 -> 2632 req/s (2.6x), ssl:send 29 -> 5
    1MB:     116 ->  358 req/s (3.1x), ssl:send 259 -> 19
    10KB:   4901 -> 5715 req/s (1.1x), ssl:send 6 -> 4

[1] https://erlangforums.com/t/livery-high-performance-http-1-1-http-2-http-3-server-for-erlang-otp-27/5693/5

Previously we wrote each element of the accumulator separately with one
`send()` call for every DATA frame header with the file bytes in between and in
ranch ssl sendfile fallback we read the file in 8KB chunks and called
`ssl:send()` on each so for a 100KB file it might call `ssl:send()` about 30 or
so times.

To speed things up gather up to 64KB chunks before calling `Transport:send()`.
Also, avoid re-opening and closing the file repeatedly, instead cache the
opened Fds. If the file cannot be opened, read or get truncated unexpectedly we
terminate the connection with a `GOWAY INTERNAL_ERROR`.

If there is TCP sendfile support then we don't do any of this and let sendfile
handle it with zero-copy support.

This was inspired from the Erlang forums post [1] comparing a variety of web
servers and noticing the the same optimization could apply to cowboy as well.

[1]
https://erlangforums.com/t/livery-high-performance-http-1-1-http-2-http-3-server-for-erlang-otp-27/5693/5

With h2load on macos (intel), otp27 with 8 connections, 32 streams, 3s runs:
got a decent improvemnt in file uploads:

```
    100KB:  1014 -> 2632 req/s (2.6x), ssl:send 29 -> 5
    1MB:     116 ->  358 req/s (3.1x), ssl:send 259 -> 19
    10KB:   4901 -> 5715 req/s (1.1x), ssl:send 6 -> 4
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant