2017-05-10 3 views
0

Ich habe ein Problem mit setInterval() verursacht einen Speicherverlust in meiner Node.js-Anwendung. Die App ist einfach: Sie wacht jede halbe Stunde auf, sucht in einer MongoDB-Tabelle nach, ob etwas zu tun ist (meistens nicht), und sendet dann eine E-Mail an die gefundenen Datensätze, die das Kriterium erfüllen. Im Laufe der Zeit (ein paar Tage) geht der Speicher von 100 MB auf über 1 GB.Speicherverlust mit setInterval in einem Node.js-Prozess ausgeführt

Ich habe versucht, die Variablen außerhalb der setInteveral zu bewegen GC'd aber kein Glück. Fehle ich etwas?

Ich verwende New Relic, um die Transaktion zu überwachen, aber dieses Problem beharrte, bevor ich diese Instrumentierung hinzufüge.

const transactionName = 'email-scheduler'; 
let invokeTransaction = newrelic.createBackgroundTransaction(transactionName, 
    function() { 
     sendEmail(function (error) { 
     log.info("Job completed; ending transaction."); 
     newrelic.endTransaction(); 
     }); 
    }); //must be outside of setInterval to be GC'd 
if (RUN_SCHEDULER) { 
    setInterval(invokeTransaction, JOB_INTERVAL_MINUTES * 1000 * 60); 
} 

function sendEmail(callback) { 
    log.info('Scheduler woke up to send emails (set to send every ' + JOB_INTERVAL_MINUTES + ' minutes)'); 
    mongo.findUsersSince(180, function (err, result) { 
    if (err) { 
     log.error("Welcome emails could not be sent: " + err); 
     callback(err); 
    } 
    else if (result && result instanceof Array) { 
     api.sendEmail(resutlt); 
    } else { 
     callback(null); 
    } 
    }); 
} 

Hier ist die alternative Version, wenn ich ein Paket wie Cron statt setInterval() verwenden. Leiden aus dem gleichen Problem:

function sendEmail(callback) { 
    log.info('Scheduler woke up to send emails (set to send every ' + JOB_INTERVAL_MINUTES + ' minutes)'); 

    try { 
    new CronJob('0 */' + JOB_INTERVAL_MINUTES + ' * * * *', function() { 
     log.info('Scheduler woke up to send emails (set to send every ' + JOB_INTERVAL_MINUTES + ' minutes)'); 
     mongo.findUsersSince(OKTA_WAIT_MINUTES, function (err, result) { 
     if (err) { 
      log.error("Welcome emails could not be sent: " + err); 
      callback(err); 
     } 
     else if (result && result instanceof Array) { 
      api.sendEmail(resutlt); 
     } else { 
      callback(null); 
     } 
     }); 
    }, function() { 
     log.info('Scheduler completed job.'); 
    }, RUN_SCHEDULER, "America/Los_Angeles"); 
    } catch (ex) { 
    log.error("cron job pattern not valid"); 
    } 
} 
+0

neugierig, warum Sie das tun: 'Lassen Sie invokeTransaction = invokeTransaction = ...' Das kompiliert nicht für mich. – flash

+0

Typo ... korrigiert. – occasl

+0

Warum sollte 'InvokeTransaction' jemals gecodiert werden?Es ist im globalen Raum, nie überschrieben ... Ich würde annehmen, dass es besser wäre, die gesamte Logik in eine Container-Funktion zu stellen, die nur lokale Variablen verwendet - das würde es wahrscheinlicher machen, dass der GC sie löscht (vorausgesetzt, Lebensvariable hält sich an den verbindlichen Geltungsbereich). – Myst

Antwort

1

Das klingt wie ein XY-Problem. Ich würde sagen, dass die Verwendung eines setInterval, um einen Scheduler in Knoten zu erstellen, nicht wirklich eine gute Idee an erster Stelle ist.

Eher würde ich einen Cronjob verwenden, der viel passender klingt.

So etwas wie node-schedule zum Beispiel würde so aussehen

const schedule = require('node-schedule') 

schedule.scheduleJob('0 * * * *', function() { 
    invokeTransaction() 
}) 

Dies könnte Ihren Speicherverlust Problem lösen. Sie könnten auch in Betracht ziehen, dass die Lecks von Ihrer Methode und nicht von der Implementierung von setInterval oder dem Cron-Scheduler stammen.

+0

Komisch, das ist eigentlich, was ich angefangen habe, aber immer noch das gleiche Problem. Ich werde mehr mit dem zugrunde liegenden MongoDB-Aufruf testen. – occasl

+1

Sie hatten Recht. Es war der zugrunde liegende Code, da ich es versäumt habe, 'db.close()' zum MongoDB-Aufruf hinzuzufügen. – occasl

1

Mit setInterval ist keine gute Software-Architektur-Implementierung. können Sie verwenden Node Cron

var CronJob = require('cron').CronJob; 
 
var job = new CronJob('00 30 11 * * 1-5', function() { 
 
    /* 
 
    * Runs every weekday (Monday through Friday) 
 
    * at 11:30:00 AM. It does not run on Saturday 
 
    * or Sunday. 
 
    */ 
 
    }, function() { 
 
    /* This function is executed when the job stops */ 
 
    }, 
 
    true, /* Start the job right now */ 
 
    timeZone /* Time zone of this job. */ 
 
);

+0

Siehe Kommentar unten. Gleicher Fehler. – occasl

1

Ich glaube, dass die Bewegung in den globalen Rahmen der GC verursacht diese Variablen am Leben zu halten.

Ich würde vorschlagen, dass Sie die genaue entgegengesetzte Richtung betrachten, mit einer Container-Funktion und keine globalen Variablen.

Ich habe keine Ahnung, wie New Relic verhält, ist aber ein schmutziges Beispiel dafür, wie Sie Ihren Code wie mit diesem Design aussehen könnte:

const transactionName = 'email-scheduler'; 
function perform_transaction() { 
    newrelic.createBackgroundTransaction(transactionName, 
     function() { 
     sendEmail(function (error) { 
      log.info("Job completed; ending transaction."); 
      newrelic.endTransaction(); 
     }); 
     }); 
} 

function sendEmail(callback) { 
    log.info('Scheduler woke up to send emails (set to send every ' + JOB_INTERVAL_MINUTES + ' minutes)'); 
    mongo.findUsersSince(180, function (err, result) { 
    if (err) { 
     log.error("Welcome emails could not be sent: " + err); 
     callback(err); 
    } 
    else if (result && result instanceof Array) { 
     api.sendEmail(resutlt); 
    } else { 
     callback(null); 
    } 
    }); 
} 

if (RUN_SCHEDULER) { 
    setInterval(perform_transaction, JOB_INTERVAL_MINUTES * 1000 * 60); 
} 

Ich weiß nicht, ob das hilft, weil ich keine Ahnung habe, Was die zugrunde liegende API tut ... Im Allgemeinen erhöht das Speichern einer Variablen im globalen Speicher jedoch das Risiko, dass dieses Objekt durch Verweise auf veraltete Daten Speicher verbraucht.

EDIT

Die richtige Antwort für diesen speziellen Fall (siehe Kommentare sowohl hier als auch an @ Aperçu Antwort), war von @occasl entdeckt, dass die zugrunde liegende API einen db.close() Aufruf fehlte.

+0

Ich hatte ursprünglich den Code in einem Export-Block aber Punkt gut gemacht. Es war immer noch undicht, bis ich einen 'db.close()' zu meinem MongoDB-Aufruf hinzugefügt habe. – occasl