Seb Jachec
06/13/2022, 3:57 PMprint
statements before/after query.execute()
) shortly after app launch. From inspecting the SQL DB, I’d guess this query reads ~2,000 SQL records worth of data.
We’re using Apollo’s in-memory cache (10MB limit), chained with the SQL normalized disk cache, in a KMM app. Inspecting the cache seems easy on Android, from where I can see our disk cache DB file is ~1.1MB, with ~2150 records in total during an average flow through our app (launch app, query for account, query user’s paginated items). Each ‘item’ has ~15 immediate fields, most of which are nested fragments/objects.
We’re creating a flow { .. }
, and using withContext(Dispatchers.Main) { .. }
inside this to execute the Apollo query for items and receive the response (we later perform some other operations within that flow, after emitting the queried cache-only items). Downstream shared view models collect via IO
(Android)/`Default` Dispatcher (iOS), and further downstream, platform view models collect from shared view models on the main dispatcher/thread to present UI.bod
06/13/2022, 4:05 PMApolloCall.toFlow()
method that already returns a Flow - this may be simpler than calling execute()
inside a flow {...}
)Seb Jachec
06/13/2022, 4:14 PMbod
06/13/2022, 4:16 PMSeb Jachec
06/13/2022, 4:37 PMbod
06/14/2022, 10:14 AMSeb Jachec
06/14/2022, 3:23 PMcacheInfo
on Apollo’s actual response still reports 500-600ms (from cacheEndMillis - cacheStartMillis
), so my next guess is the conversion from cached records into actual objects is taking a whilebod
06/14/2022, 3:35 PMSeb Jachec
06/14/2022, 4:20 PMgenerateServiceApolloSources
Gradle task might work, but it looks like that’s doing a bit more as my sync/build breaks entirelybod
06/14/2022, 4:28 PMSeb Jachec
06/14/2022, 4:56 PMwasyl
06/14/2022, 8:06 PMmbonnin
06/17/2022, 9:45 AMresponseBased
codegen, it can generate a non-trivial amount of bytecode. Maybe simply loading these classes is what is taking the ~500ms? (although it should be faster on subsequent calls)Seb Jachec
06/17/2022, 10:31 AMcacheInfo
(cacheEndMillis - cacheStartMillis
) — ~75% of that 500-600ms in our app is being spent just loading records from the in-memory cache, ~15% is taken by the response adapters (mis-measured the time before). We’ve got custom CacheKeyResolver
and CacheKeyGenerator
implementations which I think might be detrimental to the specific query we’re trying to improve. We are indeed using responseBased
codegen too though. Taken a brief break from this, will revisit next week!mbonnin
06/17/2022, 10:40 AMoperationBased
changes anything. We should certainly work on baseline benchmarks on our side too to have a comparison pointSeb Jachec
06/20/2022, 11:23 AMoperationBased
seems to be nearly the same as responseBased
for our use-case (no significant difference).
I’m looking at the just-added @typePolicy(embeddedFields:)
now, as it could be potentially useful to cut down on the sheer number of cache records. With a few types extended, I’m so far running into JsonDataException: Expected BEGIN_OBJECT but was NULL at path…
, although inspecting the normalised SQL DB on-disk it looks like a much tidier representation. Probably user error (and not something I can give helpful debug info for if not), so there’s more to work out yet!bod
06/20/2022, 11:29 AMembeddedFields
was found Friday - the fix is on another branch that is not merged yetSeb Jachec
06/20/2022, 11:30 AMmain
in the next few days or weeks?bod
06/20/2022, 11:39 AMmbonnin
06/20/2022, 12:02 PM./gradlew publishToMavenLocal
and adding mavenLocal()
to your reposbod
06/20/2022, 12:05 PMmbonnin
06/20/2022, 12:08 PMbod
06/20/2022, 12:14 PMSeb Jachec
06/20/2022, 12:19 PMbod
06/20/2022, 1:12 PMmbonnin
06/20/2022, 1:13 PMSeb Jachec
06/21/2022, 1:34 PM@typePolicy(keyFields:,embeddedFields:)
in various configurations and found we basically have a trade-off between embedding fields (reducing cache record count, increasing read speed) and being able to query for individual objects later on. In the most extreme case, embedding our entire paginated query and all its objects/fields such that there’s a single cache record (instead of ~500) gives us a 110ms cache-only query, instead of ~350ms on Android, but obviously that would then lose the benefit of getting a cache hit when querying for a single item, and we might overwrite the entire cache record for any mutations.
The schema we’re querying against looks something like this:
type PageInfo {
# Omitted for brevity
}
type ItemConnection {
edges: [ItemEdge!]!
pageInfo: PageInfo
}
type ItemEdge {
id: ID!
node: Item!
}
type Item {
id: ID!
title: String!
start: DateTimeInfo
end: DateTimeInfo
# ... ~10 other fields, mostly objects, omitted for brevity
}
Would you have any recommendations here as to how we could get the best of both worlds, i.e. storing cache keys for each edge, but also embedding for speedy paginated queries?mbonnin
06/21/2022, 1:36 PMwasyl
06/21/2022, 1:37 PMmbonnin
06/21/2022, 1:39 PMSeb Jachec
06/21/2022, 1:41 PMmbonnin
06/21/2022, 1:48 PMSeb Jachec
06/21/2022, 2:13 PMmbonnin
06/21/2022, 4:19 PMSeb Jachec
06/21/2022, 4:19 PMmbonnin
06/21/2022, 7:10 PMSeb Jachec
06/22/2022, 1:34 PMmbonnin
06/22/2022, 1:56 PMapollo-ios
?Seb Jachec
06/22/2022, 1:58 PM