Portrait de Philippe PradosPrados.FRProgrammation, architecture & sécurité — depuis 1998

La mort prochaine des ramasses-miettes

Les technologies de l’information dépendent de plusieurs ressources : la puissance des CPU, la mémoire, l’espace disque et la bande passante du réseau. Nous sommes capables d’augmenter les volumes de nos ressources, mais plus vraiment leurs performances. Lorsque la puissance n’est plus capable de gérer nos volumes, nous avons un problème. Il faut changer de paradigme.

Par Philippe PRADOS - 2013
www.prados.fr

L’évolution des ressources

Chaque catégorie de ressource bénéficie régulièrement d’évolutions technologiques, permettant des traitements de plus en plus performants.

Les tailles de la mémoire de masse (les disques durs) ont considérablement augmenté, mais le temps d’accès aux données n’a pas été capable d’évoluer en proportion. Il a fallu modifier les algorithmes de gestion des systèmes de fichiers, afin de garantir leur stabilité sans devoir effectuer une analyse complète du système en cas de crash. Il n’est plus possible d’analyser l’intégralité d’un disque dans un délai raisonnable.

Les technologies SSD permettent une rupture technologique. Dans quelques années, les disques dur classiques ne seront plus utilisés, au bénéfice de mémoire solide, avec des temps d’accès bien plus rapides. Les gestionnaires de fichiers seront à revoir pour exploiter au mieux ces technologies. Pourquoi continuer à fonctionner en secteur ? Pourquoi ne pas utiliser un allocateur comme en RAM ? Pourquoi ne pas recopier un fichier dans une nouvelle zone mémoire pour l’agrandir, comme on le pratique en RAM ? Il est alors immédiat de le mapper en mémoire.

Pour le moment, les disques SSD se dégradent naturellement au fur et à mesure de leurs utilisations. Après un certain nombre d'écriture, les blocs sont grillés et ne peuvent plus être utilisés. Il est alors pertinent de répartir les écritures le plus possible sur la mémoire statique. Cela a un impact sur la sécurité, car l’effacement d’une donnée sensible est difficile à maîtriser. En effet, les données ne sont que virtuellement effacées.

Les réseaux évoluent rapidement, avec une vitesse limite théorique proche de la vitesse de la lumière. Dans les faits, les contraintes électroniques réduisent les débits aux deux bouts de la connexion. On augmente leurs vitesses en réduisant les distances (Fiber channel, localisation des data centers près des usagers), en multipliant les câbles ou en améliorant les technologies.

Comme chacun sait, la loi de Moore arrive à ses limites. La fréquence des processeurs ne peut plus vraiment progresser sans un échauffement intolérable. La taille de la gravure des processeurs approche dangereusement des effets quantiques. Des fils trop fins entraînent que les signaux deviennent probabilistes et non déterministes. Et c’est difficile à débugger ;-)

Comme la puissance CPU est limitée, on multiplie les cœurs. Cela a un impact sur la synchronisation de ces derniers. À partir d’un certain nombre de cœurs (une centaine ?) Il faut trouver de nouvelles approches pour que ces derniers ne passent pas trop de temps à s’attendre les uns les autres. Les multiples cœurs ne peuvent être exploités qu’avec une modification profonde des programmes. Le développement linéaire classique n’est plus la solution.

Les langages ont suivi en intégrant de nouvelles syntaxes pour gérer le multitâche suivant différents paradigmes (mémoire partagée par les cœurs, isolés par cœurs, etc.) La synchronisation des caches mémoires des processeurs présente en effet des impacts importants en termes de performance. Moins il y a de collisions, plus les programmes sont véloces.

Les langages de développement évoluent pour suivre ces progrès et augmenter la productivité des équipes avec de nouveaux paradigmes (Objet, fonctionnel, parallélismes, etc.). Ils ont progressivement intégré un adressage mémoire 16 bits, puis 32 bits et maintenant 64 bits.

La gestion mémoire

Au début de l’informatique, le moindre cycle mémoire était important. Les langages étaient compilés et optimisés (C, C++). Comme la puissance le permettait, les langages avec machines virtuelles se sont chargés de la libération de la mémoire à l’aide d’algorithmes de ramasse-miettes. Ces derniers parcourent les objets d’un programme, afin d’identifier ceux qui ne sont plus nécessaires. Il est alors possible de les libérer, voire de regrouper les objets encore vivants les uns à côté des autres, afin de réduire la fragmentation de la mémoire (le fromage d’un côté, les trous d’un autre).

Mais, la loi de Moore (doublement des capacités tous les 18 mois) présente des limites qui seront prochainement atteintes. Intel prédit l'atterrissage pour 2018. Il ne sera plus possible d’améliorer la vitesse d’exécution des programmes gratuitement, rien qu’en changeant de matériel.

Dans le même temps, les limites d’accès aux disques entraînent de nouvelles approches pour la gestion des bases de données (Big Data, all in-memory, NoSQL, etc.). Suivant les scénarios, ces dernières sont maintenant réparties sur plusieurs nœuds, avec les données intégralement en mémoire.

En théorie, un processeur 64 bits peut adresser 256 tébioctets (240) ou plus suivant les architectures. L’adressage permet maintenant de monter l’intégralité des bases de données des entreprises en mémoire, pour effectuer des calculs sans accès disques. L’aire du Big-memory approche.

Dans quelques années, les mémoires statiques vont rejoindre les mémoires volatiles. Soit les processeurs travaillent directement sur la mémoire statique car les performances seront au rendez-vous (avec éventuellement des caches volatiles), soit les mémoires statiques ne seront plus assez rapides et toutes les données seront montées en mémoire volatile (in memory database).

Par exemple, analyser les statistiques de plusieurs centaines de millions de smartphones est difficile hors de la mémoire. Il faut pouvoir poser la question suivante : “Combien d’utilisateurs uniques avec un téléphone de la marque X ont eu cette erreur la semaine dernière ?” Les implémentations actuelles fonctionnent uniquement en mémoire.

Il coûte toujours moins cher d’ajouter de la mémoire que d’ajouter un nouveau nœud. La mémoire des serveurs est multipliée par 100 tous les dix ans. Il ne semble pas y avoir de raison objective que cela cesse. Il s’agit d’une augmentation du volume, sans nécessiter d’augmentation de la puissance.

http://www.jcmit.com/mem2012.htm

Quel que soit le langage, l’adressage mémoire 64 bits présente des impacts négatifs en performance par rapport à un accès 32 bits. Cela pour plusieurs raisons. La plus importante est que les pointeurs prennent deux fois plus de place (4 bytes en 32 bits contre 8 bytes en 64 bits). À l'exécution, le cache de premier niveau est plus rapidement saturé. Il existe alors des options pour l’exécution des machines virtuelles Java, pour utiliser des pointeurs 32 bits avec un processeur 64 (mode hybride). Chaque accès via un pointeur demande une arithmétique sur ce dernier, pour le convertir en 64 bits. Suivant la taille mémoire maximum allouée à la JVM, cette dernière choisit une version différente pour la gestion des pointeurs. Il est bien entendu possible de forcer une approche particulière par des paramètres.

Exigence mémoire virtuelleImpact/Contrainte
M < 2GoPartout
2Go < M < 3GoUniquement sous Linux à cause du mapping mémoire utilisé par Windows.
M < 4GoUtilisation de pointeurs 32 bits, sans conversion
4Go < M < 32GoUtilisation de pointeurs 32 bits, avec conversion mais sans traitement de la valeur NULL
32Go < M < 1 milliard d'objetsUtilisation de pointeurs 32 bits, avec conversion et traitement de la valeur NULL
1 milliard d'objets < MUtilisation de pointeurs 64 bits
Exigence mémoire virtuelle
Impact/Contrainte

M < 2Go

Partout

2Go < M < 3Go

Uniquement sous Linux à cause du mapping mémoire utilisé par Windows.

M < 4Go

Utilisation de pointeurs 32 bits, sans conversion

4Go < M < 32Go

Utilisation de pointeurs 32 bits, avec conversion mais sans traitement de la valeur NULL

32Go < M < 1 milliard d'objets

Utilisation de pointeurs 32 bits, avec conversion et traitement de la valeur NULL

1 milliard d'objets < M

Utilisation de pointeurs 64 bits

L'utilisation des registres complémentaires des architectures 64 bits permet d'utiliser une très grande partie des paramètres et des variables locales dans les registres. Cela contribue à une amélioration des performances dans les traitements. La compilation JIT bénéficie des registres supplémentaires, permettant sur certaines architectures, d'avoir une dégradation des performances limités à 15% en 64 bits, malgré la moins bonne exploitation du cache de premier niveau.

C’est le début de la fin des ramasse-miettes. Augmenter la taille mémoire commence à nuire gravement à l’approche de la gestion automatique de la mémoire. Il n’est pas rare d’avoir plusieurs secondes de pause au-delà de 4Go de mémoire. Avec des volumes si importants, il va devenir de plus en plus difficile de laisser les langages gérer la mémoire.

Comment fonctionne un ramasse-miettes ?

En théorie, un ramasse-miettes doit partir d’un objet racine, puis parcourir tous les chemins possibles vers tous les objets. Sur chaque objet identifié, l’algorithme place un drapeau. Une fois qu’il n’y a plus aucun chemin possible, une deuxième passe regarde tous les objets en mémoire afin de libérer ceux n’ayant pas de drapeau (MarkSweepCompact). Ils peuvent être détruits. En général, la phase de dépose des drapeaux s’effectue essentiellement en tâche de fond. Si des pointeurs ont été modifiés entre temps, ils sont mis de côté pour être traité à nouveau plus tard (ConcurrentMarkSweep). La phase de nettoyage s’effectue après avoir interrompu tous les flux de traitements de l’application. Cette phase est appelée : arrêt-du-monde. Elle comprend la fin du traitement de marquage, le compactage de la mémoire et la libération des objets.

Cette approche a été utilisée pour les premières versions de Java ou la première version du ramasse-miettes de la machine virtuelle présente dans Android (Dalvik). Pour pouvoir fonctionner, cet algorithme impose d’interrompre tous les traitements du programme, le temps de l’analyse. En effet, l’analyse ne peut accepter une modification de la mémoire en cours de route. La machine virtuelle arrête tous les threads, fait le ménage, puis relance les traitements. C’est l’arrêt-du-monde constaté parfois sur les anciennes versions d’Android.

Une autre approche consiste à ajouter un compteur de référence sur chaque objet. Dès qu’un pointeur référence un objet, le compteur est incrémenté. Dès que le pointeur est perdu, le compteur est décrémenté. Si le compteur tombe à zéro, l’objet n’est plus référencé. Il peut être détruit. Cela fonctionne pour une partie des objets, mais ne permet pas d’identifier les cycles, lorsque A pointe sur B qui pointe sur A. Mais cela réduit le nombre d’objets à traiter par une approche plus classique. Une partie des objets est détruite dès que possible. Le solde sera traité par un ramasse-miettes classique.

Cette approche présente plusieurs inconvénients. Elle oblige à avoir un compteur supplémentaire pour chaque objet. Cela ne sera plus un problème avec des volumes de mémoire importants. Mais comme ce compteur doit être modifié à chaque manipulation de pointeur, cela oblige à accéder à l’objet référencé, perturbant les caches des processeurs. De plus, la manipulation du compteur doit être thread-safe.

La destruction d’une grappe importante d’objets peut également prendre du temps, ce qui ralentit les programmes. Des approches consistent à placer rapidement les objets dans une file d’attente, pour un nettoyage lorsque le thread est moins chargé, entre deux messages (auto-release de iOS). Cela fonctionne, tant que le nombre d'objets est raisonnable. Il y a le risque qu’une allocation mémoire échoue, car le nettoyage n’a pas encore été fait.

Pour réduire la période d’indisponibilité de l’application lors de l’intervention du ramasse-miettes, des algorithmes recherchent une portion seulement d’objets à supprimer (Algorithme Générationnel). Quitte à y revenir souvent. Le traitement est dilué dans le temps.

Il faut qu’il existe des phases de traitements sollicitant moins la mémoire et le CPU. Sinon, le nettoyage partiel risque de ne plus être suffisant pour une allocation. Certains objets sont nettoyés rapidement car éphémères, d’autres seront nettoyés plus tard, lorsqu’une allocation mémoire ne pourra être satisfaite. L’objectif est de réduire au maximum le temps d’indisponibilité du programme. De nombreuses astuces permettent de limiter la période d’arrêt-du-monde, sans jamais réussir à l'annuler complètement. Un nettoyage complet est alors nécessaire, interrompant le programme.

Pour réduire la fragmentation de la mémoire, les objets peuvent être copiés d’un espace à un autre. Le nombre de copies effectuées indique implicitement la durée de vie de l’objet. Après un certain nombre de copies, l’objet est placé dans une zone mémoire regroupant les objets ayant une longue durée de vie. Il sera nettoyé plus tard, lorsque la mémoire viendra vraiment à manquer.

Pour améliorer cela, de nouveaux algorithmes travaillent en tâche de fond, sur des portions de la mémoire (algorithme G1). Les objets à durée de vie longue sont alloués dans des blocs de taille fixes au fur et à mesure. Ainsi, l’algorithme peut se focaliser d’abord sur les blocs les plus anciens. Si le bloc devient vide, il peut être détruit. Sinon, les objets restants sont distribués judicieusement sur les autres blocs. L’objectif est de regrouper les objets liés entre eux dans les mêmes blocs.

Pour encore améliorer cela, plusieurs processeurs sont sollicités pour faire le ménage en même temps. Pour des raisons techniques, cela augmente la fragmentation de la mémoire. Ces derniers peuvent être paramétrés pour limiter le temps d’indisponibilité à une durée arbitraire. Des statistiques permettent d’ajuster les paramètres internes en conséquence. Autant que possible, au-delà du délai, l’algorithme s’interrompt, laissant des objets parasites en mémoires. À la longue, la mémoire peut être quand même saturée. Si l’algorithme passe plus de 98% de son temps à faire le ménage, une erreur de mémoire est alors générée.

Mais ces approches peuvent réduire les performances. En effet, le ou les processeurs en charge du ramasse-miettes entraîne l’invalidation du cache des autres processeurs. En effet, tous les processeurs partagent la même zone mémoire. Et comme toute la mémoire est parcourue, cela arrive très souvent. Les approches segmentées réduisent la probabilité de collisions des caches des processeurs. Des travaux cherchent à améliorer cela au niveau des algorithmes de ramasses miettes ou de synchronisation des caches des processeurs.

Est-ce que les entreprises ont bien conscience qu’une partie non négligeable de leurs infrastructures ne sert qu’à faire le ménage dans la mémoire ? Si un cœur sur quatre sert à cela, c’est 25% de la puissance du centre de calcul qui est sacrifié. Sur quatre étages, un est consacré intégralement à nettoyer la mémoire. Plusieurs millions d’euros, juste pour gérer ce que les développeurs ne souhaitent pas prendre en charge.

Est-ce que cela compense les gains de productivité sur les développements ? Cela dépend du nombre de nœuds pour traiter les données. Les développeurs de Google préfèrent utiliser des approches plus complexes et plus efficaces, car l’équation leur est bénéfique. Protobuf est plus efficace que JSon. A chacun de l’estimer.

Chaque nouvelle génération des langages améliore les performances du ramasse-miettes, pour encaisser de plus en plus de mémoire. Cela aura bientôt une fin. Il ne sera plus possible de nettoyer la mémoire sans interrompre notablement les traitements.

Comme les programmes souhaitent en même temps utiliser de plus en plus de mémoires, pour analyser de gros volumes de données (Big Data, Dataware, Datamart, ...) ou pour du temps réel, la catastrophe est proche. Des traitements analytiques seront de plus en plus présents dans les applications car la valeur de l’entreprise sera là (analyse de graphes sociaux ou sémantiques, analyses de logs, de comportement, prédictions, conseil d’achat, etc.).

Il est clair que les nouveaux algorithmes de gestion de la mémoire ne font que retarder l’échéance. Inévitablement, dans une dizaine d’années, il ne sera plus possible d’utiliser un ramasse-miettes pour gérer la mémoire.

Le début de la fin

Avec les JVM 64 bits, le temps d’analyse de la mémoire a considérablement augmenté. Les périodes d’indisponibilité également. Des paramètres spécifiques des JVM permettent de retarder l’échéance, en optimisant les situations généralement rencontrées actuellement.

Pour libérer dix objets, l’effort est proportionnel à la mémoire globale. Pratiquement tous les algorithmes de GC sont victime d’une malédiction :

« Plus on augmente la mémoire, plus on dégrade les performances. »

Comme la puissance des processeurs est limitée, cette loi va entraîner inévitablement un changement d’approche.

La seule solution pour contrer cette terrible loi, consiste à réduire le volume de donnée à traiter par le ramasse-miettes. Si un groupe d’objet peut être isolé des autres, il est possible d’y faire le ménage rapidement. Les programmes doivent être découpés en tranches (ou agent), avec des espaces mémoires réduits. Le ramasse-miettes travaille en parallèle sur des sous-ensembles d’objets. Cette approche est pertinente si l’algorithme lui-même n’a pas besoin de plus de mémoire que la tranche qui lui est allouée.

Tant que les objets sont alloués dans la zone des objets temporaires, et que cette zone reste à une taille raisonnable, nous pouvons encaisser l’augmentation de volume.

Une première approche consiste à isoler les groupes d’objets créés par le même thread. Chaque thread possède son allocateur. Cela permet également de supprimer le verrou global de l’allocateur mémoire du programme mais augmente la fragmentation. Des ramasse-miettes peuvent alors s’occuper de ces données à la mort du thread ou périodiquement. Cela limite les données à traiter, tant qu’elles ne sont pas partagées par plusieurs threads. Il faut détecter dynamiquement les objets utilisés par plusieurs threads pour les exclure du groupe associé au thread. Un groupe “processus” peut se charger des objets partagés. Mais cela ne fait que retarder l’échéance. Android utilise une approche similaire.

Une autre approche consiste à utiliser des verrous hardwares lors de la lecture d’une zone mémoire, pour ajuster alors les pointeurs vers les nouveaux emplacements mémoires des objets ou pour les marquer sans interrompre le processus. L’algorithme C4 de Azul permet de nettoyer la mémoire sans arret-du-monde, en capturant les accès mémoires vers les objets, via des fautes de pages et un module noyau. Cela ne fonctionne que sur Linux. Est-il nécessaire de faire évoluer les systèmes d’exploitation pour pouvoir gérer correctement les ramasses-miettes ? Pourquoi pas.

Quelques projets de bases de données en mémoires proposent déjà des approches mixtes, combinant une JVM et un allocateur mémoire externe (Terracotta ou Apache Direct Memory). Utiliser une mémoire hors heap permet en effet de régler pas mal de problèmes. Les développeurs sont ainsi libérés de la gestion mémoire lors des traitements, mais doivent gérer le cycle de vie des objets en dehors du ramasse-miettes. L'existence même de ces composants démontre que le ramasse-miettes est déjà un problème qu’il faut gérer.

Comme pour les autres technologies, les langages devront évoluer pour intégrer ces nouvelles contraintes. Même si la mode du moment est d’avoir systématiquement un ramasse-miettes pour chaque nouveau langage, il va falloir trouver d’autres approches pour une gestion prédictible et déterministe de la mémoire. Fini le temps où la mémoire est traitée différemment que les fichiers ouverts, les sockets, les sessions utilisateurs, etc. Il va falloir rentrer dans le rang et gérer toutes les ressources dans les développements.

Il va être difficile de faire migrer des développeurs qui n’ont connu que Java, vers de nouvelles approches exigeant une gestion mémoire non automatique. Les développeurs des applications iOS démontrent qu’ils en sont capables, car Apple à déprécié le ramasse-miette d'Objective-C.

Bien entendu, suivant les contextes, les impacts du ramasse-miettes seront différents. Apple à choisi de le bannir, afin d’optimiser au maximum les performances. Google à choisi de le garder dans Android, afin de privilégier la portabilité du code sur différents processeurs, et éviter les fuites mémoires.

Par contre, les contextes de type Big Data + Database in memory risquent de souffrir.

Autres pistes ?

Bien entendu, il est préférable d’éviter de gérer les couples malloc()/free(). Il existe des solutions sémantiques à cela, pas forcément très compliquées, et permettant une gestion efficace de la mémoire. L’intégralité de la puissance CPU est alors dédiée au programme. Il ne faut pas dédier une fraction de la puissance à faire le ménage en tâche de fond.

Il est envisageable d’enrichir les langages de nouveaux types de pointeurs, pour associer automatiquement le cycle de vie des objets à la vie du pointeur. L’objectif est d’enrichir la sémantique des pointeurs pour identifier, lors de la compilation, le moment exact où une donnée peut être effacée. Un pointeur peut être une relation, une agrégation, une agrégation partagée, référencer un objet immuable, etc. Suivant les cas, la gestion de la mémoire peut être déterministe. L’essentiel des problèmes de gestion de la mémoire est dû à l’absence de sens sur chaque pointeur.

La version 11 de C++ propose quelques approches comme les rvalue references, les unique_ptr (pour gérer les agrégations) à la place des auto_ptr, les shared_ptr et weak_ptr pour les compteurs de références. Ces approches permettent une gestion de la mémoire déterministe, explicite, avec une sémantique forte, sans utiliser de ramasse-miettes.

Même pour des environnements plus légers, le ramasse-miette est remis en cause. Apple a déprécié le ramasse-miettes d'Objective-C. Il propose la technologie Automatic Reference Counting (ARC) pour déduire de l’analyse des sources, le cycle de vie des objets. Cette technologie s’appuie sur des compteurs de références. Lorsque le compteur tombe à zéro, l’objet peut être supprimé. La gestion des cycles dans les objets est alors problématique. Les compteurs ne sont plus à zéro, alors que l'îlot d’objets n’est plus accessible par un autre pointeur. Des pointeurs faibles permettent de gérer ces situations “à la main”.

D’autres approches proposent un ramasse-miettes à base de compteur de référence et des algorithmes pour détecter les cycles.

D’autres approches encore, proposent d’isoler les traitements dans des espaces mémoires réduits. Chaque processus ou traitement travaille dans un espace confiné. La communication entre les traitements s’effectue à base de messages (Erlang, Go). Dans une certaine mesure, Javascript ou Dart propose cette démarche pour la gestion du multitâche. Cette approche présente l’avantage de permettre la distribution des traitements sur plusieurs serveurs. Un passage par valeur des données, entre différents espaces d’exécutions, peut être la solution. Elle présente néanmoins un coût en performances dû aux sérialisation/dé-sérialisation des messages.

Une autre approche consiste à utiliser systématiquement des objets immuables. Un objet ancien ne peut pas pointer vers un objet plus récent. La direction du graphe permet alors d’optimiser les algorithmes. L'analyse se limite quasiment (en pratique) à une analyse locale dans l'espace mémoire du thread. L’arrêt-du-monde est limitée aux variables globales, très peu nombreuses en général sur les sites Web. Les langages fonctionnels comme Haskell proposent ce type d’approche.

Des extensions C++ à Hadoop permettent déjà d’améliorer notablement les performances des algorithmes Map-Reduce. Avec des résultats significatifs.

BugSense, un gestionnaire de logs mobiles, préfère utiliser ERLang, C, C++ et Lisp, pour améliorer les performances, l’empreinte mémoire et finalement, réduire le nombre de machines dans le Cloud, donc le coût.

Les machines les plus puissantes d’Amazon EC2 proposent 15 Go de mémoire. Dans quelques années, nous aurons 1To. Quel ramasse-miettes sera capable de gérer cela ?

Les derniers chiffres que nous avons trouvés datent de septembre 2008, avec 3,5 Terabyte. L’arrêt-du-monde dure 30 secondes. Les algorithmes ont été optimisés depuis, mais en quelle proportion ?

L’avenir

Nous pouvons dès à présent choisir les critères permettant de sélectionner une technologie pour les années futures. Comme toutes les ressources arrivent définitivement à saturation, nous devons sélectionner un langage de développement et une architecture qui soit capable de répondre à plusieurs critères. Dans l’idéal, les solutions sélectionnées doivent pouvoir encaisser l’augmentation des volumes des ressources, sans exiger de puissance supplémentaire.

En situation de Big-memory, il faut probablement une gestion “à la main” ou une gestion aidée par un enrichissement sémantique des pointeurs (compteurs de références, sémantiques d’agrégation, etc.). La libération de la mémoire doit être déterministe.

Pour gérer la puissance CPU, les traitements doivent pouvoir être répartis entre plusieurs nœuds. Les langages doivent supporter ce paradigme, en facilitant la communication entre les traitements, par l'envoi de données par valeur.

Pour le réseau, les communications points à points doivent être privilégiées. Ainsi, en augmentant le nombre de câbles, on est capable d’augmenter la vitesse effective de transmission.

Pour la mémoire de masse, les données doivent pouvoir être traitées intégralement en mémoire. La persistance s’effectue, soit avec une mémoire statique, soit avec une réplication entre plusieurs nœuds, soit en enregistrant des logs lors de chaque modification. L’enregistrement étant au fil de l’eau, il n’est pas nécessaire de repositionner la tête de lecture lors de chaque écriture.

Ces critères de choix permettent de sélectionner aujourd’hui les technologies à investir.

Il y a quinze ans, nous étions peu nombreux à penser que les bases de données relationnelles seraient contestées, pour utiliser d’autres approches comme des données intégralement en mémoire ou d’autres NoSQL.

Parions que les mémoires statiques et volatiles vont se rejoindre. Qu’ainsi, la notion de mémoire de masse disparaisse. Toutes les données seront présentes en mémoire et persistantes. Les ramasse-miettes ne seront alors plus la solution, mais le problème. Dans quelques années, une révolution sera nécessaire dans la gestion mémoire des langages.

Les ramasse-miettes ne sont pas encore morts, mais des approches complémentaires sont nécessaires.