-
Notifications
You must be signed in to change notification settings - Fork 270
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
fix(sqlite): SqliteEventCacheStore
is 35 times faster
#4739
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4739 +/- ##
==========================================
- Coverage 86.12% 86.11% -0.02%
==========================================
Files 292 292
Lines 34346 34355 +9
==========================================
+ Hits 29581 29585 +4
- Misses 4765 4770 +5 ☔ View full report in Codecov by Sentry. |
6e215ce
to
ed1a906
Compare
LinkedChunk
SqliteEventCacheStore
is 35 times faster
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.
Nice performance improvements, good job! Unfortunately the new schema is incorrect because of the uniqueness constraint, see latest comment.
crates/matrix-sdk-sqlite/migrations/event_cache_store/006_events.sql
Outdated
Show resolved
Hide resolved
ed1a906
to
282c120
Compare
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.
Thanks, looks good, only one minor comment about the benchmark below.
On the other hand, to avoid another later migration, maybe this is a good time to (1) do not store events without an event id in the linked chunk (in memory or in store), (2) add the non null constraint on the event_id field, (3) add the uniqueness constraint back on the tuple (room, event_id). What do you think? (Please ask for another round of review if you do it in this PR)
This patch uses a prepared statement to insert events in the linked chunks. It offers more predictable performance, and SQLite prefers that.
aed17bc
to
1d1b4fc
Compare
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.
This looks good to me. I left a small suggestion.
Please beware that commit 4 modifies the table again without having a migration, also some of the code changes in commit 4 should probably have been part of commit 3, i.e. where we change the insert statement where the event ID becomes a String
instead of an Option<String>
.
What I'm saying, 3 and 4 should probably be squashed.
1d1b4fc
to
71c8516
Compare
Patches 3 and 4 are now squashed together. Thanks for the suggestion. |
This patch is twofold. First off, it provides a new schema allowing to improve the performance of `SqliteEventCacheStore` for 100_000 events from 6.7k events/sec to 284k events/sec on my machine. Second, it now assumes that `EventCacheStore` does NOT store invalid events. It was already the case, but the SQLite schema was not rejecting invalid event in case some were handled. It's now explicitely forbidden.
71c8516
to
16a5401
Compare
This patch adds a benchmark for the
LinkedChunk
with the following matrix:MemoryStore
, with theSqliteEventCacheStore
.Before this patch, the case with no store and
MemoryStore
were not a blocker. I was surprisingly delighted to see that theLinkedChunk
is able to handle a throughput of 9.3 millions events per second. With theMemoryStore
, it drops at 4.5 millions events per second, not bad for a test-tailored store. However, forSqliteEventCacheStore
, the story was clearly different… 7_300 events per second, only. And it got worst with more events.I dug into this for 2 days, and found several things.
In the future we can improve the benchmark to test more operations, but I wanted to solve the performance issue first.
Prepared statements for
Update::PushNewItems
This is addressed as a standalone patch. I've replaced a
query()
in a loop by aprepare()
outside the loop, then anexecute()
inside the loop.Guess what? It barely improved the situation. I've reached 8_400 events per second. Not really the improvement I was expecting. However, the performance was now linear with the number of events. Before that, it was totally unpredictable. That's a first step.
I've tried to use a bulk insertion to avoid to call
execute()
in a loop. A singleprepare()
+ a singleexecute()
like so:Absolutely zero difference. Well. Time not well-spent on this one, but it was worth the try.
Remove all tables: time for a new schema
The tables were fine but none of them contained primary keys. I hoped it could solve things. Sadly,
ALTER TABLE
doesn't allow to change the schema of tables, only a bit of renamings, and column changes.The solution is to remove all indexes and all tables, and to recreate them. Same table names, same column names, but different semantics:
linked_chunks
has a primary key overroom_id
andid
(the chunk identifier), this pair is necessarily unique (this uniqueness constraint was missing before),gaps
has a primary key overroom_id
andchunk_id
, same reason,events
has a primary key composed ofroom_id
,chunk_id
andposition
. Again, this tuple is necessarily unique, and this constraint was missing. The uniqueness onevent_id
has been added in feat(sqlite) Add an index onevents.event_id
and.room_id
#4685.Why
events
cannot useevent_id
as its primary key? Because anevent_id
can be null if the event is invalid. After all, it's allowed in SQLite (because it was a bug in the past, buggy behaviours are kept).Note
Why an invalid event is stored in the database? It's still a mystery to me.
cc @bnjbvr
With this new schema, things have improved a bit. 14_000 events per second, better!
WITHOUT ROWID
Please read Clustered Indexes and the
WITHOUT ROWID
Optimization.In addition to the new schema, the 3 tables are marked as
WITHOUT ROWID
. It has dramatically improved the performance. We are reaching 178_000 events per second!I lied a bit in the previous section. At first, the primary key for
events
wasevent_id
, and it did improved the performance, butWITHOUT ROWID
expects the primary key to NOT be null. So I had to find another unique key, hence the tupleroom_id
,chunk_id
andposition
. And this whole re-designed was withWITHOUT ROWID
in mind, but it's the result of several tries and failures.Restore the index over
events
Finally, in the new schema, I re-create the index for
events.room_id
andevents.event_id
. And we are finally reaching 260_000 events per second!!Results for 100_000 events
(These results are a bit different because, at the time of publishing these patches, I re-run them on my laptop running on a low battery. The orders are the same though.)
No store
MemoryStore
SqliteEventCacheStore
Cons
Migrating from version 5 to 6 will erase all existing caches. I consider it's okay since (i) it's a cache, (ii) it's not released yet.
EventCache
storage #3280