KomgaJS : Komga en Node.js, six fois moins de RAM
Komga bouffait 1 Go de RAM sur mon RPi 5. J'ai fait porter son serveur Java ligne par ligne en Node.js par une IA : même Komga, trois à six fois moins de mémoire.
Il y a quelques semaines, je vous ai parlé de Komga. Un super outil, vraiment génial pour les eBooks, les comics et les mangas. En plus compatible avec les liseuses et les outils du même genre. Sauf qu’il avait un défaut, et j’ai fini par m’en occuper.
1 Go de RAM à lui tout seul
Je le faisais tourner dans ma stack Docker, sur mon RPi 5. Petit souci : à lui tout seul, il bouffait 1 Go de RAM. C’était le plus gros de tous les conteneurs.
La raison, et je ne vais pas faire du militantisme sur les technologies (c’est pas le sujet ici, surtout que niveau syntaxe je l’aime beaucoup) : Java est un gouffre à RAM. Les joueurs et les hébergeurs de Minecraft en témoigneront, lol. Et Komga est historiquement construit sur Java, en tout cas la partie serveur.
Je vais pas vous mentir, ça me faisait littéralement un peu ch…. 😉
À une IA, il faut demander des choses inhumaines
Et là, j’ai eu une idée.
Depuis plusieurs mois, je travaille beaucoup (pour ne pas dire tout le temps, ou presque) avec des IA, et notamment avec notre ami Claudio. Je vous ferai je pense un article plus tard sur lui, et sur comment il a changé le monde du dev. Je continue pour certains de mes clients à coder à la main (philosophie interne, et amour de l’écriture), mais on est quand même sur de l’artisanal.
En regardant une vidéo de la chaîne Underscore, « On ne paie plus les développeurs pour écrire du code ? », il y a un passage de Quentin Adam où j’ai eu l’impression de m’entendre, et d’avoir vécu les mêmes choses. Il dit qu’à une IA, il faut demander des choses inhumaines.
Alors j’ai demandé un truc inhumain : porter à plat, direct, ligne pour ligne, chaque fonction Java/Kotlin du serveur Komga en Node.js. J’ai même hésité pour Rust, mais le manque de bibliothèques externes pertinentes augmentait l’affaire énormément.
Ligne par ligne, et testé des deux côtés
Il a porté ligne par ligne. Chaque fonction a été testée unitairement des deux côtés, pour qu’elle fasse la même chose. Le but : avoir le même serveur Komga, mais plus en Java, en Node.js.
Point important : je n’ai rien fait à part prompter et bouffer (énormément) de tokens. Presque 150 agents en même temps, 30 h de travail qui run. Des tests Playwright et des tests unitaires sur chaque fonction, qu’il a fallu aussi écrire en Java à l’identique pour avoir une référence. Mais ça tournait. Je venais voir de temps en temps, et je testais le résultat.
Pour vous donner l’échelle, tout est dans le dépôt :
- les 442 fichiers du backend de Komga ont chacun leur jumeau TypeScript, même nom, même endroit, mêmes fonctions dans le même ordre ;
- les 775 tests Kotlin de Komga sont portés ;
- 1 380 des 1 384 fonctions Kotlin sont comparées à l’original : le vrai code de Komga tourne sur la JVM, ses résultats sont enregistrés, et la version Node.js doit rendre exactement les mêmes valeurs. 11 684 cas en tout, et environ 70 bugs de portage trouvés et corrigés en les écrivant.
J’ai appliqué quelques divergences légères pour gagner en performance (techno différente oblige), mais sur des détails : l’index de recherche a son propre format de fichier, et les miniatures sont encodées par d’autres bibliothèques. En tout cas, le travail est au rendez-vous.
Le résultat : le jour et la nuit
Je maintiens, le but n’est pas de faire une comparaison de taille de voiture. On sait que la JVM de Java est gourmande, elle a d’autres avantages. Et on sait que Node.js, avec ses inconvénients, est énormément plus rapide à exécuter et moins gourmand en RAM. Et ça se constate.
Même bibliothèque de 60 BD (645 Mo), même scénario, même machine, chaque serveur démarré sur une configuration vierge :
| Komga (JVM) | KomgaJS | ||
|---|---|---|---|
| Au repos, après le démarrage | 599 Mo | 165 Mo | ÷ 3,6 |
| Après scan et analyse de la bibliothèque | 1 372 Mo | 234 Mo | ÷ 5,9 |
| Après lecture (miniatures, pages) | 1 496 Mo | 235 Mo | ÷ 6,4 |
| Démarrage | 11,3 s | 1,0 s | ÷ 11 |
| Scan et analyse des 60 livres | 13,3 s | 10,4 s | 1,3 × plus rapide |
Et sur un Raspberry Pi, avec une vraie bibliothèque de 6 594 livres répartis en 4 bibliothèques, chaque serveur tournant seul pendant 15 minutes :
| Komga (JVM) | KomgaJS | ||
|---|---|---|---|
| Au repos, 15 min après le démarrage | 483 Mo | 166 Mo | ÷ 2,9 |
| Pic (démarrage, scan des 4 bibliothèques) | 492 Mo | 256 Mo | ÷ 1,9 |
| Démarrage | 18,3 s | 4,5 s | ÷ 4 |
| Temps de réponse de l’API, médiane | 8 ms | 4 ms | |
| Temps de réponse de l’API, 99ᵉ centile | 391 ms | 16 ms |
C’est le jour et la nuit.
Et comme c’est un portage direct, les mises à jour pourront suivre les versions de Komga sans réel problème. Chaque fichier porte la référence du commit Komga dont il vient : quand Komga avance, il suffit de lire le diff et de le reporter. Aujourd’hui, KomgaJS suit Komga 1.27.1.
L’installer : même procédure que d’habitude
Pour l’installation, procédure habituelle : je recommande Docker et Portainer. Le compose vient de la doc d’installation du dépôt, avec l’image latest :
services:
komga:
image: ghcr.io/smeagolworms4/komga-js:latest
container_name: komga
volumes:
- /chemin/vers/config:/config
- /chemin/vers/ma/bibliotheque:/data:ro
ports:
- 25600:25600
environment:
- TZ=Europe/Paris
restart: unless-stopped
L’image existe aussi sur Docker Hub (smeagolworms4/komga-js), en amd64, arm64 et armv7 : ça tourne donc aussi sur les Raspberry Pi 32 bits.
Quelques variables d’environnement permettent de régler la consommation, mais les valeurs par défaut conviennent très bien :
| Variable | Par défaut | Effet |
|---|---|---|
KOMGAJS_MAX_HEAP_MB |
¼ de la limite mémoire du conteneur, au moins 256 Mo | plafond de mémoire JavaScript |
KOMGAJS_IMAGE_THREADS |
2 | threads par opération d’image (1 : le moins de mémoire, 0 : tous les cœurs) |
UV_THREADPOOL_SIZE |
4 | opérations natives simultanées |
KOMGAJS_TASK_WORKER |
false | true exécute les tâches dans un thread séparé (+60 à 100 Mo pendant les tâches) |
Les réglages de Komga, eux, restent les mêmes : le fichier application.yml et les variables KOMGA_* s’appliquent pareil.
Reprendre votre Komga tel quel
Le plus, c’est que si vous avez un serveur Komga existant, vous pouvez reprendre le même dossier de config. C’est le même backend, en fait : même base de données, table pour table. Le seul fichier qui n’est pas partagé, c’est l’index de recherche, et il se reconstruit tout seul au premier démarrage.
Je vous conseille de faire une sauvegarde avant, on sait jamais. Mais personnellement, je n’ai eu aucun souci. Seule règle : on bascule de l’un à l’autre, on ne fait pas tourner les deux en même temps sur la même config.
Vos retours sont les bienvenus
Je serai ravi d’avoir vos retours. Si vous voyez des bugs, il y a de fortes chances que ce soit lié à la version d’origine. Mais si vous voyez des bugs liés au portage lui-même, là je suis votre homme : ouvrez une issue sur le dépôt. Et je serai ravi d’avoir des contributions au projet.
Et n’oubliez pas : Komga, ses idées et son design, c’est le travail de Gauthier Roebroeck et de ses contributeurs. Si vous donnez, donnez à Komga en premier.
Image d’en-tête générée par IA.
SmeagolWorms4
Commentaires