Cache Write Events on Android

Hi All, :wave:
I’m working on a project to capture Client Side Cache Usage data.
One of my requirements is to log data associated with Cache Write events on Android Clients.
The expectation is to log cache_size along with some other metadata (request_id and document_id) whenever a Cache Write event occurs.

Unfortunately, I cannot find a simple way to implement this bit.
My initial thought process was to have the MemoryCache extended and then log the Cache Write event in the merge().
But obviously I cannot do that because MemoryCache is not declared as open.

My only other alternative is to log the Cache Write events by coming up with my own implementation of the ApolloStore.
But before I go down this path, just thought to check with the community for their suggestions.
Thanks!

In the current state of things, I’d say the easiest way to do this would be to wrap the normalized cache with your own LoggingCache:

class LoggingCache(val delegate: NormalizedCache): NormalizedCache() {
  override fun merge(record: Record, cacheHeaders: CacheHeaders): Set<String> {
    // Do your logging here
    return delegate.merge(record, cacheHeaders)
  }

  override fun merge(records: Collection<Record>, cacheHeaders: CacheHeaders): Set<String> {
    // Do your logging here
    return delegate.merge(records, cacheHeaders)
  }

  // more methods...
}

That’s a few methods to implement but it’s mostly delegating calls so it should be doable.

For the request_id and document_id, you can pass them with cacheHeaders:

        apolloClient.query(query).cacheHeaders(
            CacheHeaders.Builder()
                .addHeader("request_id", request_id)
                .addHeader("document_id", document_id)
                .build()
        ).execute()

For the cache_size, it’s hard to get the 100% accurate size but you can get a pretty good estimation by copy/pasting the RecordWeighter class: apollo-kotlin/RecordWeigher.kt at 3798a36f06dfc624674b3acf5bb3f22358130fe5 · apollographql/apollo-kotlin · GitHub

Hope this helps!

1 Like

Thanks @mbonnin!
This is exactly what I’ve in mind.
Would there be a way to update the CacheHeaders after getting a response from the Server??
I don’t think there is but hoping to get lucky!

I did checkout the MutableExecutionOptions<T>.cacheHeaders(cacheHeaders: CacheHeaders) method. But this helps in creating a new request but doesn’t change the CacheHeaders associated with ApolloRequest

Would there be a way to update the CacheHeaders after getting a response from the Server?

Can you elaborate a little bit? If you want to have CacheHeaders that depend on the response, you could certainly use ApolloInterceptors and add one between the ApolloCacheInterceptor and the NetworkInterceptor. But it’s hard to tell without more context.

But this helps in creating a new request but doesn’t change the CacheHeaders associated with ApolloRequest

Can’t you create a new request though? Apollo Kotlin is mostly using immutable data structures so it’s very hard to mutate something. But you can always create a new ApolloRequest and use it down the interceptor chain or in other places.

So the request_id that we’re supposed to log on Cache Write(s) is actually fetched from the server instead of being set by the client.
So I was hoping if I could mutate the CacheHeaders with the request_id fetched from server by the intercepting response between the ApolloCacheInterceptor and the NetworkInterceptor.

Alright, I see what you mean now… Because ApolloInterceptor uses request.cacheHeaders, it can’t be changed by any downstream interceptors.

We could change the behaviour there to use response.cacheHeaders but that’d certainly mean copying request.executionContext in response.executionContext which is a bigger change and I’m unsure what kind of side effects it could have. Feel free to open a Github issue and we can gather some community feedback before committing to this route.

In the short term, the easiest is certainly to copy/paste ApolloCacheInterceptor so you can add your logic there. I know it never feels good to copy/paste but in this case, it’s ~200 lines, maybe even less if you don’t care about the asynchronous cache writing and optimistic updates. And it’s not requiring any deep change to the API/behaviour of the execution pipeline.

1 Like

Issue created!

Appreciate the quick responses @mbonnin

1 Like