Closed SebastianSzturo closed 2 years ago
Here is a full benchmark example of the performance difference with a limit 10000
to make the performance difference clearer without having the client timeout in the connection example:
EDIT: This benchmark is wrong, and bot approaches are equally slow.
DB Connection has the overhead of pooling connections and all that jazz that makes it play nice with postgres, mysql, and mssql. This is something to keep in mind.
In your test you also do not send the same options for {:ok, conn} = Exqlite.Sqlite3.open("./dev.db")
I'll definitely look into this more and maybe throw this into a benchmark that we similarly do for ecto_sqlite3
Thank you for your reply and taking a look at this, I really appreciate it the hard work you are doing on this package! 🙇🏻♂️
Also sorry, I've messed up a bunch of things in the benchmarks. I also forget to actually call step
on the statement in the first example... here is a bit of a more complete benchmark but still reproduces the same issue:
EDIT: This benchmark is wrong, and bot approaches are equally slow.
The Query is as fast as expected in other clients like Ruby (ActiveRecord & standalone) or Python
I suspect something is funky with db connection or with how I implemented it.
@SebastianSzturo multi_step/2
is not the function you want to call. You'll want to instead call fetch_all/2
.
Multi step only goes into the NIF and runs sqlite3_step(statement->statement)
chunk_size
times. fetch_all/2
does exactly what the name says and grabs all the rows of the query. You'll probably find out that it is just as fast as exqlite_connection
.
You are totally right, now that I properly debugged my benchmark code and used fetch_all/2
for the direct approach, both codepaths are equally slow.
Some really amateur-ish IO.inspect
debugging shows that it is indeed fetch_all
and stepping through all results and building a list that takes a long time and not the database:
Connection.handle_execute.start: 2022-04-09 10:46:14.821665
handle_execute_start: 2022-04-09 10:46:14.822756
prepare_start: 2022-04-09 10:46:14.822766
prepare_ok: 2022-04-09 10:46:14.822842
handle_execute_before_execute: 2022-04-09 10:46:14.822864
execute_start: 2022-04-09 10:46:14.822872
bind_params: 2022-04-09 10:46:14.822879
get_columns: 2022-04-09 10:46:14.822890
get_columns_return: 2022-04-09 10:46:14.822906
get_rows: 2022-04-09 10:46:14.822917
fetch_all: 2022-04-09 10:46:36.672343 <--- Takes 22 seconds.
returning_result: 2022-04-09 10:46:36.672480
Result.new: 2022-04-09 10:46:36.675554
Connection.handle_execute.end: 2022-04-09 10:46:36.675590
And turns out you call this issue out in the comments: https://github.com/elixir-sqlite/exqlite/blob/main/lib/exqlite/sqlite3.ex/#L125-L133
It looks like other SQLite libraries like sqlite3-ruby
do all of that in C. I am very surprised that it is so much slower in Elixir!
I am not sure how much of an impact this has on small queries but it seems to become really noticeable with >5000 rows. I am not quite sure how NIFs work in Erlang and what the bigger evil here would be of blocking the NIF for longer 😅
Here is some interesting background on lists in Elixir/Erlang that you probably already know but helped me understand the problem better: https://www.wyeworks.com/blog/2019/03/01/to-use-or-not-to-use-the-++-operator-in-elixir/
I've opened a PR to try to improve the performance by prepending the list and reversing at the end, instead of appending it: https://github.com/elixir-sqlite/exqlite/pull/200 (Up to 73x faster for 1m records!)
Thanks for the quick review and merge. My queries are now blazing fast 🔥
SQLite3 + Livebook are a quite unbeatable team when it comes to analyzing big data sets. Thank you for making that possible!!
I've noticed that large queries are significantly slower via
ecto_sqlite3
than through thesqlite3
CLI when using Ecto orExqlite.Connection
(not withExlite.Sqlite3
)Here is the example data of ~650k rows: dev.db.zip
Via SQLite3 CLI or any other SQLite3 GUI (with wal mode enabled):
With
Exqlite.Connection
: 21.98 sSame with Ecto : 21520.2ms
Any idea what this could be caused by and how to debug it further?