I am using the built-in system to save hundreds of thousands of items and I can share some thoughts on what I am doing and what I am considering:
What I am doing now:
First I use a GUID for every save-game object’s key. My base class has this method so everything inherits from it.
I have 2 savegame objects. One for player made stuff which starts small and will likely grow to a few thousand things by end-game. I have another for procedurally generated content that I want to restore every time the player loads the game. In addition, this content can be removed by the player’s actions, so it is dynamic. This file has around 350,000 objects in it which are just a GUID and a transform.
Saving to the large file would make a noticeable hitch in frame rate so instead I save transactions to the large save game file in the normal savegame file (basically a list of entries I need to remove). Then on next load game I purge them from the large savegame file.
As I mentioned, all of the entries use a GUID as the key. Each savegame class gets its own struct in the savegame file. These structs are organized into Maps (the data type kind). Through polymorphism they each get a chance to save/load their data during the save/load process and they access the Map via their key to read/write.
Downsides
This approach causes data duplication because not only do the actors have their data, it is stored in the savegame as well. In addition, there is a data collection process to save the game before writing it to disk. Writing to disk is fast if the file is reasonable (20-30 meg starts to become noticeable) but not when it is 150-200 meg like my large savegame file. Hence the need for the transaction trick I mentioned.
What I am considering
If I replace the map system with simple arrays (keeping the GUID as a key still, but changing how it is stored in the savegame file, then I can have the load game process give each actor its index into the array. Then the actor can locally store the reference to the array and the index it uses. Through the use of Get->ByReference the actor can then store all of its local data right into the array, eliminating the need for a collection process. When the savegame timer comes due, it simply writes the file. Actors that are removed would just be marked as dead since reordering the array would be dangerous and costly. On the load process dead entries would be purged.
Final thoughts
Everything I made was done in blueprint and is more performant than I expected. The tools Clockwork mentioned would likely be vastly superior in speed because of how they save, however I have tons of object references in my hierarchy and those all have to be manually handled and I didn’t want to go through the overhead. I also had an issue getting RamaSave to work on a vanilla 4.25 project at the time since there is no longer support for that decided I didn’t want to rely on 3rd party stuff for this. YMMV.
Good luck with this. Savegame is a pain in the butt to handle and spending time on it means not making the game. So try to find the solution that has you spending the least amount of time on it you can.