Skip to content
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

avoid copy when encoding request body #2349

Merged
merged 2 commits into from
Sep 19, 2022
Merged

Conversation

robx
Copy link
Contributor

@robx robx commented Jun 24, 2022

(Updated after the merge of #2467)

This update hasql and switches to the new lazy json byte encoder, saving one copy of the request body.
After the merge of the libpq change in #2467, this is now essentially what #2333 was.

@robx
Copy link
Contributor Author

robx commented Jun 24, 2022

memory test diff (< main, >, #2333, + this)

< ok 1 - POST /rpc/leak?columns=blob: with a json key of 1M the memory usage(15,605,584 bytes) is less than 16M
> ok 1 - POST /rpc/leak?columns=blob: with a json key of 1M the memory usage(14,662,232 bytes) is less than 16M
+ ok 1 - POST /rpc/leak?columns=blob: with a json key of 1M the memory usage(11,594,920 bytes) is less than 16M
< ok 2 - POST /leak?columns=blob: with a json key of 1M the memory usage(15,538,336 bytes) is less than 16M
> ok 2 - POST /leak?columns=blob: with a json key of 1M the memory usage(14,541,408 bytes) is less than 16M
+ ok 2 - POST /leak?columns=blob: with a json key of 1M the memory usage(11,509,712 bytes) is less than 16M
< ok 3 - PATCH /leak?id=eq.1&columns=blob: with a json key of 1M the memory usage(15,585,384 bytes) is less than 16M
> ok 3 - PATCH /leak?id=eq.1&columns=blob: with a json key of 1M the memory usage(14,601,496 bytes) is less than 16M
+ ok 3 - PATCH /leak?id=eq.1&columns=blob: with a json key of 1M the memory usage(11,561,088 bytes) is less than 16M
< ok 4 - POST /rpc/leak?columns=blob: with a json key of 10M the memory usage(42,927,272 bytes) is less than 44M
> ok 4 - POST /rpc/leak?columns=blob: with a json key of 10M the memory usage(32,960,632 bytes) is less than 44M
+ ok 4 - POST /rpc/leak?columns=blob: with a json key of 10M the memory usage(20,980,360 bytes) is less than 44M
< ok 5 - POST /leak?columns=blob: with a json key of 10M the memory usage(42,847,232 bytes) is less than 44M
> ok 5 - POST /leak?columns=blob: with a json key of 10M the memory usage(32,893,504 bytes) is less than 44M
+ ok 5 - POST /leak?columns=blob: with a json key of 10M the memory usage(20,897,896 bytes) is less than 44M
< ok 6 - PATCH /leak?id=eq.1&columns=blob: with a json key of 10M the memory usage(42,900,040 bytes) is less than 44M
> ok 6 - PATCH /leak?id=eq.1&columns=blob: with a json key of 10M the memory usage(32,947,056 bytes) is less than 44M
+ ok 6 - PATCH /leak?id=eq.1&columns=blob: with a json key of 10M the memory usage(20,933,576 bytes) is less than 44M
< ok 7 - POST /rpc/leak?columns=blob: with a json key of 50M the memory usage(164,339,792 bytes) is less than 172M
> ok 7 - POST /rpc/leak?columns=blob: with a json key of 50M the memory usage(114,496,360 bytes) is less than 172M
+ ok 7 - POST /rpc/leak?columns=blob: with a json key of 50M the memory usage(62,643,264 bytes) is less than 172M
< ok 8 - POST /leak?columns=blob: with a json key of 50M the memory usage(164,227,224 bytes) is less than 172M
> ok 8 - POST /leak?columns=blob: with a json key of 50M the memory usage(114,421,840 bytes) is less than 172M
+ ok 8 - POST /leak?columns=blob: with a json key of 50M the memory usage(62,568,984 bytes) is less than 172M
< ok 9 - PATCH /leak?id=eq.1&columns=blob: with a json key of 50M the memory usage(164,282,832 bytes) is less than 172M
> ok 9 - PATCH /leak?id=eq.1&columns=blob: with a json key of 50M the memory usage(114,479,848 bytes) is less than 172M
+ ok 9 - PATCH /leak?id=eq.1&columns=blob: with a json key of 50M the memory usage(62,614,928 bytes) is less than 172M
< ok 10 - POST /perf_articles?columns=id,body: with a json payload of 32K that has 1000 array values the memory usage(12,605,744 bytes) is less than 14M
> ok 10 - POST /perf_articles?columns=id,body: with a json payload of 32K that has 1000 array values the memory usage(12,574,616 bytes) is less than 14M
+ ok 10 - POST /perf_articles?columns=id,body: with a json payload of 32K that has 1000 array values the memory usage(10,510,256 bytes) is less than 14M
< ok 11 - POST /perf_articles?columns=id,body: with a json payload of 329K that has 10000 array values the memory usage(13,508,120 bytes) is less than 14M
> ok 11 - POST /perf_articles?columns=id,body: with a json payload of 329K that has 10000 array values the memory usage(13,182,160 bytes) is less than 14M
+ ok 11 - POST /perf_articles?columns=id,body: with a json payload of 329K that has 10000 array values the memory usage(10,820,976 bytes) is less than 14M
< ok 12 - POST /perf_articles?columns=id,body: with a json payload of 3.4M that has 100000 array values the memory usage(22,804,512 bytes) is less than 24M
> ok 12 - POST /perf_articles?columns=id,body: with a json payload of 3.4M that has 100000 array values the memory usage(19,428,296 bytes) is less than 24M
+ ok 12 - POST /perf_articles?columns=id,body: with a json payload of 3.4M that has 100000 array values the memory usage(14,019,096 bytes) is less than 24M

@robx
Copy link
Contributor Author

robx commented Jun 24, 2022

Once again, the load test is not affected in any obvious way.

@steve-chavez
Copy link
Member

steve-chavez commented Jun 24, 2022

< ok 7 - POST /rpc/leak?columns=blob: with a json key of 50M the memory usage(164,339,792 bytes) is less than 172M
+ ok 7 - POST /rpc/leak?columns=blob: with a json key of 50M the memory usage(62,643,264 bytes) is less than 172M

Whoa that is awesome, a 61% decrease! It's almost close to the same payload size!

avoids copies for all the other small parameters we pass on (which should affect more than POST requests)

🔥 Will also affect other http methods.

Once again, the load test is not affected in any obvious way.

I think this is because we don't include a bulk insert in the load tests. We only have a single object POST.

GET http://postgrest/
Prefer: tx=commit
HEAD http://postgrest/actors?actor=eq.1
Prefer: tx=commit
GET http://postgrest/actors?select=*,roles(*,films(*))
Prefer: tx=commit
POST http://postgrest/films?columns=title
Prefer: tx=rollback
@post.json
PUT http://postgrest/actors?actor=eq.1&columns=name
Prefer: tx=rollback
@put.json
PATCH http://postgrest/actors?actor=eq.1
Prefer: tx=rollback
@patch.json
DELETE http://postgrest/roles
Prefer: tx=rollback
GET http://postgrest/rpc/call_me?name=John
POST http://postgrest/rpc/call_me
@rpc.json
OPTIONS http://postgrest/actors

{
"title": "Workers Leaving The Lumière Factory In Lyon"
}

If we include a bulk insert, this change should show improved numbers.

@robx
Copy link
Contributor Author

robx commented Jun 29, 2022

Whoa that is awesome, a 61% decrease! It's almost close to the same payload size!

Being close the payload size almost seems a bit too good to be true... By my understanding we should at least be strictly reading the body to a lazy bytestring, and then copying it to a strict bytestring. But that should mean we need at least 2x the payload size, right?

@robx
Copy link
Contributor Author

robx commented Jun 30, 2022

I pushed an updated version with a milder dependency footprint. Memory results seem similar.

In this version:

@steve-chavez
Copy link
Member

switch from text unknown to json binary encoding for the request body (this means we don't have to deal with zero-termination of the CString)
use a jsonBytesLazy encoder variant

Just for my own sanity, the json function doesn't use Aeson at all right?
(Aeson was horrible for memory usage in previous benchmarks)

jsonBytesLazy -> PTI.json
jsonBytesLazy -> bytea_lazy -> lazyBytes.

Nope, it uses LibPQ.Binary. Nice!

@steve-chavez
Copy link
Member

Once again, the load test is not affected in any obvious way.
If we include a bulk insert, this change should show improved numbers.

Just added a bulk insert loadtest on #2358, if you rebase then I think we should see a change in the numbers.

@robx robx force-pushed the optimize-unsafe branch from e510a1c to 8e29a43 Compare July 8, 2022 08:45
@robx robx force-pushed the optimize-unsafe branch 2 times, most recently from fba5b1a to 9f01aa4 Compare July 11, 2022 16:29
@steve-chavez
Copy link
Member

Just added a bulk insert loadtest on #2358, if you rebase then I think we should see a change in the numbers.

Hm, even with the bulk insert addition seems we're not getting better results, not sure if this is true or if our loadtest setup is at fault.

One idea for later could be hosting an instance on AWS(like we do for our aarch64 builds) and use NixOps to deploy the built static binary and then run a more realistic load test there.

NixOps has been getting some improvements(like using s3 instead of sqlite for its state) and seems 2.0 is becoming more usable nowadays(ref).

@wolfgangwalther
Copy link
Member

Just added a bulk insert loadtest on #2358, if you rebase then I think we should see a change in the numbers.

Hm, even with the bulk insert addition seems we're not getting better results, not sure if this is true or if our loadtest setup is at fault.

I don't think the loadtest is "at fault". The loadtest is just not designed for this. The loadtest is designed for a single request at a time, not for concurrent access. The loadtest is not constrained on memory either. And the loadtest data is still not analyzed separately by request.

Improvements in memory usage are just not expected to improve the loadtest's results.

A full-system load test, including many concurrent requests with bigger request bodies at the same time could show some improvements with this change, so running something like that automatically could indeed help a lot.

@robx robx changed the title experiment: avoid copies when passing parameters to libpq experiment: avoid copies when encoding request body Sep 6, 2022
@robx robx changed the title experiment: avoid copies when encoding request body avoid copy when encoding request body Sep 8, 2022
@robx robx marked this pull request as ready for review September 19, 2022 10:13
- switch from "unknown" parameter in text format to a "json" parameter in
  binary format (no dependency update required)
- use a lazy bytestring "json" encoder (via updated hasql)
@robx robx mentioned this pull request Sep 19, 2022
Copy link
Member

@steve-chavez steve-chavez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excited to try this one 🎉

@robx robx merged commit 38ecaa4 into PostgREST:main Sep 19, 2022
@robx robx deleted the optimize-unsafe branch September 19, 2022 19:33
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.

3 participants