Contexte et problème initial

Logfire ingère les traces OpenTelemetry sous forme semi‑structurée. Chaque span comporte des champs fixes (timestamp, span_name, etc.) et un champ attributes contenant un JSON libre. Au départ, ces attributs étaient stockés dans une colonne Utf8View (JSON) de fichiers Parquet. Cette approche, bien que simple, imposait le téléchargement complet du blob JSON pour chaque requête, ce qui, sur un stockage objet, générait un temps d'IO dominant. Les requêtes dépassant 30 s aboutissaient à des time‑outs, alors que le CPU était sous‑exploité.

Architecture de stockage initiale

Le schéma Parquet utilisé était :

start_timestamp: Timestamp(Microsecond, "+00:00")
end_timestamp: Timestamp(Microsecond, "+00:00")
span_name: Utf8View
message: Utf8View
trace_id: FixedSizeBinary(16)
span_id: FixedSizeBinary(8)
parent_span_id: FixedSizeBinary(8)
attributes: Utf8View (JSON)
otel_resource_attributes: Utf8View (JSON)

Cette disposition permettait aux moteurs de requête de ne lire que les colonnes nécessaires, mais la colonne attributes restait opaque. La compression était inefficace : chaque clé/valeur variable empêchait les dictionnaires Parquet et même le codec ZSTD peinait à réduire la taille du texte JSON, augmentant ainsi le volume de données transférées.

Shredding dynamique des attributs

Logfire a introduit un « shredding » dynamique qui transforme chaque clé d'attribut en une colonne dédiée au moment de l'ingestion. Contrairement au shredding statique, qui ne ciblait qu'un petit sous‑ensemble (par ex. http.status_code, http.method), le processus dynamique analyse le JSON, crée des colonnes _lf_attributes_key typées (entier, chaîne, booléen) et les ajoute au schéma Parquet. Ainsi, une requête filtrant sur _lf_attributes_http.status_code = 200 ne lit que la colonne correspondante, et les zone‑maps de Parquet permettent d'ignorer des groupes de lignes dont les statistiques indiquent l'absence de la valeur recherchée.

Impact sur les performances et limites

Les premiers tests internes montrent des gains allant jusqu'à 1000× : des requêtes qui mettaient plus de 30 s sont exécutées en moins d'une seconde. Le facteur principal est la réduction du trafic réseau ; le moteur ne télécharge plus le blob complet mais uniquement les colonnes ciblées, limitant le temps de latence de démarrage des téléchargements depuis l'object storage. Le coût additionnel réside dans la génération de métadonnées lors de l'écriture : chaque nouvelle clé crée une colonne, ce qui augmente le nombre de colonnes et peut impacter la vitesse d'ingestion et la taille du méta‑schéma. De plus, la présence de colonnes très peu utilisées peut entraîner un gaspillage d'espace disque si la compression n'est pas suffisante. Logfire prévoit de gérer ces effets en appliquant des politiques de rétention de colonnes peu sollicitées et en combinant le shredding dynamique avec des index secondaires pour les attributs très fréquents.