apollographql / apollo-feature-requests

🧑‍🚀 Apollo Client Feature Requests | (no 🐛 please).
Other
130 stars 7 forks source link

[apollo-cache-inmemory] Disable 'normalization'. #109

Open samkelleher opened 5 years ago

samkelleher commented 5 years ago

apollo-cache-inmemory performs data normalisation before placing data into the cache, it does this by splitting apart the models hieararchy, giving it ids, and storing them as flat documents. The model is then stored using these IDs as reference.

The issue is, the output is very verbose. When doing Server Side Rendering, the cache is extracted (as per Apollos own guidance) and sent in the page as a JSON object.

A simple array of 20 items, where an item might have 3 or 4 levels or children, the resulting JSON payload can easily end up being over 10MB in size.

The splitting apart of models is redundant if those child types are never referenced directly.

It is desired that the data is stored as downloaded without needing to do any normalisation. Ie, the cache would use the query as the key, but instead of reconstructing the normalised results; just return the original data.

Perhaps I'll have to use a different cache method than what this lib provides.

Feature documentation: https://github.com/apollographql/apollo-client/tree/master/packages/apollo-cache-inmemory#normalization

Similiar issue closed by author: https://github.com/apollographql/apollo-client/issues/3822

AlexMost commented 4 years ago

Also, normalization makes a significant CPU load on the server side. Are there any thoughts on this issue?

dylanwulf commented 4 years ago

You can disable normalization on a type-by-type basis by returning null from the dataIdFromObject function

AlexMost commented 4 years ago

@dylanwulf Thanks! Seems like it works.

Here is my case:

  1. Rendering page on a server (fetching data). And here I want to skip normalization to reduce CPU usage.
  2. Passing initialState (client.cache.extract()) to the frontend to make hydration.

Is it ok to use new InMemoryCache({ dataIdFromObject: () => null }) only on a server? Or maybe I have to normalize that state before hydration on a frontend? Are there any helpers to do that?

intellix commented 4 years ago

Also having this issue. We're using a GQL CMS to populate the page and the TransferState object is insanely large. You can scroll down and see pages of useless metadata.

Tried disabling the normalisation as above:

new InMemoryCache({ dataIdFromObject: () => null })

But my HTML response sizes went from 2.5mb to 5.3mb. Hopefully it's fixed in Apollo Client 3 but guessing that's still months away and will require some lengthy migration

Edit: I've shoved a basic cache in my services and serialize that instead of InMemoryCache and got huge savings in my delivered index.html 2.4mb -> 1.4mb.

Basically:

import { makeStateKey } from '@angular/platform-browser';

export const CACHE_STATE_KEY = makeStateKey<any>('cache.state');
export const cache = new Map<string, any>();

with Serialization:

// SSR hydration and dehydration of the cache
if (this.transferState.hasKey(CACHE_STATE_KEY)) {
  const state = this.transferState.get<any>(CACHE_STATE_KEY, {});
  Object.keys(state).forEach(key => cache.set(key, state[key]));
} else {
  this.transferState.onSerialize(CACHE_STATE_KEY, () => Object.fromEntries(cache.entries()));
}

And usage:

return cache.has(cacheKey)
  ? of(cache.get(cacheKey))
  : apollo.query({ ... }).pipe(
      map(({ data }) => data.getSomething),
      tap(v => cache.set(cacheKey, v)),
    );
intellix commented 2 years ago

I've come back to this issue because Apollo InMemory is performing magic and ruining my responses. What the client is giving me is different to what the server is giving, I assume due to it trying to normalise something. I just want to outright disable this magic, I don't want any normalisation at all

Apparently a good solution is to just use apollo-cache-hermes (but haven't tried it yet): https://github.com/convoyinc/apollo-cache-hermes