I'm porting an application from Postgres which has some non trivial queries that take ~20ms to execute. These queries are run concurrently using Task.async_stream. When using Postgres, this doesn't really affect the runtime of the individual queries so it yields a nice performance benefit. When I used SQLite (via ecto_sqlite3), I noticed that when run concurrently, they would seem to effectively run serially, like each query will just take longer so the total runtime doesn't improve.
The effect is minimal for simple queries, but much more noticeable for the more involved queries in my app. Which is unfortunate because chains of slow queries are the ones I'm more likely to attempt to speed up via concurrency 😅.
I believe this is happening due to the custom allocators used here: https://github.com/elixir-sqlite/exqlite/blob/main/c_src/sqlite3_nif.c#L1428-L1439. When I disable them, throughput starts scaling nicely with the number of workers as expected. (As an aside, after I set SQLITE_DEFAULT_MEMSTATUS=0 which yielded another ~x2 speed up)
My thinking is that these queries are complex enough that they result in a lot of allocations and the custom allocator is becoming a serialization bottleneck.
Would you be open to a PR that provides a flag to disable the custom allocator? If I'm understanding the motivations for its introduction, I'm personally fine trading off visibility into memory usage for the performance benefits.
If you're not quite convinced this could be the problem, I'm happy to work on building a minimal repro tomorrow. Thanks!
I'm porting an application from Postgres which has some non trivial queries that take ~20ms to execute. These queries are run concurrently using
Task.async_stream. When using Postgres, this doesn't really affect the runtime of the individual queries so it yields a nice performance benefit. When I used SQLite (via ecto_sqlite3), I noticed that when run concurrently, they would seem to effectively run serially, like each query will just take longer so the total runtime doesn't improve.The effect is minimal for simple queries, but much more noticeable for the more involved queries in my app. Which is unfortunate because chains of slow queries are the ones I'm more likely to attempt to speed up via concurrency 😅.
I believe this is happening due to the custom allocators used here: https://github.com/elixir-sqlite/exqlite/blob/main/c_src/sqlite3_nif.c#L1428-L1439. When I disable them, throughput starts scaling nicely with the number of workers as expected. (As an aside, after I set
SQLITE_DEFAULT_MEMSTATUS=0which yielded another ~x2 speed up)My thinking is that these queries are complex enough that they result in a lot of allocations and the custom allocator is becoming a serialization bottleneck.
Would you be open to a PR that provides a flag to disable the custom allocator? If I'm understanding the motivations for its introduction, I'm personally fine trading off visibility into memory usage for the performance benefits.
If you're not quite convinced this could be the problem, I'm happy to work on building a minimal repro tomorrow. Thanks!