Backend de Dados
O IronFlock provisiona um banco de dados privado para cada projeto, baseado no TimescaleDB. Sua aplicação define o esquema de dados; o IronFlock cria as tabelas e começa a coletar dados no momento em que um dispositivo é adicionado à aplicação.
Como Funciona
- Defina seu esquema de dados em
.ironflock/data-template.yml. - Use o SDK do IronFlock para publicar dados a partir do seu código de borda.
- O IronFlock configura automaticamente as tabelas do banco de dados em cada projeto onde a aplicação está instalada.
- Os dados fluem dos dispositivos pelo sistema de mensagens até o banco de dados do projeto.
Cada projeto recebe seu próprio banco de dados físico — não há compartilhamento de dados entre projetos.
O usuário tem controle total sobre os dados coletados pela sua aplicação no projeto dele. Como desenvolvedor, você não tem acesso a esses dados.
Definindo o Esquema de Dados
Crie um arquivo data-template.yml no diretório .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: stringOpções de Coluna
| Campo | Descrição |
|---|---|
id | Identificador interno da coluna (use tsp para colunas de timestamp) |
name | Nome legível da coluna exibido nos boards |
description | Descrição opcional |
path | Caminho até o valor no objeto de dados publicado (ex.: args[0].temperature) |
dataType | Um de: timestamp, numeric, string, boolean |
Opções de Tabela
Além de columns, uma tabela aceita algumas chaves opcionais que controlam como ela é descrita e como seus dados envelhecem:
data:
tables:
- tablename: sensordata
description: Leituras ambientais do chão de fábrica
chunkTimeInterval: 1 hour
dropAfter: 30 days
columns:
# ...| Campo | Descrição |
|---|---|
tablename | Nome da tabela |
description | Descrição opcional, exibida na interface e usada por agentes de IA para entender a tabela |
chunkTimeInterval | Tamanho das partições de tempo em que a tabela é dividida. Padrão: 7 days |
dropAfter | Janela de retenção — partições mais antigas que isso são descartadas automaticamente |
downsample | Mantém uma cópia pré-agregada para gráficos rápidos de janela longa — veja Downsampling Contínuo abaixo |
maintainLatestFlagFor | Colunas que identificam uma entidade única — veja Rastreando o Estado Mais Recente de uma Entidade abaixo |
private | Oculta esta tabela dos outros apps — veja Compartilhando Dados com Outros Apps abaixo |
chunkTimeInterval controla como os dados de série temporal são particionados em disco. Escolha-o de modo que uma partição corresponda aproximadamente ao que você consulta de uma vez: dados de alta frequência coletados a cada segundo se beneficiam de chunks pequenos (de minutos a horas), e dados que mudam devagar, de chunks grandes (semanas). Este é apenas o padrão do app — o proprietário do projeto pode ajustá-lo depois em seu próprio data backend.
dropAfter transforma a tabela em uma janela deslizante. Partições inteiras mais antigas que o intervalo informado são descartadas, o que é bem mais barato do que excluir linhas individuais. A rotina de limpeza roda em um ciclo de dropAfter / 4, então um registro pode sobreviver ao seu vencimento por até um quarto do intervalo antes que sua partição seja removida. Omita dropAfter para manter os dados indefinidamente.
Ambos aceitam strings de intervalo do PostgreSQL — 30 minutes, 1 hour, 7 days, 6 months.
Downsampling Contínuo
Os dashboards podem pedir ao banco de dados que agregue os dados — médias horárias, totais diários, contagens por máquina. Calcular isso a partir dos registros brutos é tranquilo para um dia e caro para um ano. Adicione downsample a uma tabela e a plataforma manterá uma cópia pré-agregada dela, continuamente atualizada, e passará a responder às consultas de janela longa a partir dessa cópia:
data:
tables:
- tablename: sensordata
dropAfter: 30 days
downsample:
bucket: 1 minute
keepFor: 2 years
paths:
- payload.temperature
columns:
# ...| Campo | Descrição |
|---|---|
bucket | Granularidade da cópia pré-agregada. Padrão: 1 minute |
keepFor | Por quanto tempo manter o histórico reduzido. Omita para mantê-lo indefinidamente |
paths | Caminhos de campos JSON a incluir, na mesma notação usada pelos dashboards |
bucket é a resolução mais fina a partir da qual um gráfico pode ser servido — um gráfico que peça intervalos bem mais finos que isso lê a tabela bruta. Aceita intervalos de largura fixa de 1 second a 1 day que dividam o dia de forma exata (1 minute, 5 minutes, 1 hour). O padrão de 1 minute atende praticamente qualquer dashboard; um bucket mais grosso custa menos armazenamento e menos throughput de escrita.
keepFor é o que torna os históricos longos possíveis. Os registros brutos desaparecem com dropAfter, mas a cópia reduzida tem sua própria retenção: mantenha os dados brutos por 30 dias e os reduzidos por 2 anos, e um board ainda poderá plotar dois anos de médias horárias usando uma fração do armazenamento. Defina-o maior que dropAfter — a plataforma rejeita o contrário como configuração incorreta.
paths estende o downsampling a valores dentro de colunas JSON. As colunas numéricas são incluídas automaticamente; os campos JSON precisam ser nomeados explicitamente, já que uma coluna JSON não tem um conjunto fixo de chaves. Campos não declarados continuam funcionando nos dashboards — eles simplesmente são calculados a partir da tabela bruta.
Todo o resto é automático. Cada coluna numérica tem suas estatísticas mantidas (média, soma, mínimo, máximo, primeiro, último e uma contagem de registros), agrupadas pela chave de entidade da tabela (maintainLatestFlagFor, ou o dispositivo publicador). Os dashboards não precisam de configuração nem de qualquer consciência disso: um widget consulta como de costume, e a plataforma decide a cada consulta se a cópia pré-agregada consegue respondê-la — recorrendo à tabela bruta de forma transparente quando não consegue, por exemplo quando um filtro referencia uma coluna pela qual a cópia não agrupa.
Mudanças de esquema reconstroem a cópia. Adicionar, remover ou trocar o tipo de uma coluna de uma tabela com downsampling — ou editar o próprio bloco
downsample— reconstrói a cópia pré-agregada a partir da tabela bruta. Tudo que for mais antigo quedropAfternão pode ser reconstruído e é perdido. Configure o bloco junto com a tabela sempre que possível, e trate mudanças posteriores de esquema em tabelas de vida longa como uma decisão deliberada.
Publicando Dados a partir do Código de Borda
Use o SDK do IronFlock para enviar dados da sua aplicação:
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"
})Para dados de alta frequência, envie várias linhas em uma única mensagem em vez de uma ida e volta por linha usando publish_rows_to_table / publishRowsToTable (fire-and-forget) ou append_rows_to_table / appendRowsToTable (retorna o resultado da inserção). Cada lote é inserido atomicamente — tudo ou nada. Consulte a referência do SDK para detalhes.
Tabelas de Transformação
Você pode definir transformações SQL que automaticamente agregam ou processam seus dados brutos:
data:
tables:
- tablename: sensordata
columns:
# ... raw data columns ...
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| Campo | Descrição |
|---|---|
tablename | Nome da tabela derivada |
materialize | Se true, os resultados são persistidos como uma tabela |
schedule | Expressão cron que define quando a transformação é executada |
sql | Consulta SQL que computa a transformação |
columns | Definições de coluna para a saída |
As tabelas de transformação ficam acessíveis nos boards e via SDK, exatamente como as tabelas comuns.
Rastreando o Estado Mais Recente de uma Entidade
Para tabelas que representam o estado atual de entidades do mundo real — máquinas, ativos, ordens de produção — o IronFlock suporta um padrão chamado rastreamento do estado mais recente.
Em vez de sobrescrever uma linha quando algo muda, você sempre acrescenta uma nova linha. Você declara quais colunas identificam uma entidade única, e o IronFlock deriva a linha mais recente de cada entidade sempre que a tabela é lida. Isso lhe dá um histórico completo de cada alteração e, ao mesmo tempo, facilita consultar apenas o estado atual.
Habilite-o em uma tabela com 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 recebe uma lista de colunas que, juntas, identificam uma entidade única. Nada é escrito na própria linha: o IronFlock indexa a tabela por essa chave de entidade mais o timestamp e escolhe a linha mais recente de cada entidade no momento da consulta. Assim, uma linha que chega atrasada ou fora de ordem nunca pode deixar para trás uma marcação desatualizada.
Para consultar apenas os estados atuais das máquinas:
SELECT DISTINCT ON (machinename) *
FROM machineform
ORDER BY machinename, tsp DESCPara visualizar o histórico completo de uma máquina específica:
SELECT * FROM machineform WHERE machinename = 'Assembly-Line-01' ORDER BY tspRaramente você escreve essa consulta à mão. Widgets em um board que se conectam a esta tabela possuem um interruptor latest nas configurações de filtro, de modo que os usuários sempre vejam os valores atuais sem nenhum trabalho extra. A partir do SDK, solicite o mesmo modo adicionando {"latest": true} ao filterAnd — veja getHistory.
Migrando de
latest_flag: versões anteriores do IronFlock armazenavam uma coluna booleana física chamadalatest_flag. Essa coluna não existe mais — o estado atual passou a ser derivado em SQL, o que o mantém correto quando as linhas chegam fora de ordem. Boards e chamadas de SDK existentes que filtram porlatest_flag = truecontinuam funcionando: o IronFlock os reconhece e aplica o modo de estado mais recente. Código novo deve usar o interruptor latest ou a entrada de filtro{"latest": true}.
Exclusão Lógica de Registros
O modelo append-only do IronFlock significa que os registros nunca são fisicamente excluídos. Em vez disso, use uma coluna booleana deleted para marcar um registro como removido. Isso preserva a trilha de auditoria completa ao mesmo tempo em que oculta os registros excluídos dos dashboards.
Adicione uma coluna deleted a qualquer tabela de entidade:
- id: deleted
name: Deleted
dataType: booleanQuando um usuário exclui um registro (por exemplo, via um formulário no board), sua aplicação publica uma nova linha para aquela entidade com deleted: true. Combinado com maintainLatestFlagFor, essa nova linha torna-se o estado mais recente.
Para consultar apenas os registros atuais ativos (não excluídos):
SELECT * FROM (
SELECT DISTINCT ON (machinename) *
FROM machineform
ORDER BY machinename, tsp DESC
) latest
WHERE deleted IS NULL OR deleted = falseA verificação de deleted é executada depois que a linha mais recente de cada máquina foi escolhida. Essa ordem importa: filtrar as linhas excluídas antes faria a linha anterior, não excluída, reaparecer como o estado atual.
Os widgets do board e o SDK aplicam a mesma ordem automaticamente — combine o interruptor latest (ou {"latest": true}) com um filtro de deleted e você obtém exatamente esse comportamento. Os registros excluídos desaparecem do dashboard imediatamente após o envio do formulário, mas permanecem no banco de dados para fins de histórico e auditoria.
Compartilhando Dados com Outros Apps
Seu data backend é privado do seu app: nenhum outro app instalado no projeto enxerga suas tabelas. Duas chaves opcionais em data-template.yml mudam isso.
Para ler os dados de outro app, liste os apps dos quais quer ler em uma seção consumes: de nível superior — ao lado de data:, não dentro dela:
consumes:
- app: machine-monitor
reason: "Calcula o OEE a partir dos fluxos de estado de máquina e contadores do monitor"
data:
tables:
- tablename: oee_results
columns:
# ... as tabelas do seu próprio app, como sempreapp é o nome técnico do app fornecedor, ou "*" (as aspas são obrigatórias) para todos os apps do projeto. reason é exibido ao usuário no diálogo de consentimento — a declaração sozinha não concede nada até que ele aprove.
Para reservar tabelas específicas, marque-as com private: true. Tudo que você define é compartilhável por padrão; uma tabela ou transformação privada nunca aparece no catálogo que os outros apps veem.
data:
tables:
- tablename: measurements # compartilhada (padrão)
columns: [ ... ]
- tablename: calibration_state # interna — nunca visível para outros apps
private: true
columns: [ ... ]O acesso é somente leitura, concedido pelo usuário por projeto e revogável a qualquer momento. Veja Consumindo Dados de Outros Apps para o modelo completo e as chamadas do SDK que leem o histórico e os fluxos ao vivo de um app fornecedor.