The first and easiest step is to break the data into pieces. The size of the piece depends on your needs: it can be the smallest or largest fragment that can be drawn at a time, or for which geometry can be built, or the optimal size for compression.
As soon as you work with managed chunks, a memory problem will be prevented. A stream of pieces (loading and unloading / saving) as needed.
During the load / save process, you may need compression and / or a database. Even something simple, such as RLE and SQLite (a separate table with coordinates and a data block), can save a little space. Better compression allows you to work with larger block sizes.
Depending on the usage, it may be possible to store the compressed fragments in memory and only unpack the modification for a short time (or when they can be changed). If your data is read-only, downloading it and decompressing it only if necessary will be very useful.
Dividing data into pieces also has side benefits, such as an extremely simple form for axes, which allows you to generate geometry (marching cubes, etc.) for working on isolated data fragments (simplifies flows) and makes saving / loading process much easier.
source share