Combien de setTimeouts simultanés avant les problèmes de performance?

J'ai une application node.js avec 10k-100k simultané setTimeouts fonctionnant à un moment donné. (Ils ont tous une durée de 5 minutes.) Le rappel est assez trivial, juste un HDECRBY en redis. Je n'ai pas encore eu de problèmes de performance avec cela, même sur une instance t2.micro.

Je sais que je rencontrerai des problèmes si les fonctions de rappel ne peuvent pas être exécutées aussi rapidement que je définis setTimeouts (évidemment), mais il y a-t-il des problèmes avec un nombre élevé de setTimeouts, en soi ? Par exemple, est-ce que je vais courir dans un goulot d'étranglement RAM si j'élargis, par exemple, 1 million de concurrents? 10 millions?

Pour ces types de questions, il est souvent utile de regarder simplement comment node.js gère les temporisateurs dans le code source .

Ce que vous trouverez, c'est que node.js conserve une ou plusieurs listes liées de ses propres objets temporels internes et que tous les temporisateurs sont configurés en même temps pour partager une minuterie libuv. Cela signifie que des zillions de minuteries configurés pour se produire dans une fenêtre de temps assez spécifique vont inévitablement partager de nombreux temps de tir et donc partageront des listes de minuteries et partageront ainsi de nombreux objets temporisés du système.

Cela réduit le problème pour avoir des zillions d'objets temporisés. Maintenant, chaque objet de minuterie nécessite encore une certaine mémoire et toutes les opérations de l'exécution de la minuterie ne sont pas toujours constantes, mais vous pouvez voir dans les commentaires ci-dessous, ils ont essayé de faire le plus possible possible d'être à temps constant pour permettre un grand nombre de temporisateurs Avec une performance toujours décente.

Si vous n'avez pas besoin d'une précision absolue exactement lorsque la minuterie se déclenche, vous risquez probablement de faire en sorte que vos chronométreurs se coalisent et de partager des objets temporisés plus souvent en programmant vos temporisateurs uniquement pour des limites de temps spécifiques, comme un nombre pair de 100 ms. Cela planifiait plus de vos zillions de temporisations pour le même temps de tir et permettrait à node.js de mettre plus de temporisations dans la même liste que toutes les parties d'une seule minuterie de système. Je ne sais pas si cela est possible avec vos minuteries ou s'il est même nécessaire, mais en étudiant comment fonctionne node.js, cela augmenterait l'efficacité. Il y aurait à la fois deux listes de minuterie en interne pour node.js et moins de temporisateurs de système dans libuv.


Voici quelques commentaires explicatifs à partir du code node.js sur les horlogers qui explique certains aspects de la conception:

// HOW and WHY the timers implementation works the way it does. // // Timers are crucial to Node.js. Internally, any TCP I/O connection creates a // timer so that we can time out of connections. Additionally, many user // user libraries and applications also use timers. As such there may be a // significantly large amount of timeouts scheduled at any given time. // Therefore, it is very important that the timers implementation is performant // and efficient. // // Note: It is suggested you first read though the lib/internal/linkedlist.js // linked list implementation, since timers depend on it extensively. It can be // somewhat counter-intuitive at first, as it is not actually a class. Instead, // it is a set of helpers that operate on an existing object. // // In order to be as performant as possible, the architecture and data // structures are designed so that they are optimized to handle the following // use cases as efficiently as possible: // - Adding a new timer. (insert) // - Removing an existing timer. (remove) // - Handling a timer timing out. (timeout) // // Whenever possible, the implementation tries to make the complexity of these // operations as close to constant-time as possible. // (So that performance is not impacted by the number of scheduled timers.) // // Object maps are kept which contain linked lists keyed by their duration in // milliseconds. // The linked lists within also have some meta-properties, one of which is a // TimerWrap C++ handle, which makes the call after the duration to process the // list it is attached to. // // // ╔════ > Object Map // ║ // ╠══ // ║ refedLists: { '40': { }, '320': { etc } } (keys of millisecond duration) // ╚══ ┌─────────┘ // │ // ╔══ │ // ║ TimersList { _idleNext: { }, _idlePrev: (self), _timer: (TimerWrap) } // ║ ┌────────────────┘ // ║ ╔══ │ ^ // ║ ║ { _idleNext: { }, _idlePrev: { }, _onTimeout: (callback) } // ║ ║ ┌───────────┘ // ║ ║ │ ^ // ║ ║ { _idleNext: { etc }, _idlePrev: { }, _onTimeout: (callback) } // ╠══ ╠══ // ║ ║ // ║ ╚════ > Actual JavaScript timeouts // ║ // ╚════ > Linked List // // // With this, virtually constant-time insertion (append), removal, and timeout // is possible in the JavaScript layer. Any one list of timers is able to be // sorted by just appending to it because all timers within share the same // duration. Therefore, any timer added later will always have been scheduled to // timeout later, thus only needing to be appended. // Removal from an object-property linked list is also virtually constant-time // as can be seen in the lib/internal/linkedlist.js implementation. // Timeouts only need to process any timers due to currently timeout, which will // always be at the beginning of the list for reasons stated above. Any timers // after the first one encountered that does not yet need to timeout will also // always be due to timeout at a later time. // // Less-than constant time operations are thus contained in two places: // TimerWrap's backing libuv timers implementation (a performant heap-based // queue), and the object map lookup of a specific list by the duration of // timers within (or creation of a new list). // However, these operations combined have shown to be trivial in comparison to // other alternative timers architectures. // Object maps containing linked lists of timers, keyed and sorted by their // duration in milliseconds. // // The difference between these two objects is that the former contains timers // that will keep the process open if they are the only thing left, while the // latter will not.