Reduce memory usage of large maps

We have a service that uses graphQL. This graphQL file is parsed into an AST. It is a huge map (~7 Mb) that contains atoms, small binaries, lists, and maps.

The problem is that we have to analyse the schema every time a request comes to our server. So the AST gets copied to the process heap. For example, if we have 100 concurrent requests, it takes 700 MB of memory. We expect the schema to grow so it will be worse.

My question is, is there a better way to store the AST or any trick what we can use, to optimize the memory usage?

persistent_term — OTP 29.0.5 (erts 17.0.5) ?

Thanks, we might write it back.
We used to use the persistent_term function, but the schema changes from time to time, so it drops the performance for seconds.

If the updates are relatively few and you’re okay with ”leaking” memory for a while, consider placing the schema under a new persistent_term key for each version (index it by an atomic or whatever). That way you can shift the cost of garbage collecting the old schemas until off-peak hours.

Instead of having fixed code interpreting occasionally-changing data, you could consider generating code specialized for a given version of the data.

The way it works is that a request came to the server, we do different kind of traversal, find operations on the AST based on the graphQL request. Usually the same requests have been made to the server, so the result can be cached.

We used to use persistent_term but, when a schema changes we need to erase multiple items which kicks multiple gc on the vm.

We already using the schema versioning because we need to deal the request during the schema change. Now I just need to figure out the off-peak hours. :slight_smile:

What does each process do with this large data structure when handling a request?

We do Graphql schema federation. In simple terms, basically different parts of the schema belong to a different services and we have to figure out how to execute based on the query and the given schema.

Could it be broken up into those sub-schemas and only the needed parts be loaded by each process?

A technique that could be a good fit (though I’ve not seen it used in some time) is to compile the data into a module, taking advantage of the literal pool. Updating a module is fairly expensive, but it doesn’t stop-the-world like persistent term does. The mochiglobal and fastglobal libraries are built around this idea.

Unfortunately, it does global GC like persistent_term does :-/

Oh! I thought it did the code version check more slowly but asynchronously prior to purging any processes. My bad.

It does, but it also needs to GC the literals associated with the module, just like persistent_term.

ah of course! I didn’t think of that. Thank you