Un log Kafka peut déjà contenir la dernière valeur de chaque clé. Pourtant, dès qu'on veut demander simplement « donne-moi la valeur de user-42 », on finit souvent par recopier ces données dans un autre endroit conçu pour les lire vite.

Marcos Sánchez a décidé que cette duplication l'agaçait assez pour en faire un logiciel.

Son projet s'appelle rpkv. Il tient dans une idée presque bête : ne pas recopier la valeur. Garder seulement l'endroit où elle se trouve.12

Dans son index Pebble, une clé pointe vers un couple (partition, offset). Quand un client demande une valeur, rpkv consulte ce petit index puis retourne chercher l'enregistrement exact dans Redpanda à travers le protocole Kafka. Le log reste le stockage. L'index n'est qu'une carte.2

Cette architecture est moins intéressante comme nouvelle base clé-valeur que comme expérience sur un compromis devenu presque invisible : nous dupliquons énormément de données pour acheter de la vitesse de lecture.

Le log sait conserver une histoire, pas répondre comme un dictionnaire

Kafka est construit autour d'un journal ordonné. Les producteurs ajoutent des enregistrements dans des partitions, et les consommateurs les parcourent par offset. Un topic compacté ajoute une propriété très utile : pour une clé donnée, Kafka peut supprimer les anciennes valeurs et conserver la dernière.4

Cela ressemble déjà beaucoup à une table clé-valeur.

Sauf que le modèle d'accès n'est pas le même.

Le log sait très bien dire : « lis à partir de cet offset ». Il n'est pas conçu comme un moteur de requête qui transforme immédiatement user-42 en « partition 3, offset 918272 ». Pour obtenir cette propriété, les systèmes construisent généralement un état dérivé.

Kafka Streams le fait explicitement. Sa documentation recommande pour la plupart des usages un KeyValueStore persistant basé sur RocksDB. Cet état vit localement sur disque et peut être reconstruit depuis un changelog compacté.3

Ce choix est parfaitement raisonnable. Une copie locale permet de répondre sans aller demander chaque valeur au broker. Elle peut aussi offrir des opérations plus riches et isoler la lecture applicative du chemin réseau.

Mais elle crée une situation amusante : le log contient la valeur, puis le state store contient encore la valeur, parce que la première représentation n'est pas assez pratique pour la seconde tâche.

Sánchez résume le problème de façon plus personnelle : son homelab possède déjà le disque qu'il possède, et il n'a pas envie d'en acheter pour stocker une seconde fois les mêmes octets.1

rpkv part donc dans l'autre direction.

Douze octets de pointeur, puis la vraie facture

Le format logique de l'index est minuscule : une partition sur 32 bits et un offset sur 64 bits. À cela s'ajoutent évidemment la clé, le stockage Pebble, ses métadonnées, ses fichiers de journalisation et ses structures internes.2

Dans le benchmark publié avec le projet, un index fermé contenant un million de clés occupe 14,2 octets par clé en moyenne sur disque dans ce run. Cette empreinte inclut les SSTables, le WAL et les manifests Pebble. Le document précise qu'il s'agit d'un seul run et que l'état de compaction de Pebble peut faire varier le résultat.2

Ce chiffre n'est donc pas une constante physique de rpkv. Il donne plutôt l'ordre de grandeur de l'idée : conserver des pointeurs coûte beaucoup moins que dupliquer de grosses valeurs.

Imaginons des objets de quelques centaines de kilo-octets, des blobs de métadonnées, des documents ou des états sérialisés assez lourds. Plus la valeur grossit, plus l'écart devient intéressant. Un pointeur ne grossit pas avec elle.

Mais la facture n'a pas disparu. Elle a changé de colonne.

Chaque lecture doit maintenant revenir au broker.

Le benchmark de l'auteur, sur une Redpanda locale dockerisée et 5000 lectures aléatoires parmi 100 000 clés contenant des valeurs de 256 octets, mesure 11,1 ms au p50 et 16,9 ms au p99.2

Onze millisecondes peuvent être insignifiantes pour une API d'administration et catastrophiques pour un chemin appelé des dizaines de milliers de fois par seconde. Le README ne cherche d'ailleurs pas à cacher le problème : petites valeurs et lectures très chaudes sont explicitement présentées comme de mauvais cas d'usage.2

C'est ce qui rend le projet utile à regarder. Il ne promet pas de supprimer un coût. Il choisit lequel payer.

Un index jetable change la manière de penser la panne

La seconde idée intéressante est que l'index n'est pas traité comme une nouvelle source de vérité.

On peut supprimer son répertoire et le reconstruire depuis le log. Le projet annonce environ 996 000 clés par seconde dans son benchmark de reconstruction.2

Encore une fois, ce nombre appartient au banc de test de l'auteur, pas à une loi universelle. Sur un autre broker, un autre disque, d'autres tailles de messages ou un stockage distant, le résultat changera.

La propriété conceptuelle est plus importante : l'index est une projection disposable.

Cela retire toute une classe de questions. Il n'y a pas de politique de rétention des valeurs à synchroniser entre le log et une seconde base. Il n'y a pas de mécanisme de réplication propre à rpkv. Chaque instance consomme les topics, maintient son Pebble privé et peut être reconstruite.2

Cette simplicité crée immédiatement une autre limite. Deux replicas peuvent être à des checkpoints différents. Le projet ne garantit donc ni read-your-writes ni des lectures monotones lorsqu'un client passe d'une instance à l'autre. La fraîcheur dépend du retard d'ingestion de chaque replica, exposé comme métrique.2

On économise une réplication spécialisée en acceptant une cohérence plus modeste.

C'est une constante de ce projet : chaque suppression d'infrastructure réapparaît sous forme de compromis explicite.

La compaction rend le pointeur plus subtil qu'il en a l'air

Il y a un piège dans l'idée « clé → offset ».

Kafka peut compacter le log. Une ancienne valeur d'une clé disparaît, tandis que la nouvelle reste à son offset logique d'origine. La documentation Kafka décrit la compaction comme une politique qui retient la dernière valeur pour chaque clé.4

Supposons maintenant que l'index rpkv pointe encore vers l'ancienne version au moment précis où le broker l'a déjà compactée.

Un fetch à cet offset ne doit surtout pas être interprété comme « voici forcément ma valeur ». Le protocole peut faire revenir le prochain enregistrement disponible. rpkv vérifie donc l'offset et la clé de ce qui revient.2

Si le pointeur est dépassé, le serveur attend que son checkpoint d'ingestion franchisse l'offset concerné, résout à nouveau la clé, puis retente la lecture dans un budget total de deux secondes. Si la situation n'est pas résolue, il répond 503 plutôt que de servir silencieusement une mauvaise valeur.2

La rétention produit un cas différent. Si le record pointé est réellement parti parce que la politique delete a déplacé le début du log au-delà de cet offset, rpkv répond 410 Gone.2

Le détail est important parce qu'il casse la version trop jolie du projet. L'index n'est pas juste douze octets et un GET. Une bonne partie du travail consiste précisément à prouver que le pointeur reste interprétable pendant que le stockage sous-jacent bouge.

Le dépôt contient pour cela une suite d'intégration qui génère des milliers d'écrasements sur un topic compacté agressivement, force plusieurs compactions et redémarre rpkv sur le même index avant de vérifier les valeurs et tombstones via son API HTTP.2

On est encore loin d'un historique de production. Mais au moins le prototype attaque sa partie la plus désagréable plutôt que de l'écrire dans une section « future work ».

Pourquoi matérialiser reste souvent la bonne réponse

Il serait facile de transformer cette histoire en petite morale contre RocksDB, les caches et « l'industrie qui gaspille du stockage ».

Ce serait rater le sujet.

La matérialisation existe parce qu'elle achète des propriétés utiles. Une copie locale évite le round-trip vers le broker sur le chemin de lecture. Elle protège le chemin de lecture d'une partie de la charge et des incidents du cluster Kafka. Elle peut indexer autrement, faire des scans de plage, maintenir des agrégats ou servir des requêtes que le log brut n'offre pas.3

Le stockage n'est pas du gaspillage lorsqu'il paie une fonctionnalité dont on a besoin.

Le problème apparaît quand la décision devient automatique.

On voit un log, donc on ajoute un consommateur. Le consommateur écrit dans une base. Puis on ajoute un cache devant la base. Puis une autre copie pour l'analytique. Chaque couche est défendable localement, mais personne ne revient toujours demander si la valeur avait réellement besoin d'être matérialisée à cet endroit.

rpkv est intéressant parce qu'il pose cette question à l'envers : quelle est la plus petite structure supplémentaire qui rend le stockage existant suffisamment interrogeable ?

Dans son cas, la réponse est un index d'adresses et l'acceptation d'un fetch réseau.

Dans un autre système, ce peut être un Bloom filter, une table de métadonnées, un index secondaire ou simplement rien du tout parce que la fréquence de lecture ne justifie pas une nouvelle base.

Le coût du matériel remet de vieilles architectures sur la table

Le billet de Sánchez part d'une contrainte très terre à terre : il ne veut pas acheter plus de disque.1

Cette contrainte paraît presque mesquine au milieu de discussions d'architecture qui supposent volontiers que le stockage est bon marché. Mais c'est souvent ainsi que naissent les systèmes intéressants. Une ressource cesse d'être gratuite dans la tête du développeur, et des choix que l'on ne voyait plus redeviennent visibles.

Redpanda ajoute une autre dimension avec le stockage hiérarchisé : le log peut conserver une partie des données à distance tout en continuant à exposer les offsets à travers l'interface Kafka.5 rpkv a même ajouté un benchmark où les segments locaux sont évincés et les données passent par un stockage objet. L'auteur rapporte environ 10,6 ms p50 à froid dans son environnement MinIO local, résultat suffisamment contre-intuitif pour mériter surtout d'être reproduit ailleurs avant d'en tirer une conclusion.2

Mais l'idée ouvre une question plus large : si le système de référence sait déjà déplacer ses octets entre plusieurs niveaux de stockage, combien de copies applicatives ajoutons-nous encore uniquement parce que notre interface de lecture n'est pas celle que nous voulons ?

La bonne unité n'est peut-être pas le téraoctet économisé

rpkv vient à peine de sortir. Il n'a pas, à notre connaissance, le recul d'exploitation qui permettrait de dire que cette architecture est une bonne idée en production. Ses chiffres sont ceux de son auteur, sur ses scénarios, et son domaine idéal est étroit : grosses valeurs, lectures modestes, environnement où le disque compte réellement.2

Cela suffit pourtant à rendre l'expérience utile.

Parce que son unité de travail n'est pas vraiment le stockage. C'est la duplication.

Chaque fois qu'un système transforme une représentation en une autre, il paie quelque chose : espace, latence, calcul, complexité opérationnelle, cohérence ou disponibilité. La matérialisation déplace le curseur vers l'espace. rpkv le pousse vers la latence de lecture et la dépendance au broker.

Il n'y a pas de version gratuite.

Mais il y a une différence entre payer consciemment et empiler des copies parce que c'est devenu la recette par défaut.

Le projet de Sánchez tient pour l'instant dans ce geste assez sain : avant d'acheter un autre disque, vérifier si l'on avait réellement besoin de recopier ce qu'il contenait déjà.