Skip to Content

Backend de données

IronFlock provisionne une base de données privée pour chaque projet, propulsée par TimescaleDB. Votre application définit le schéma de données ; IronFlock crée les tables et commence à collecter les données dès qu’un appareil est ajouté à l’application.

Fonctionnement

  1. Définissez votre schéma de données dans .ironflock/data-template.yml.
  2. Utilisez le SDK IronFlock pour publier des données depuis votre code edge.
  3. IronFlock configure automatiquement les tables de base de données dans chaque projet où l’application est installée.
  4. Les données circulent des appareils via le système de messagerie vers la base de données du projet.

Chaque projet dispose de sa propre base de données physique — il n’y a aucun partage de données entre les projets.

L’utilisateur a le contrôle total sur les données collectées par votre application dans son projet. En tant que développeur, vous n’avez pas accès à ces données.

Définir le schéma de données

Créez un fichier data-template.yml dans le répertoire .ironflock/ :

data: tables: - tablename: sensordata columns: - id: tsp name: Timestamp description: Timestamp of measurement path: args[0].timestamp dataType: timestamp - id: temperature name: Temperature description: Temperature reading in Celsius path: args[0].temperature dataType: numeric - id: humidity name: Humidity description: Relative humidity percentage path: args[0].humidity dataType: numeric - id: device_id name: Device ID description: Source device identifier path: args[0].device_id dataType: string

Options des colonnes

ChampDescription
idIdentifiant interne de la colonne (utilisez tsp pour les colonnes timestamp)
nameNom de colonne lisible affiché dans les boards
descriptionDescription optionnelle
pathChemin vers la valeur dans l’objet de données publié (p. ex. args[0].temperature)
dataTypeL’un des types : timestamp, numeric, string, boolean

Options des tables

Outre columns, une table accepte quelques clés optionnelles qui contrôlent la façon dont elle est décrite et dont ses données vieillissent :

data: tables: - tablename: sensordata description: Relevés environnementaux de l'atelier chunkTimeInterval: 1 hour dropAfter: 30 days columns: # ...
ChampDescription
tablenameNom de la table
descriptionDescription optionnelle, affichée dans l’interface et utilisée par les agents IA pour comprendre la table
chunkTimeIntervalTaille des partitions temporelles dans lesquelles la table est découpée. Par défaut 7 days
dropAfterFenêtre de rétention — les partitions plus anciennes sont supprimées automatiquement
downsampleMaintient une copie pré-agrégée pour des graphiques rapides sur de longues fenêtres — voir Sous-échantillonnage continu plus bas
maintainLatestFlagForColonnes identifiant une entité unique — voir Suivi de l’état actuel d’une entité plus bas
privateMasque cette table aux autres applications — voir Partager des données avec d’autres applications plus bas

chunkTimeInterval détermine la façon dont les données de séries temporelles sont partitionnées sur le disque. Choisissez-le pour qu’une partition corresponde à peu près à ce que vous interrogez en une fois : les données à haute fréquence collectées chaque seconde bénéficient de petits chunks (de quelques minutes à quelques heures), les données qui évoluent lentement de grands chunks (des semaines). Ce n’est que la valeur par défaut de l’application — le propriétaire du projet peut l’ajuster ensuite sur son propre backend de données.

dropAfter transforme la table en fenêtre glissante. Des partitions entières plus anciennes que l’intervalle indiqué sont supprimées, ce qui est bien moins coûteux que de supprimer des lignes une à une. La tâche de nettoyage s’exécute selon un rythme de dropAfter / 4 : un enregistrement peut donc survivre à son expiration pendant un quart de l’intervalle au maximum, avant que sa partition ne disparaisse. Omettez dropAfter pour conserver les données indéfiniment.

Les deux acceptent des chaînes d’intervalle PostgreSQL — 30 minutes, 1 hour, 7 days, 6 months.

Sous-échantillonnage continu

Les tableaux de bord peuvent demander à la base de données d’agréger les données — moyennes horaires, totaux journaliers, nombres d’enregistrements par machine. Calculer cela à partir des enregistrements bruts est acceptable sur une journée et coûteux sur une année. Ajoutez downsample à une table et la plateforme en maintient une copie pré-agrégée, continuellement mise à jour, puis répond aux requêtes portant sur de longues fenêtres à partir de cette copie :

data: tables: - tablename: sensordata dropAfter: 30 days downsample: bucket: 1 minute keepFor: 2 years paths: - payload.temperature columns: # ...
ChampDescription
bucketGranularité de la copie pré-agrégée. Par défaut 1 minute
keepForDurée de conservation de l’historique sous-échantillonné. Omettez-le pour le conserver indéfiniment
pathsChemins de champs JSON à inclure, dans la même notation que celle des tableaux de bord

bucket est la résolution la plus fine à partir de laquelle un graphique peut être servi — un graphique demandant des intervalles nettement plus fins lit la table brute à la place. Il accepte des intervalles de largeur fixe de 1 second à 1 day qui divisent une journée de façon régulière (1 minute, 5 minutes, 1 hour). La valeur par défaut de 1 minute convient à pratiquement tous les tableaux de bord ; un intervalle plus grossier coûte moins de stockage et moins de débit en écriture.

keepFor est ce qui rend les historiques longs possibles. Les enregistrements bruts disparaissent avec dropAfter, mais la copie sous-échantillonnée a sa propre rétention : conservez les données brutes 30 jours et les données sous-échantillonnées 2 ans, et un board pourra toujours tracer deux ans de moyennes horaires pour une fraction du stockage. Définissez-le plus long que dropAfter — la plateforme refuse l’inverse comme une erreur de configuration.

paths étend le sous-échantillonnage aux valeurs situées à l’intérieur de colonnes JSON. Les colonnes numériques sont incluses automatiquement ; les champs JSON doivent être nommés explicitement, car une colonne JSON n’a pas d’ensemble de clés fixe. Les champs non déclarés fonctionnent quand même dans les tableaux de bord — ils sont simplement calculés à partir de la table brute.

Tout le reste est automatique. Chaque colonne numérique voit ses statistiques maintenues (moyenne, somme, minimum, maximum, première et dernière valeur, ainsi qu’un nombre d’enregistrements), groupées par la clé d’entité de la table (maintainLatestFlagFor, ou l’appareil publiant). Les tableaux de bord n’ont besoin d’aucune configuration ni même d’en avoir connaissance : un widget interroge comme d’habitude, et la plateforme décide requête par requête si la copie pré-agrégée peut y répondre — en se rabattant de façon transparente sur la table brute lorsque ce n’est pas le cas, par exemple quand un filtre porte sur une colonne selon laquelle la copie ne regroupe pas.

Les changements de schéma reconstruisent la copie. Ajouter, supprimer ou retyper une colonne d’une table sous-échantillonnée — ou modifier le bloc downsample lui-même — reconstruit la copie pré-agrégée à partir de la table brute. Tout ce qui est plus ancien que dropAfter ne peut pas être reconstitué et est perdu. Mettez ce bloc en place en même temps que la table lorsque c’est possible, et traitez les changements de schéma ultérieurs sur des tables à longue durée de vie comme une décision délibérée.

Publier des données depuis le code edge

Utilisez le SDK IronFlock pour envoyer des données depuis votre application :

from ironflock import IronFlock flock = IronFlock() flock.publish_to_table("sensordata", { "timestamp": "2025-01-15T10:30:00Z", "temperature": 23.5, "humidity": 62.1, "device_id": "sensor-001" })

Pour les données à haute fréquence, envoyez plusieurs lignes dans un seul message au lieu d’un aller-retour par ligne en utilisant publish_rows_to_table / publishRowsToTable (fire-and-forget) ou append_rows_to_table / appendRowsToTable (qui retourne le résultat de l’insertion). Chaque lot est inséré de manière atomique — tout ou rien. Consultez la référence du SDK pour plus de détails.

Tables de transformation

Vous pouvez définir des transformations SQL qui agrègent ou traitent automatiquement vos données brutes :

data: tables: - tablename: sensordata columns: # ... colonnes de données brutes ... transforms: - tablename: hourly_averages materialize: true schedule: "0 * * * *" sql: > SELECT time_bucket('1 hour', tsp) AS hour, avg(temperature) AS avg_temp, avg(humidity) AS avg_humidity FROM sensordata GROUP BY hour columns: - id: hour name: Hour dataType: timestamp - id: avg_temp name: Average Temperature dataType: numeric - id: avg_humidity name: Average Humidity dataType: numeric
ChampDescription
tablenameNom de la table dérivée
materializeSi true, les résultats sont persistés sous forme de table
scheduleExpression cron indiquant quand la transformation s’exécute
sqlRequête SQL qui calcule la transformation
columnsDéfinitions des colonnes de sortie

Les tables de transformation sont accessibles dans les boards et via le SDK, exactement comme les tables ordinaires.

Suivi de l’état actuel d’une entité

Pour les tables représentant l’état actuel d’entités réelles — machines, actifs, ordres de production — IronFlock supporte un modèle appelé suivi de l’état le plus récent.

Au lieu d’écraser une ligne lorsqu’une valeur change, vous ajoutez toujours une nouvelle ligne. Vous déclarez quelles colonnes identifient une entité unique, et IronFlock en déduit la ligne la plus récente par entité à chaque lecture de la table. Vous obtenez ainsi l’historique complet de chaque modification tout en pouvant interroger facilement le seul état actuel.

Activez-le sur une table avec maintainLatestFlagFor :

- tablename: machineform maintainLatestFlagFor: ['machinename'] columns: - id: tsp dataType: timestamp - id: machinename dataType: string - id: machinetype dataType: string - id: active dataType: boolean - id: description dataType: string

maintainLatestFlagFor prend une liste de colonnes identifiant une entité unique. Rien n’est écrit dans la ligne elle-même : IronFlock indexe la table selon cette clé d’entité et l’horodatage, puis sélectionne la ligne la plus récente de chaque entité au moment de la requête. Une ligne qui arrive en retard ou dans le désordre ne peut donc jamais laisser derrière elle un marqueur obsolète.

Pour n’interroger que l’état actuel des machines :

SELECT DISTINCT ON (machinename) * FROM machineform ORDER BY machinename, tsp DESC

Pour consulter l’historique complet d’une machine donnée :

SELECT * FROM machineform WHERE machinename = 'Assembly-Line-01' ORDER BY tsp

Vous écrivez rarement cette requête à la main. Les widgets d’un board connectés à cette table disposent d’un commutateur latest dans leurs paramètres de filtre, de sorte que les utilisateurs voient toujours les valeurs actuelles sans effort supplémentaire. Depuis le SDK, activez le même mode en ajoutant {"latest": true} à filterAnd — voir getHistory.

Migration depuis latest_flag : les versions précédentes d’IronFlock stockaient une colonne booléenne physique nommée latest_flag. Cette colonne n’existe plus — l’état actuel est désormais dérivé en SQL, ce qui le garde correct lorsque les lignes arrivent dans le désordre. Les boards et les appels SDK existants qui filtrent sur latest_flag = true continuent de fonctionner : IronFlock les reconnaît et applique le mode « état le plus récent ». Le nouveau code doit utiliser l’option latest ou l’entrée de filtre {"latest": true}.

Suppression logique des enregistrements

Le modèle append-only d’IronFlock signifie que les enregistrements ne sont jamais physiquement supprimés. Utilisez plutôt une colonne booléenne deleted pour marquer un enregistrement comme supprimé. Cela préserve la piste d’audit complète tout en masquant les enregistrements supprimés dans les dashboards.

Ajoutez une colonne deleted à n’importe quelle table d’entités :

- id: deleted name: Deleted dataType: boolean

Lorsqu’un utilisateur supprime un enregistrement (par exemple via un formulaire sur le board), votre application publie une nouvelle ligne pour cette entité avec deleted: true. Combinée à maintainLatestFlagFor, cette nouvelle ligne devient l’état le plus récent.

Pour ne consulter que les enregistrements actuels et actifs (non supprimés) :

SELECT * FROM ( SELECT DISTINCT ON (machinename) * FROM machineform ORDER BY machinename, tsp DESC ) latest WHERE deleted IS NULL OR deleted = false

Le contrôle sur deleted s’applique après la sélection de la ligne la plus récente de chaque machine. Cet ordre est important : filtrer d’abord les lignes supprimées ferait remonter la ligne précédente, non supprimée, comme état actuel.

Les widgets des boards et le SDK appliquent automatiquement le même ordre — combinez le commutateur latest (ou {"latest": true}) avec un filtre sur deleted et vous obtenez exactement ce comportement. Les enregistrements supprimés disparaissent du dashboard dès l’envoi du formulaire, mais restent dans la base de données à des fins d’historique et d’audit.

Partager des données avec d’autres applications

Votre data backend appartient à votre seule application : aucune autre application installée dans le projet ne voit vos tables. Deux clés optionnelles de data-template.yml changent cela.

Pour lire les données d’une autre application, listez les applications concernées dans une section consumes: de premier niveau — à côté de data:, et non à l’intérieur :

consumes: - app: machine-monitor reason: "Calcule le TRS à partir des flux d'état machine et de compteurs du moniteur" data: tables: - tablename: oee_results columns: # ... les tables propres à votre application, comme d'habitude

app est le nom technique de l’application fournisseuse, ou "*" (les guillemets sont obligatoires) pour toutes les applications du projet. reason est affichée à l’utilisateur dans la boîte de dialogue de consentement — la déclaration seule n’accorde rien tant qu’il n’a pas approuvé.

Pour garder certaines tables privées, marquez-les private: true. Tout ce que vous définissez est partageable par défaut ; une table ou une transformation privée n’apparaît jamais dans le catalogue que voient les autres applications.

data: tables: - tablename: measurements # partagée (par défaut) columns: [ ... ] - tablename: calibration_state # interne — jamais visible par les autres applications private: true columns: [ ... ]

L’accès est en lecture seule, accordé par l’utilisateur projet par projet, et révocable à tout moment. Voir Consommer les données d’autres applications pour le modèle complet et les appels SDK qui lisent l’historique et les flux en direct d’une application fournisseuse.

Last updated on