Production postmortem: Out of memory on a clear sky
A customer opened a support call telling us that they reached the scaling limits of RavenDB. Given that they had a pretty big machine specifically to handle the load they were expecting, they were (rightly) upset about that.
A short back and forth caused us to realize that RavenDB started to fail shortly after they added a new customer to their system. And by fail I mean that it started throwing OutOfMemoryException in certain places. The system was not loaded and there weren’t any other indications of high load. The system had plenty of memory available, but critical functions inside RavenDB would fail because of out of memory errors.
We looked at the actual error and found this log message:
Raven.Client.Exceptions.Database.DatabaseLoadFailureException: Failed to start database orders-21 At /data/global/ravenData/Databases/orders-21 ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown. at System.Threading.Thread.StartInternal(ThreadHandle t, Int32 stackSize, Int32 priority, Char* pThreadName) at System.Threading.Thread.StartCore() at Raven.Server.Utils.PoolOfThreads.LongRunning(Action`1 action, Object state, String name) in C:BuildsRavenDB-5.3-Custom53024srcRaven.ServerUtilsPoolOfThreads.cs:line 91 at Raven.Server.Documents.TransactionOperationsMerger.Start() in C:BuildsRavenDB-5.3-Custom53024srcRaven.ServerDocumentsTransactionOperationsMerger.cs:line 76 at Raven.Server.Documents.DocumentDatabase.Initialize(InitializeOptions options, Nullable`1 wakeup) in C:BuildsRavenDB-5.3-Custom53024srcRaven.ServerDocumentsDocumentDatabase.cs:line 388 at Raven.Server.Documents.DatabasesLandlord.CreateDocumentsStorage(StringSegment databaseName, RavenConfiguration config, Nullable`1 wakeup) in C:BuildsRavenDB-5.3-Custom53024srcRaven.ServerDocumentsDatabasesLandlord.cs:line 826
This is quite an interesting error. To start with, this is us failing to load a database, because we couldn’t spawn the relevant thread to handle transaction merging. That is bad, but why?
It turns out that .NET will only consider a single failure scenario for a thread failing to start. If it fails, it must be because the system is out of memory. However, we are running on Linux, and there are other reasons why that can happen. In particular, there are various limits that you can set on your environment that would limit the number of threads that you can set.
There are global knobs that you should look at first, such as those:
/proc/sys/kernel/threads-max
/proc/sys/kernel/pid_max
/proc/sys/vm/max_map_count
Any of those can serve as a limit. There are also ways to set those limits on a per process manner.
There is also a per user setting, which is controlled via:
/etc/systemd/logind.conf: UserTasksMax
The easiest way to figure out what is going on is to look at the kernel log at that time, here is what we got in the log:
a-orders-srv kernel: cgroup: fork rejected by pids controller in /system.slice/ravendb.service
That made it obvious where the problem was, in the ravendb.service file, we didn’t have TasksMax set, which meant that it was set to 4915 (probably automatically set by the system depending on some heuristic).
When the number of databases and operations on the database reached a particular size, we hit the limit and started failing. That is not a fun place to be in, but at least it is easy to fix.
I created this post specifically so it will be easy to Google that in the future. I also created an issue to get a better error message in this scenario.
Woah, already finished? 🤯
If you found the article interesting, don’t miss a chance to try our database solution – totally for free!