Réalisations

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.

5 min de lecture commentaires

Logo KomgaJS et slogan « Rapide et moins de RAM », à côté d'un ordinateur portable affichant l'interface Komga avec des couvertures de mangas, comics, BD et ebooks

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

Les nouveaux articles par mail

Un mail par article publié. Rien d'autre, et désinscription en un clic.

Commentaires

Chargement des commentaires…

    Laisser un commentaire

    Vérification anti-robot en cours…

    Jamais publié.