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

Cache lock changes #5608

Merged
merged 4 commits into from
Feb 9, 2024
Merged

Cache lock changes #5608

merged 4 commits into from
Feb 9, 2024

Conversation

BoD
Copy link
Contributor

@BoD BoD commented Feb 7, 2024

The idea: instead of having a lock in ApolloStore, let caches handle their own locking, in the hope that this would reduce overall contention (see #4605).

Things to note:

  • Each cache still uses a lock to at least ensure consistency between the different caches in the chain
  • The access to SQLite appears to be guarded by a lock on its own, so no locking is needed in general. A lock is still used only in case there is a nextCache
  • In the MemoryCache, the lock guards writes to the LRUCache and access to the next cache
  • In incubating only

Added micro-benchmarks that start a few threads performing read and writes through ApolloStore in parallel (memory / sql / memory then sql).

It looks like the bench performance is improved when comparing the results before/after the change (avg of 6 runs on a Samsung S10+):

Memory Sql Memory then sql
Before 50,565,308 801,852,416 1,182,789,288
After 45,090,843 708,763,083 900,559,012
Improvement 10.8% 11.6& 23.9%

A few flame charts

Memory, before:
incubating-before-10-100-memory

Memory, after:
incubating-after-10-100-memory

Sql, before:
incubating-before-10-100-sql

Sql, after:
incubating-after-10-100-sql

@BoD BoD requested a review from martinbonnin as a code owner February 7, 2024 09:41
Copy link

netlify bot commented Feb 7, 2024

Deploy Preview for apollo-android-docs canceled.

Name Link
🔨 Latest commit c8cee0f
🔍 Latest deploy log https://app.netlify.com/sites/apollo-android-docs/deploys/65c605d2121b3f0008622d71

Comment on lines +59 to +63
repeat(WORK_LOAD) {
apolloStore.writeOperation(query, data)
val data2 = apolloStore.readOperation(query)
Assert.assertEquals(data, data2)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you also add an integration test, that tests end to end using apolloClient.query() and the default dispatcher? (should be Dispatchers.Default IIRC). Are the results similar?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean still in a microbenchmark or something else?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think so? Same thing but closer to the real scenario of executing a query. Probably using MockWebServer or so. I know it adds more variance but it's also what users are actually doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added in c8cee0f benches that execute queries in parallel. The results are a bit questionable:

Memory Sql Memory then sql
Before 44,201,994 1,047,540,153 41,335,067
After 40,916,743 1,079,325,820 38,458,753
Improvement 7.4% -3% 6.9%

Comment on lines +30 to +33
@Test
fun concurrentReadWritesMemory() {
concurrentReadWrites(MemoryCacheFactory())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Are those uploaded to datadog automagically using the run-benchmarks script ? I would say so but I can't remember.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think so from what I read in the script 😅.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want that? I guess yes? Additional question is: do we want to track it in the dashboard? I don't think it's going to appear in the dashboard without manual datadog configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd say yes (and also yes for DataDog) Doesn't hurt to track it :) I'll have a look at the DD conf.

if (record != null) {
if (cacheHeaders.hasHeader(EVICT_AFTER_READ)) {
recordDatabase.delete(key)
// A lock is only needed if there is a nextCache
Copy link
Contributor

@martinbonnin martinbonnin Feb 7, 2024

Choose a reason for hiding this comment

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

Are we 100% certain of that? SQLDelight queries being thread safe no matter what underlying driver is used? A comment to the relevant doc/reference would be handy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not 100% certain alas. At least on native (MacOS) I made a small test that starts 2 threads, 1st starts a transaction, 2nd one also, I could see that the 2nd one was blocked until the 1st one finished. I am not sure if this is due to a lock somewhere in the driver, or if SQLite itself handles this. What I think I understand from here is that SQLite handles this itself, but that's not 100% clear to me tbh.

Copy link
Contributor

@martinbonnin martinbonnin left a comment

Choose a reason for hiding this comment

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

looks good! I like that it's not changing any API so it's a net gain.
I'm curious what the integration tests results are going to be.

@BoD BoD merged commit 42f3208 into main Feb 9, 2024
9 checks passed
@BoD BoD deleted the cache-lock-changes branch February 9, 2024 11:09
martinbonnin pushed a commit that referenced this pull request Feb 27, 2024
* Add ApolloStore concurrency micro benchmarks

* Remove lock from ApolloStore - now individual NormalizedCaches must be thread-safe

* Make the call to the next cache be transactional

* Add some 'integration test' benchmarks that execute queries
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants