Described in the answer is really not quite true .
GC can free up memory, but the rate of release can be less than the growth rate. Outside of synthetic tests usually is.
The answer is subject to processing taking into account this fact.
Yes, this is due to the peculiarities of GC work in Ruby itself. He keeps his own pool of memory, increasing it if necessary, but never reducing it . And there is a reason for this.
Objects “swept up” by the garbage collector free memory from the point of view of the interpreter, but not from the point of view of the OS. Therefore, just the fact that the memory is occupied by the process does not mean that it really has something valuable for the program , most likely it’s just a “stock” in which the interpreter will place new objects on its own, without pulling the OS allocator.
Accordingly, at each moment of time the process will occupy the maximum of what he needed for the entire time of his life. The size of the pool will be with a small margin equal to the peak memory consumption.
The trouble with large objects is that they require large sequential memory areas. And if such a block is not in the pool, then ... the pool is still increased by such a value that a large object fits!
This problem could be partially defeated, using the compaction garbage collector, when the GC “shifts” smaller objects closer to each other in the process, thereby forming larger consecutive areas of free memory. The same could allow to give large "tails" of unallocated memory back to the OS. But it is great complicates the work of S-shnyh extensions that remember where the object was, directly at the address in memory. You can not just tell them "I moved that object, keep in mind."
You can do a lot of things.
- Half a gigabyte for an entire rail application and data warehouses is ... very little. It makes sense to add, if not physical RAM, then at least swap.
- Tighten the garbage collector to a more careful expansion of the pool and frequent trips. This is unlikely to help, but it may slightly delay the inevitable. This is a rather extensive and dangerous topic that requires thorough stress testing for every change.
- (The richest in adventures) Really reduce peak memory consumption by giving big answers in pieces so that the GC has time to clean up what has already been sent to the client. There is
ActionController::Live , with which you can write the answer to the stream in pieces , without loading all the original data for it into memory.