-
Notifications
You must be signed in to change notification settings - Fork 23
Compression for sync streams #329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 2c3f94c The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me 👍
I also wonder if we want to consider supporting brotli to give Safari something better than gzip, but that may be to small to matter.
zlib.createZstdCompress({ | ||
// We need to flush the frame after every new input chunk, to avoid delaying data | ||
// in the output stream. | ||
flush: zlib.constants.ZSTD_e_flush, | ||
params: { | ||
// Default compression level is 3. We reduce this slightly to limit CPU overhead | ||
[zlib.constants.ZSTD_c_compressionLevel]: 2 | ||
} | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor nit, feel free to ignore: Since this is the only part that is meaningfully different between zstd
and gzip
, can we move it into a separate function? Then maybeCompressResponseStream
could take the stream.Transform
instance for the compressor and the common tracker.setCompressed()
, transform
and encodingHeaders
lines can be shared.
Background on compression options we're investigating in PowerSync: #330
This enables gzip and zstd as transport compression options for sync streams. Since data sync is often performed on metered mobile connections or slow WiFi connections, this could reduce data costs and increase performance for those cases.
This uses the standard
Accept-Encoding
request header, andContent-Encoding
response header. As an example, Chrome would sendaccept-encoding: gzip, deflate, br, zstd
, then we'd response withcontent-encoding: zstd
.zstd
is the best option, and is supported by default in Chrome 123+ and Firefox 126+.gzip
is provided as a fallback option, and is supported in Safari and many native clients.Since browsers send the
accept-encoding
header by default, this will have an immediate effect for the Web SDK. For other SDKs we'd have to test individually, and see whether we need more client-side changes to enable compression.Performance
These are not scientific benchmarks, but just gives an initial idea of expected performance. Since decompression is performed by platform-provided native code in each SDK, this adds minimal overhead and minimal client-side changes (we're not manually decompressing using JavaScript, for example).
Synthetic test dataset: 1.6 million rows of data, 384MiB uncompressed download size, 117MiB data size (the rest is metadata).
zstd compression gives 72.4MiB download size (19% of the original size); gzip gives 76.2MiB (20% of the original size).
In all cases (uncompressed, zstd, gzip), it took around 13s to download the data with curl, and 52s with the diagnostics app. So on a desktop machine, the primary difference is in bandwidth usage, rather than performance.
When downloading the data with curl, the nodejs process averaged around 90% CPU usage for uncompressed data, and 120% when using zstd or gzip. Note that it uses more than 1 core since compression uses a separate thread.
Testing clients
OkHttp supports gzip by default - likely affects both React-Native and Kotlin.
Chrome:
accept-encoding: gzip, deflate, br, zstd
NodeJS SDK:
accept-encoding: gzip, deflate
Dart/Flutter:
accept-encoding: gzip
React-Native on Android:
accept-encoding: gzip
Kotlin on Android:
accept-encoding: gzip
TODO
Proper benchmarksAccording to my initial tests, performance is on par to uncompressed speeds when bandwidth is not a limitation. Will test constrained bandwidth environments later.Testpermessage-deflate compression for websockets #337permessage-deflate
for websockets