Node.js optimieren

BTCStorage

Ensign
Registriert
Mai 2020
Beiträge
137
Hallo, ich baue zur Zeit mit Node.js einige Sachen und stelle zur Zeit etwas fest wo ich denke das man es optimieren koennte, mir fehlt aber noch die Erfahrung, ich will das gerne noch lernen zu optimieren.

Ich stehe hier vor folgenden problem.

In einer Funktion welche mit setTimeout alle 3 Sekunden aufgerufen wird sind 3 weitere Funktionen welche von Crypto Exchanges API Daten lesen. Die Intervalfunktion welche also jedesmal nach Ablauf von Zeit erneut aufgerufen wird sollte eiegntlich alle 3 Sekunden eine neue console.log ausdrucken und somit bestaetigen das alles laeuft ohne irgendwo Blockaden zu haben.

Ich sehe aber das es oft vorkommt das die Zeitabstaende zwischen den console logs einige Sekunden sind auch mal 10 oder 30 sekunden. Mir ist zwar bewust das es an den Funktionen liegt welche innerhalb der Intervalfunktion aufgerufen werden jedoch fehlt mir noch die Erfahrung dies zu optimeiren.

Was machen nun die Funktionen welche in der Intervalfunktion jedesmal erneut aufgerufen werde. Nun diese Funktionen arbeiten auf eine recrusive Art, sie elsen zum beispiel von einem Exchange Konto alle abgeschlossenen Trades da die APi aber nur 100 Trades zurueck gibt muste ich die Funktion recrusive bauen, das heist sie wird jedesmal weitere 100 tardes abfragen bis die komplette Tradehistorie gelsen wurde.

Ich koennte jetzt natuerlich dieses recrusive lesen raus bauen und bei Ablauf von jeweils 3 Sekunden dann die naechsten 100 Trades lesen, aber ich frage mich ob es auch moeglich ist das ganze so zu optimieren das Node.js irgendwie im Hintergrund das recrusive lesen weiter fuehrt ohne den Ablauf der Intervalfunktion zu blockieren. Also ich poste jetzt noch Beispielcode damit man es bisjen einfacher versteht.

Javascript:
function IntervalFunction1()
{
  try
  {
  console.log("=================================================================================================================================");
  console.log("============================================ IntervalFunction1 "+new Date(Date.now()).toLocaleString()+" ===========================================================================");
  console.log("=================================================================================================================================");

  let a=0;
  OpenOrdersAndBalance(Kontos[a].account, Kontos[a].key, Kontos[a].secret);
  GetClosedTradesRecursive(Kontos[a].account, Kontos[a].key, Kontos[a].secret);
  GetOrders(Kontos[a].account, Kontos[a].key, Kontos[a].secret);
 
  setTimeout(IntervalFunction1, 3000);
 }
 catch (error)
 {
    console.error('IntervalFunction1, Ein Fehler ist aufgetreten:', error);
    setTimeout(IntervalFunction1, 3000);
 }
}//IntervalFunction1
IntervalFunction1();

Und hier ist noch ein beispiel einer Funktion welche recrusive Daten lest:

Javascript:
async function GetOrders(account, key, secret, nextPageCursor = '')
{ 
  const Client = new FuturesClient({
     apiKey: key,
     apiSecret: secret,
  });

  try {
      const getOrders = await Client.getProductTypeOrderHistory("usdt", 0, Date.now(), '100', nextPageCursor);
      for(let a=0; a<Object.size(getOrders.data.orderList); a++)
      {
         //console.log("getOrders result: ", getOrders.data.orderList[a]);
      }
      //wenn es mehr daten gibt wird hier ein nextpagecursor gelesen und die funktion wird recrusive erneut aufgerufen
      let nextPage = ""; if(getOrders.data.endId !== undefined)nextPage=getOrders.data.endId; if(getOrders.data.nextFlag == false)nextPage="";

      if(nextPage !== "")
      {
          setTimeout(async () => {
            await GetOrders(account, key, secret, nextPage);
          }, 900);
      }
      else
      {
         console.log("========= get orders gespeichert ==================");
      }
  }
  catch (e) {
    console.error('request failed: ', e);
  }
}

Wenn ich also die Funktion aender und nicht recrusive weitere trades von der api lese, dann klappt es mit den Einhalten von 3 Sekunden, dann bekomme ich alle 3 Sekunden von der Intervalfunktion eine console log ausgedruckt, aber ich frage mich bo man das ganze irgendwie so aufbauen kann damit auch recrusive lesen weiter funktioniert und aber auch das die Intervalfunktion weiter alle 3 Sekunden eine neue console log ausdruckt, also in anderen Worten damit nichts blockiert wird im Ablauf vom ganzen Program.

Das wuerde ich gerne lernen da fehlen mir noch genug Erfahrungen mit Node.js, habt ihr gute Tipps?
 
Es dürfte prinzipiell eine schlechte Idee sein, stumpf jedes Intervall auszuführen, wenn der Code darin potentiell länger läuft als das Intervall lang ist. Da würde ich am Anfang der IntervalFunction1 einfach irgendwo ein Flag setzen, dass gerade API-Abfragen laufen und am Anfang der Funktion prüfen, ob das Flag gesetzt ist. Ist es gesetzt, wird in diesem Intervall einfach keine neue API-Abfrage gemacht.
Ansonsten müllst du den Server ja mit Abfragen voll, weil du alle 3 Sekunden neue Abfragen machst, obwohl die vorherigen noch gar nicht abgeschlossen sind.

Dass der Code mitunter 20-30 Sekunden braucht ist ganz normal, wenn das Ergebnis der API-Abfrage mehrere Seiten hat (sogenanntes Pagination). Du wartest ja mit jedem rekusriven Aufruf für die nächste Ergebnisseite 900ms. Bei 20-30 Seiten für eine vollständige Abfrage kommen allein dadurch schon 18-27s zusammen plus die Antwortzeiten des Servers.

Auch braucht man die "Pagination" bei Abfragen nicht rekursiv machen, sondern das geht als einfache Schleife, die beendet wird, wenn man die letzte Seite der Ergebnisse der API-Abfrage erreicht hat.
 
Dieser Teil hier
Javascript:
setTimeout(async () => {             await GetOrders(account, key, secret, nextPage);           }, 900);
ist async und wird nicht awaited nur weil das in der function steht die an setTimeout() übergeben wird. Hier bräuchtest du allerdings ein await new Promise((resolve, reject) =>{...}); Konstrukt mit dem setTimeout um das zu synchronisieren.
Das gilt wie @mibbio bemerkt hat auch für den äußeren handler.
 
Hier mal ein Ansatz, bei dem in den Intervallen nur neue API-Abfragen gemacht werden, wenn vorherige beendet sind und die Pagination als einfache Schleife.

Code:
Abfrage läuft = false

Intervall alle x Sekunden
    wenn Abfrage läuft, verlasse den Code des Intervalls
 
    setze Abfrage läuft auf true
    setze nextPageCursor auf einen leeren Wert
 
    wiederhole
        API-Abfrage mit aktuellem nextPageCursor
        mache etwas mit dem Ergebnis
        setzte neuen nextPageCursor aus dem Abfrageergebnis
    bis es kein neuer nextPageCursor gibt, also Ende der Ergebnisse erreicht ist
 
    warte x Millisekunden, damit man nicht in ein Rate Limit der API läuft
 
    setze Abfrage läuft auf false
Ende vom Intervall-Code

So mache ich das meistens in meinem Code, wenn ich in regelmäßigen Abständen APIs mit mehrseitigen Ergebnissen abfragen will.

Wobei ich dabei dann die Wartezeit nach jedem Durchlauf anhand der Dauer der einzelnen Abfrage dynamisch berechne und nicht beispielweise fest 900 ms warte, wie in deinem Code.
Javascript:
do {
    const startTime = process.hrtime.bigint();

    hier Code für die API-Abfrage und setzen des Wertes für die nächste "Seite"
  
    // Berechne Wartezeit bis zum nächsten Durchlauf und warte entsprechen lange
    const requestDuration = process.hrtime.bigint() - startTime;
    if (requestDuration < minimumTimePerRequest) {
        const difference = Number(minimumTimePerRequest - requestDuration);
        await new Promise(resolve => setTimeout(() => resolve(null), difference));
    } 
} while (weitere Seite verfügbar)
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: TomH22
Danke fuer die Tipps, mir ist gar nicht aufgefallen das ich unnoetig die Funktionen wiederholt aufrufe anstatt darauf zu warten bis eine abgeschlossen hat usw, die guten alten Forums da lernt man doch am besten dieser ChatGPT bot meint immer alles zu wissen aber den seine Tipps haben mich nicht weiter gebracht.
 
  • Gefällt mir
Reaktionen: floq0r
Ganz übersehen, dass du die IntervalFunction ja mit einem setTimeout am Ende der Funktion selbst aufrufst und das nicht über ein setInterval von außerhalb der Funktion machst.

Daraus ergibt sich noch ein ganz anderes Problem. Mit der Variante hast du praktisch eine endlose Rekursion, wodurch das Programm nach einer gewissen Zeit mit einem Stackoverflow abstürzen wird.

Auch dass du im catch direkt wieder die IntervalFunction1 aufrufst ist nicht so wirklich elegant. Du rufst im Fehlerfall einfach stumpf die Funktion neu auf, die gerade eine Exception verursacht hat. Bekommst also potentiell direkt wieder die Exception und wieder und wieder - also ebenfalls endlose Rekursion, wenn die Fehlerursache weiter bestehen bleibt.

Einen Retry im Fehlerfall würde ich eher außen rum programmieren mit einer entsprechend gescheiten Fehlerbehandlung.
 
Zuletzt bearbeitet:
Ich hatte anfangs eine "setInterval" benutzt aber ChatGPT sagte mit "setTimeout" waere besser weil "setInterval" die Funktionen erneut aufruft ohne zu warten bis die Aufgaben abgearbeitet wurden. Vielleicht hat er dort recht oder?

Dann habe ich in der "catch" Rueckgabe das "setTimeout" nochmal eingebaut weil ich hatte anfangs wegen Fehlermeldungen gesehen das alles stoppte, es sollte aber die ganze Zeit weiter laufen, also das waren anfangs nur so Fehler wie wenn eine Variable "undefined" ist, also in diesem Fall wegen Fehlermeldungen waere auch vielleicht die "setInterval" geeigneter weil die glaube ich unabhaengig von Fehlermeldungen wieder ein neuen Aufruft macht und die "setTimeout" muss man erst nochmal aufrufen damit es eine Wiederholung gibt.

Nun ja ich will kucken das ich mich heute Abend nochmal etwas konzentrieren kann und die heute neu gelernten Sachen austeste. Diese Frage ob "setInterval" oder "setTimeout" scheint in Javascript auch irgendwie manchmal nicht deutlich beantwortbar, zumindest nicht so einfach oder deutlich fuer jeden. Aber die anderen Sachen die ihr bemerkt habt da sehe ich ein da kann man noch ordentlich was optimieren.
 
Naja, bei setInterval(..., 3000) wird halt stumpf alle 3000ms die angegebene Funktion aufgerufen, egal ob der vorherige Aufruf schon fertig ist oder nicht. Da hättest du dann die Gefahr, schneller neue API-Anfragen zu erstellen als das Programm die abarbeitet.

Ich würde einfach generell die beiden Rekursionen, also einmal das rekursive Aufrufen von IntervalFunction1 per setTimeout für ein endlos laufendes Programm und dann das rekursive Aufrufen von GetOrders für Pagination ändern. Das geht beides mit simplen Schleifen und passenden Abbruchbedingungen, ganz ohne Rekursion. Besonders das beide Rekursionen zusammen produzieren dir früher oder später garantiert einen Stacküberlauf und damit einen Programmabsturz.
 
Das hoert sich nuetzlich an, aber ich hatte anfangs das Problem in Javascript gibt es keine sleep Funktion welche ich einsetzen kannn fuer die while Schleife. Dann bekam ich diese Hinweise setInterval und setTimeout zu nutzen. ich will bei meinem derzeitigen Program keine Zeit und Muehen sparen ich will das es so gut und stabil wie moeglich laeuft. Ich hatte auch von ChatGPT so Hinweise bekommen man koennte die Arbeit etwas aufteilen mit Workern und auf CPU Kerne, das waeren bestimmt auch noch Sachen die ich mir anschauen will wenn es den wirklich nuetzlich ist. Zur Zeit habe ich ganz einfach eine index.js in dieser befinden sich einige Router welche meiste Zeit von der Weboberflaeche genutzt werden und dann habe ich noch zwei bis drei Interval Funktionen bzw setTimeout Funktionen welche meiste Zeit mit den Apis der Exchanges arbeiten, diese dachte ich mir koennte man dann vielleicht ueber solche CPU Kerne verteilen oder Worker, ich habe aber bisher sowas auch noch nicht genutzt ich weis nicht wie weit ich dann mein derzeitigen Aufbau aendern muss, wieviel Aufwand das waere. Zur Zeit habe ich Globale Objekte auf welche einfach zugeriffen werden kann, in jeder Funktion und in jedem Router weil sie ja global sind und in der gleichen Datei stehen, wenn man Worker benutzt muss man da dann bestimmt auch wieder einiges aendern.

Ich probiere jetzt erst mal Schritt fuer Schritt das lesen von den APIs zu optimieren, also unnoetige Aufrufe weglassen und abwarten bis eine Funktion alles gelesen hat danach koennte ich mir auch vorstellen dein zweiten Tipp mit den Endlosschleifen zu probieren ich bin mir aber noch nicht so sicher wie weit ich es verstanden habe, aber wenn du schon sagst man vermeidet damit Stackoverflow will ich es auch nutzen.
 
Ich hab bisher noch keinen echten usecase für worker gefunden weil die keinen Zugriff aufs window-object haben. Außerdem wirst du auch mit 100 XHR den main thread nicht auslasten können, zumal alle großen browser ein hard limit für concurrent requests in ihrer policy haben (bei chrome sind es z.B. 6).
Ein sleep kannst du folgendermaßen faken (wenn ich mich nicht täusche):
Javascript:
await new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve();
    }, 5000);
});
 
Ja die Nutzung von "setTimeout" wurde mir im Prinzip auch von ChatGPT als Loesung gegeben weil man in Javascript keine sleep Funktion hat, aber ich glaube der user "mibbio" hat dort eine Loesung fuer gefunden wie man ohne probleme while Schleifen benutzt und er sagt im prinzip fuehren diese Funktionen wie setInterval oder setTimeout irgendwann zu Stackoverflow und eine Endlosschleife waere geeigneter, ich werde mich die Tage langsam rantasten ob ich sowas hinbekomme.
 
Naja, setTimeout und setInterval haben schon ihren Verwendungszweck. Das Nachbilden von Sleep, wie im Beispiel von @floq0r wäre da sein eine Verwendung. Da kann man sich auch einfach eine Funktion draus machen.

Javascript:
function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve();
        }, ms);
    });   
}

An Stellen im Code, wo du eine bestimmte Zeit warten willst, kannst du die dann einfach mit
Javascript:
await sleep(3000); // 3000 durch gewünschte Zeit in ms ersetzen
verwenden.

Und Schleifen statt Rekursion haben auch den Vorteil, dass die sich in der Regel einfacher debuggen lassen.
 
Koennte man jetzt zum Beispiel die von dir gebildete sleep Funktion innerhalb einer while Schleife so einsetzen? Es sieht ja so aus wegen dem await das der Programablauf dann pausiert.

Jedoch wenn dann das Program pausiert koennte es zu anderen Problemen kommen. Ich habe jetzt zum Beispiel in meiner index.js auch einige Router welche von der Weboberflaeche angesprochen werden und ich weis nicht genau aber wenn jetzt so ein await den Programablauf pausiert dann wuerde ja die Weboberflaeche auch Probleme bekommen. Bei setTimeout wird ja nichts pausiert wie bei await dort wird nur nach Ablauf einer Zeit die Funktion erneut aufgerufen.

Aber ich habe da hoechstwahrscheinlich auch einige Denkfehler, im Prinzip habe ich ja ueberall in meiner Datei an verschiedenen Stellen ein await Statement stehen. Ich muss mich da ein wenig langsam rantasten durch verschiedene Test. Heute konnte ich schonmal einige Verbesserungen machen, ich habe zum Beispiel auch die Zeit ausgerechnet wie lange die letzte Abfrage zur APi gedauert hat und dann diese Zeit als Wartezeit an die setTimeout gegeben so wie in deinem Beispiel und auch die Nutzung von Flags habe ich eingebaut damit nicht eine Funktion jedesmal neu aufgerufen wird solange die noch arbeitet. Und ich habe auch alle drei Funktionen in ein Promise.all eingebaut damit die alle gleichzeittig starten, es laeuft jetzt auch soweit stabiler, ich bekomme alle 3 Sekunden meine console.log ausgedruckt von der Interval Funktion und gleichzeittig laufen alle drei Funktionen, davor hatte ich ja lange Wartezeiten zwischen den console.log

ich lasse mir da genug Zeit und will was sauberes und stabiles bauen, ich taste mich da langsam an jede Stelle und teste aus was gut funktioniert.
 
Ich habe heute eine neue Frage zum Thema optimieren, zur Zeit benutze ich SQLite fuer Node.js, die Standard SQLite Bibliothek fuer Node.js welche ich installiert habe bietet kein pooling an, aber anscheinend waere es besser wenn man sowas benutzt oder?

Zur Zeit habe ich es so aufgebaut das bei Serverstart einmalig eine Verbindung zur SQLite Datenbank erstellt wird, die Verbindung wird in eine Globale Variable gespeichert und danach wird diese Verbindung immer wieder benutzt, irgendwo scheint es so einfacher zu sein, man muss nicht ueberall eine Neue verbindung erstellen und dann wieder schliesen, aber ich weis auch vom hoeren und sagen das Leute empfehlen immer die verbindung wieder zu schliesen, da bin ich mir deswegen etwas unsicher was die beste Loesung waere, hat jemand hier Empfehlungen?
 
Ich habe jetzt einige von den Tipps zum Optimieren genutzt und es laeuft alles besser aber ich habe mir auch dazu entschieden Worker Threads zu benutzen fuer solche Aufgaben wie API von Exchanges lesen der Einbau dieser Worker Threads ist ja nicht zu kompliziert und es scheint ja ein Nutzen zu haben, denkt irgendjemand es wuerde Nachteile bringen wenn man Worker Threads benutzt? Weil eigentlich ist es ja so optimal, die ganzen API lesen Aufgaben koennen dann in Ruhe in einem anderen Thread ablaufen und im Hauptreahd laufen die Router fuer die Webapp und aehnliches Aufgaben, ich denke das bringt doch ein Nutzen so oder sieht irgendjemand Nachteile?
 
Der erste Nachteil ist der Implementationsaufwand, insbesonders in Hinblick darauf, dass du den main thread wie erwähnt mit API calls und parses sowieso nicht auslasten wirst können.

Und hier steht:
Additionally, web workers have a high overhead of creation and communication, as creating a web worker involves loading a separate JavaScript file and communicating with a web worker involves serializing and deserializing data. Furthermore, web workers have limited compatibility and support, as they are a relatively new feature of HTML5 and not all browsers support them or implement them consistently. You may need to use polyfills, fallbacks, or feature detection to ensure your web application works across different browsers and devices, and you may need to test and debug your web workers carefully.

Du wirst also vielleicht einen performance gain durch den overhead wieder annullieren.
 
Wenn es kein Vorteil bringt will ich es auch nicht einsetzen, aber vielleicht meinst du etwas anders als ich, was ich meine sind worker threads, wie in diesem Beispielvideo:

Du meinst vielleicht webworker welche etwas mit Browser zu tun haben, aber die worker threads welche ich meine sind fuer node.js und unabhaengig vom browser und die Nutzung ist sehr einfach, du must im Prinzip nicht viel aendern, sagen wir du hast eine Intervalfunktion welche eine Menge Sachen bearbeitet und dann die gesammelten Daten in Globale Objekte speichert, da kannst du dann zb einfach eine worker thread Datei erstellen den ganzen Code dort speichern und die globalen variablen kannst du auch einfach mit bisjen Code an diese Datei senden, die bearbeitet dann alles und am Ende der worker datei gibst du das gefuellte Objekt wieder an die original index.js zurueck und deine Webapp muss so nicht die ganze last tragen welche in der Intervalfunkktion abgearbeitet wird.

So gesehen hat es ja ein Nutzen aber die Frage wieviel mehr resourcen usw es kostet worker in node js ein zu setzen weis ich nicht, so wie es sich aber anhoert kann es aber ein guten Nutzen haben, ist ja auch logisch zwei oder drei threads koennen halt mehr leisten als nur einer, die Frage ist natuerlich ob diese extra threads irgendwie dann alles langsamer machen, also im prinzip sollen die ja alles schneller machen, wenn die aber zu viel extra cpu kosten oder aehnliches ja dann waere natuerlich der Nutzen auch in Frage zu stellen.

Und du sagst den main thread wird man sowieso kaum auslasten koennen, aber man sieht schon einige sekunden unterschied in der reaktionszeit wenn man zb so wie ich hier anfangs viele api calls macht mit einer intervalfunktion jede paar sekunden und aehnliche sachen.
 
Ah, da war ich komplett am falschen Dampfer. Ich hatte nur an webworker im Frontend gedacht.
Die Argumentation wird trotzdem gleich bleiben und wenn du ein paar Sekunden (!) Unterschied in der exec time siehst liegt es vielleicht an Asynchronitäten die du durch deine awaits im main thread nicht hättest. Schau dir mal die Auslastung per top an, der main thread ist garantiert nicht ausgelastet. Falls doch, dann hast du ein ganz anderes Problem.
 
Ich hatte anfangs zu lange wartezeit dank eurer hilfe habe ich einife fehler gefunden zb hatte ich unnoetig die gleiche Funktion wieder aufgerufen anstatt auf das Abarbeiten zu warten, jetzt laeuft die intervalfunktion schon sauberer alle drei sekunden so wie es sein soll ab und zu auch mal vier sekunden aber nicht mehr so lange wartezeiten wie davor. Es war eigentlich schon ganze zeit asynchron aufgebaut von mir, aber so wie es aussieht blockieren halt auch asynchrone Anfragen an eine API den Ablauf, weil auch ein asynchrones Statement wie await mal eine sekunde brauchen kann.

Anfangs habe ich mich selbst gewundert warum es ueberhaupt blockiert und man Wartezeiten hat weil ueberall benutzte ich asynchrone Anfragen mit Callback usw.

Ich muss zugeben sowas wie Auslastung habe ich bisher nie in der Entwicklerkonsole beobachtet es wird wohl auch nicht so sein das bei meinem Program die Auslastung zu hoch waere, das Problem fuer mich waren bisher nur die zu langen Wartezeiten.

Mich wuerde interissieren warum bist du gegen den Einsatz von Worker Threads, der Einbau ist doch gar nicht so schwer und man kann die ganze Arbeit in diesen Threads ablaufen lassen und wenn die Einsparrung nur Millisekunden Wartezeit weniger bedeutet, am Ende freut sich doch jeder ueber eine Webapp die schnell er reagiert usw.

Ich habe jetzt zwar mit euren vorherigen Tipps schon gute Optimierungen gemacht und frage mich warum sollte man jetzt diesen gut optimierten Code nicht einfach auch noch auslagern auf Worker Threads, wenn man sowieso schon am optimieren ist, kostet ja nichts so zu sagen der Einbau ist einfach.
 
Worker Threads bringen halt eine zusätzliche Komplexität in den Code. Die bieten dir keinen relevanten Mehrwert bei der Performance, machen den Code aber potentiell schwerer wartbarer und debugbarer.

Die Worker Threads sind laut Dokumentation für CPU-lastig Workloads gedacht, weil Nodejs an sich für asynchrones IO optimiert ist. Deine API-Abfragen sind nicht annähernd CPU-lastig, sondern reines IO (Daten umherschieben).
 
Zurück
Oben