Backend danych
IronFlock tworzy prywatną bazę danych dla każdego projektu, obsługiwaną przez TimescaleDB. Twoja aplikacja definiuje schemat danych; IronFlock tworzy tabele i zaczyna zbierać dane w momencie gdy urządzenie jest dodawane do aplikacji.
Jak to działa
- Zdefiniuj swój schemat danych w
.ironflock/data-template.yml. - Użyj SDK IronFlock do publikowania danych z kodu brzegowego.
- IronFlock automatycznie konfiguruje tabele bazy danych w każdym projekcie gdzie aplikacja jest zainstalowana.
- Dane przepływają z urządzeń przez system komunikatów do bazy danych projektu.
Każdy projekt otrzymuje własną fizyczną bazę danych — nie ma współdzielenia danych między projektami.
Użytkownik ma pełną kontrolę nad danymi zbieranymi przez twoją aplikację w swoim projekcie. Jako deweloper nie masz dostępu do tych danych.
Definiowanie schematu danych
Utwórz plik data-template.yml w katalogu .ironflock/:
data:
tables:
- tablename: sensordata
columns:
- id: tsp
name: Timestamp
description: Czas pomiaru
path: args[0].timestamp
dataType: timestamp
- id: temperature
name: Temperature
description: Odczyt temperatury w Celsjuszach
path: args[0].temperature
dataType: numeric
- id: humidity
name: Humidity
description: Procentowa wilgotność względna
path: args[0].humidity
dataType: numeric
- id: device_id
name: Device ID
description: Identyfikator urządzenia źródłowego
path: args[0].device_id
dataType: stringOpcje kolumn
| Pole | Opis |
|---|---|
id | Wewnętrzny identyfikator kolumny (użyj tsp dla kolumn znacznika czasu) |
name | Czytelna dla człowieka nazwa kolumny wyświetlana w panelach |
description | Opcjonalny opis |
path | Ścieżka do wartości w opublikowanym obiekcie danych (np. args[0].temperature) |
dataType | Jeden z: timestamp, numeric, string, boolean |
Opcje tabel
Poza columns tabela przyjmuje kilka opcjonalnych kluczy, które określają, jak jest opisana i jak starzeją się jej dane:
data:
tables:
- tablename: sensordata
description: Odczyty środowiskowe z hali produkcyjnej
chunkTimeInterval: 1 hour
dropAfter: 30 days
columns:
# ...| Pole | Opis |
|---|---|
tablename | Nazwa tabeli |
description | Opcjonalny opis, wyświetlany w interfejsie i wykorzystywany przez agentów AI do zrozumienia tabeli |
chunkTimeInterval | Rozmiar partycji czasowych, na które dzielona jest tabela. Domyślnie 7 days |
dropAfter | Okno retencji — starsze partycje są automatycznie usuwane |
downsample | Utrzymuje wstępnie zagregowaną kopię na potrzeby szybkich wykresów z długim oknem czasowym — zobacz Ciągły downsampling poniżej |
maintainLatestFlagFor | Kolumny identyfikujące unikalną encję — zobacz Śledzenie aktualnego stanu encji poniżej |
private | Ukrywa tę tabelę przed innymi aplikacjami — zobacz Udostępnianie danych innym aplikacjom poniżej |
chunkTimeInterval decyduje o tym, jak dane szeregów czasowych są partycjonowane na dysku. Dobierz go tak, aby jedna partycja odpowiadała mniej więcej temu, co odczytujesz w jednym zapytaniu: dane o wysokiej częstotliwości zbierane co sekundę zyskują na małych chunkach (minuty do godzin), a dane zmieniające się powoli — na dużych (tygodnie). To jedynie wartość domyślna aplikacji — właściciel projektu może ją później zmienić we własnym data backendzie.
dropAfter zamienia tabelę w przesuwane okno. Usuwane są całe partycje starsze niż podany interwał, co jest znacznie tańsze niż kasowanie pojedynczych wierszy. Zadanie czyszczące działa w cyklu dropAfter / 4, więc rekord może przetrwać swój termin ważności nawet o jedną czwartą interwału, zanim jego partycja zostanie usunięta. Pomiń dropAfter, aby przechowywać dane bezterminowo.
Oba przyjmują łańcuchy interwałów PostgreSQL — 30 minutes, 1 hour, 7 days, 6 months.
Ciągły downsampling
Panele mogą poprosić bazę danych o agregację danych — średnie godzinowe, sumy dzienne, liczbę rekordów na maszynę. Obliczanie ich z surowych rekordów jest w porządku dla jednej doby, ale kosztowne dla roku. Dodaj downsample do tabeli, a platforma będzie utrzymywać jej stale aktualizowaną, wstępnie zagregowaną kopię i odpowiadać z niej na zapytania z długim oknem czasowym:
data:
tables:
- tablename: sensordata
dropAfter: 30 days
downsample:
bucket: 1 minute
keepFor: 2 years
paths:
- payload.temperature
columns:
# ...| Pole | Opis |
|---|---|
bucket | Ziarnistość wstępnie zagregowanej kopii. Domyślnie 1 minute |
keepFor | Jak długo przechowywać historię po downsamplingu. Pomiń, aby przechowywać ją bezterminowo |
paths | Ścieżki pól JSON do uwzględnienia, w tej samej notacji, której używają panele |
bucket to najdrobniejsza rozdzielczość, z jakiej może zostać obsłużony wykres — wykres proszący o przedziały znacznie drobniejsze niż ta wartość odczyta zamiast tego tabelę surowych danych. Przyjmuje interwały o stałej szerokości od 1 second do 1 day, które dzielą dobę bez reszty (1 minute, 5 minutes, 1 hour). Domyślna wartość 1 minute odpowiada praktycznie każdemu panelowi; grubszy przedział kosztuje mniej miejsca na dysku i mniejszą przepustowość zapisu.
keepFor jest tym, co w ogóle umożliwia długie historie. Surowe rekordy znikają wraz z dropAfter, ale kopia po downsamplingu ma własną retencję: przechowuj surowe dane przez 30 dni, a dane po downsamplingu przez 2 lata, a panel nadal narysuje dwa lata średnich godzinowych, zajmując ułamek miejsca. Ustaw ją na dłuższą niż dropAfter — sytuację odwrotną platforma odrzuca jako błędną konfigurację.
paths rozszerza downsampling na wartości wewnątrz kolumn JSON. Kolumny liczbowe są uwzględniane automatycznie; pola JSON trzeba wskazać wprost, ponieważ kolumna JSON nie ma stałego zestawu kluczy. Niezadeklarowane pola nadal działają w panelach — są po prostu obliczane z tabeli surowych danych.
Cała reszta dzieje się automatycznie. Dla każdej kolumny liczbowej utrzymywane są statystyki (średnia, suma, minimum, maksimum, pierwsza i ostatnia wartość oraz liczba rekordów), pogrupowane według klucza encji tabeli (maintainLatestFlagFor albo publikujące urządzenie). Panele nie wymagają żadnej konfiguracji ani nawet wiedzy o tym mechanizmie: widżet odpytuje bazę jak zwykle, a platforma decyduje przy każdym zapytaniu, czy wstępnie zagregowana kopia jest w stanie na nie odpowiedzieć — przezroczyście wracając do tabeli surowych danych, gdy nie jest, na przykład gdy filtr odwołuje się do kolumny, według której kopia nie grupuje.
Zmiany schematu przebudowują kopię. Dodanie, usunięcie lub zmiana typu kolumny w tabeli objętej downsamplingiem — albo edycja samego bloku
downsample— powoduje przebudowanie wstępnie zagregowanej kopii z tabeli surowych danych. Wszystkiego, co jest starsze niżdropAfter, nie da się odtworzyć i zostaje bezpowrotnie utracone. Tam, gdzie to możliwe, skonfiguruj ten blok razem z tabelą, a późniejsze zmiany schematu w długo żyjących tabelach traktuj jako świadomą decyzję.
Publikowanie danych z kodu brzegowego
Użyj SDK IronFlock, aby wysyłać dane z twojej aplikacji:
Python
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"
})W przypadku danych o wysokiej częstotliwości wysyłaj wiele wierszy w jednym komunikacie zamiast jednego cyklu na wiersz, używając publish_rows_to_table / publishRowsToTable (wyślij i zapomnij) lub append_rows_to_table / appendRowsToTable (zwraca wynik wstawienia). Każda partia jest wstawiana atomowo — wszystko albo nic. Szczegóły znajdziesz w dokumentacji SDK.
Tabele transformacyjne
Możesz definiować transformacje SQL, które automatycznie agregują lub przetwarzają surowe dane:
data:
tables:
- tablename: sensordata
columns:
# ... kolumny surowych danych ...
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| Pole | Opis |
|---|---|
tablename | Nazwa tabeli pochodnej |
materialize | Jeśli true, wyniki są utrwalane jako tabela |
schedule | Wyrażenie cron określające kiedy uruchamiać transformację |
sql | Zapytanie SQL obliczające transformację |
columns | Definicje kolumn dla danych wyjściowych |
Tabele transformacyjne są dostępne w panelach i przez SDK, tak jak zwykłe tabele.
Śledzenie aktualnego stanu encji
Dla tabel reprezentujących aktualny stan rzeczywistych encji — maszyn, zasobów, zleceń produkcyjnych — IronFlock obsługuje wzorzec zwany śledzeniem najnowszego stanu.
Zamiast nadpisywania wiersza gdy coś się zmienia, zawsze dołączasz nowy wiersz. Deklarujesz, które kolumny identyfikują unikalną encję, a IronFlock wyznacza najnowszy wiersz dla każdej encji przy każdym odczycie tabeli. Daje to pełną historię każdej zmiany, jednocześnie ułatwiając zapytanie tylko o aktualny stan.
Włącz to na tabeli za pomocą 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: stringmaintainLatestFlagFor przyjmuje listę kolumn, które razem identyfikują unikalną encję. Do samego wiersza nie jest przy tym nic zapisywane: IronFlock indeksuje tabelę według tego klucza encji oraz znacznika czasu i przy wykonywaniu zapytania wybiera najnowszy wiersz dla każdej encji. Wiersz, który przyjdzie z opóźnieniem lub poza kolejnością, nigdy nie może więc pozostawić po sobie nieaktualnego oznaczenia.
Aby zapytać tylko o aktualne stany maszyn:
SELECT DISTINCT ON (machinename) *
FROM machineform
ORDER BY machinename, tsp DESCAby wyświetlić pełną historię konkretnej maszyny:
SELECT * FROM machineform WHERE machinename = 'Assembly-Line-01' ORDER BY tspRzadko piszesz to zapytanie ręcznie. Widgety na panelu połączone z tą tabelą mają w ustawieniach filtrów przełącznik latest, dzięki czemu użytkownicy zawsze widzą aktualne wartości bez dodatkowej pracy. Z poziomu SDK zażądasz tego samego trybu, dodając {"latest": true} do filterAnd — zobacz getHistory.
Migracja z
latest_flag: wcześniejsze wersje IronFlock przechowywały fizyczną kolumnę boolean o nazwielatest_flag. Ta kolumna już nie istnieje — aktualny stan jest zamiast tego wyznaczany w SQL, co utrzymuje jego poprawność, gdy wiersze przychodzą poza kolejnością. Istniejące panele i wywołania SDK, które filtrują polatest_flag = true, działają nadal: IronFlock je rozpoznaje i stosuje tryb najnowszego stanu. Nowy kod powinien używać przełącznika latest lub wpisu filtra{"latest": true}.
Miękkie usuwanie rekordów
Model tylko-do-dołączania IronFlock oznacza, że rekordy nigdy nie są fizycznie usuwane. Zamiast tego użyj kolumny boolean deleted, aby oznaczyć rekord jako usunięty. Zachowuje to pełny ślad audytu, jednocześnie ukrywając usunięte rekordy przed panelami.
Dodaj kolumnę deleted do dowolnej tabeli encji:
- id: deleted
name: Deleted
dataType: booleanAby zapytać tylko o aktywne (nie usunięte) aktualne rekordy:
SELECT * FROM (
SELECT DISTINCT ON (machinename) *
FROM machineform
ORDER BY machinename, tsp DESC
) latest
WHERE deleted IS NULL OR deleted = falseSprawdzenie kolumny deleted jest wykonywane po wybraniu najnowszego wiersza dla każdej maszyny. Ta kolejność ma znaczenie: odfiltrowanie usuniętych wierszy w pierwszej kolejności sprawiłoby, że poprzedni, nieusunięty wiersz pojawiłby się ponownie jako aktualny stan.
Widgety na panelach oraz SDK stosują tę samą kolejność automatycznie — połącz przełącznik latest (lub {"latest": true}) z filtrem na kolumnie deleted, a otrzymasz dokładnie takie zachowanie. Usunięte rekordy znikają z panelu natychmiast po wysłaniu formularza, ale pozostają w bazie danych na potrzeby historii i audytu.
Udostępnianie danych innym aplikacjom
Twój data backend należy wyłącznie do Twojej aplikacji: żadna inna aplikacja zainstalowana w projekcie nie widzi Twoich tabel. Zmieniają to dwa opcjonalne klucze w data-template.yml.
Aby czytać dane innej aplikacji, wypisz aplikacje, z których chcesz czytać, w sekcji consumes: na najwyższym poziomie — obok data:, a nie w jej wnętrzu:
consumes:
- app: machine-monitor
reason: "Oblicza OEE na podstawie strumieni stanu maszyn i liczników z monitora"
data:
tables:
- tablename: oee_results
columns:
# ... własne tabele Twojej aplikacji, jak zwykleapp to techniczna nazwa aplikacji udostępniającej albo "*" (cudzysłowy są wymagane) dla wszystkich aplikacji w projekcie. reason jest pokazywany użytkownikowi w oknie zgody — sama deklaracja niczego nie przyznaje, dopóki użytkownik jej nie zatwierdzi.
Aby zachować wybrane tabele dla siebie, oznacz je private: true. Wszystko, co zdefiniujesz, jest domyślnie udostępnialne; prywatna tabela lub transformacja w ogóle nie pojawia się w katalogu widzianym przez inne aplikacje.
data:
tables:
- tablename: measurements # udostępniana (domyślnie)
columns: [ ... ]
- tablename: calibration_state # wewnętrzna — nigdy niewidoczna dla innych aplikacji
private: true
columns: [ ... ]Dostęp jest wyłącznie do odczytu, przyznawany przez użytkownika w obrębie projektu i w każdej chwili odwoływalny. Pełny model oraz wywołania SDK odczytujące historię i strumienie na żywo aplikacji udostępniającej opisano w Korzystanie z danych innych aplikacji.