Ever since I incorporated APM (elastic-apm-node) in some nodejs microservices they have developed memory leaks.
I have a small repo that shows a couple strange things that might be related...
You will need a MS Sql Server db connection string and an APM server token
In the trace you will notice that even though some spans are sent, not all are. Many times I only get one.
If you set count to 10 or higher you will also see one or more "MaxListenersExceededWarning: Possible EventEmitter memory leak detected." messages.
If you set the count to 4000 you will (hopefully) get:
"MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 infoMessage listeners added."
"MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 errorMessage listeners added."
and then ... "FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory"
Kibana version: 6.7.1
Elasticsearch version: 6.7.1
APM Server version: 6.6.1
APM Agent language and version: elastic-apm-node 2.10.0
Steps to reproduce:
First Case: not all spans sent and memory leak warning
Thanks for the report and sorry you're having issues.
I've cloned your repo and tried running it locally--I do get the emitter count warning, but no fatal error, even bumping it up all the way to 1,000,000. It's likely just caused by the closure leak caused by the event handlers though. It's due to a mismatch between adding an event handler and trying to remove it due to the function wrap in https://github.com/elastic/apm-agent-nodejs/blob/master/lib/instrumentation/index.js#L284-L299
Maybe getting the FATAL error somehow involves the nature/structure of data returned from the query. I was returning between 1 and 100 rows from a table with more than 50 columns in my testing.
Are you suggesting I do something to the application logic?
It's possible, but no need to do anything right now. I've got an idea for a fix for the emitter leak in mind. I'll try to get that out hopefully later today. After that, give it a try and see if that fixes the fatal error too--I suspect it does, but we'll see.
It's a context loss issue. We use async_hooks to maintain context through an async execution tree, but it appears to not support thenables, which is an object that looks like a promise by having a "then" method, but is not actually a promise. Tedious seems to use thenables internally. After awaiting a thenable async_hooks loses track of what the async ids are. A way around it is to wrap it in a real promise before awaiting, like this:
await new Promise((...args) => {
promiseLike.then(...args)
})
I use knex in the application, not tedious. tedious is used by mssql, which is the client specified in the config sent to knex to get the query-builder.
Oh, sorry--I meant knex, not tedious. I'm suggesting you wrap your queries that you want to await in a real promise so async_hooks can propagate the context properly.
Apache, Apache Lucene, Apache Hadoop, Hadoop, HDFS and the yellow elephant
logo are trademarks of the
Apache Software Foundation
in the United States and/or other countries.