Skip to Content
IoT 앱 개발데이터 백엔드

데이터 백엔드

IronFlock은 TimescaleDB를 기반으로 각 프로젝트에 전용 데이터베이스를 프로비저닝합니다. 앱이 데이터 스키마를 정의하면 IronFlock이 테이블을 생성하고 디바이스가 앱에 추가되는 순간부터 데이터 수집을 시작합니다.

작동 방식

  1. .ironflock/data-template.yml에 데이터 스키마를 정의합니다.
  2. IronFlock SDK를 사용하여 엣지 코드에서 데이터를 발행합니다.
  3. IronFlock이 앱이 설치된 각 프로젝트에서 자동으로 데이터베이스 테이블을 설정합니다.
  4. 데이터는 디바이스에서 메시징 시스템을 통해 프로젝트 데이터베이스로 흐릅니다.

각 프로젝트는 자체 물리적 데이터베이스를 가집니다 — 프로젝트 간 데이터 공유는 없습니다.

사용자는 자신의 프로젝트에서 앱이 수집하는 데이터를 완전히 제어합니다. 개발자는 이 데이터에 접근할 수 없습니다.

데이터 스키마 정의

.ironflock/ 디렉터리에 data-template.yml 파일을 생성합니다:

data: tables: - tablename: sensordata columns: - id: tsp name: 타임스탬프 description: 측정 타임스탬프 path: args[0].timestamp dataType: timestamp - id: temperature name: 온도 description: 섭씨 온도 측정값 path: args[0].temperature dataType: numeric - id: humidity name: 습도 description: 상대 습도 백분율 path: args[0].humidity dataType: numeric - id: device_id name: 디바이스 ID description: 소스 디바이스 식별자 path: args[0].device_id dataType: string

컬럼 옵션

필드설명
id내부 컬럼 식별자 (타임스탬프 컬럼에는 tsp 사용)
name보드에 표시되는 사람이 읽을 수 있는 컬럼 이름
description선택적 설명
path발행된 데이터 객체에서 값의 경로 (예: args[0].temperature)
dataTypetimestamp, numeric, string, boolean 중 하나

테이블 옵션

columns 외에도 테이블을 어떻게 설명하고 데이터를 얼마나 오래 보관할지 제어하는 몇 가지 선택적 키가 있습니다.

data: tables: - tablename: sensordata description: 생산 현장의 환경 측정값 chunkTimeInterval: 1 hour dropAfter: 30 days columns: # ...
필드설명
tablename테이블 이름
description선택적 설명. UI에 표시되며 AI 에이전트가 테이블을 이해하는 데 사용됩니다
chunkTimeInterval테이블이 나뉘는 시간 파티션의 크기. 기본값은 7 days
dropAfter보존 기간 — 이보다 오래된 파티션은 자동으로 삭제됩니다
downsample긴 시간 창 차트를 빠르게 처리하기 위해 사전 집계된 사본을 유지합니다 — 아래 연속 다운샘플링 참고
maintainLatestFlagFor고유 엔티티를 식별하는 컬럼 — 아래 엔티티의 최신 상태 추적 참고
private이 테이블을 다른 앱에서 숨깁니다 — 아래 다른 앱과 데이터 공유하기 참고

**chunkTimeInterval**은 시계열 데이터를 디스크에서 어떻게 분할할지 결정합니다. 파티션 하나가 한 번에 조회하는 데이터량과 대략 일치하도록 선택하세요. 매초 수집되는 고빈도 데이터는 작은 청크(분~시간 단위)가, 천천히 변하는 데이터는 큰 청크(주 단위)가 적합합니다. 이는 앱의 기본값일 뿐이며, 프로젝트 소유자가 나중에 자신의 데이터 백엔드에서 조정할 수 있습니다.

**dropAfter**는 테이블을 롤링 윈도우로 만듭니다. 지정한 기간보다 오래된 파티션이 통째로 삭제되므로 개별 행을 지우는 것보다 훨씬 저렴합니다. 정리 작업은 dropAfter / 4 주기로 실행되므로, 레코드는 파티션이 제거되기 전까지 만료 시점을 최대 해당 기간의 1/4만큼 넘겨 남아 있을 수 있습니다. 데이터를 무기한 보관하려면 dropAfter를 생략하세요.

두 값 모두 PostgreSQL 인터벌 문자열을 받습니다 — 30 minutes, 1 hour, 7 days, 6 months.

연속 다운샘플링

대시보드는 데이터베이스에 데이터 집계를 요청할 수 있습니다 — 시간별 평균, 일별 합계, 기계별 개수 등. 이를 원시 레코드에서 계산하는 것은 하루치라면 괜찮지만 1년치라면 비용이 큽니다. 테이블에 downsample을 추가하면 플랫폼이 지속적으로 갱신되는 사전 집계된 사본을 유지하고, 긴 시간 창 쿼리를 원시 데이터 대신 그 사본으로 응답합니다:

data: tables: - tablename: sensordata dropAfter: 30 days downsample: bucket: 1 minute keepFor: 2 years paths: - payload.temperature columns: # ...
필드설명
bucket사전 집계된 사본의 세분성. 기본값은 1 minute
keepFor다운샘플링된 이력을 보관할 기간. 생략하면 무기한 보관
paths포함할 JSON 필드 경로. 대시보드에서 사용하는 것과 동일한 표기법

**bucket**은 차트에 제공할 수 있는 가장 미세한 해상도입니다 — 이보다 훨씬 미세한 버킷을 요청하는 차트는 대신 원시 테이블을 읽습니다. 하루를 균등하게 나누는 1 second부터 1 day까지의 고정 폭 인터벌(1 minute, 5 minutes, 1 hour)을 받습니다. 기본값인 1 minute은 사실상 모든 대시보드에 적합하며, 버킷을 더 크게 잡으면 스토리지와 쓰기 처리량이 줄어듭니다.

**keepFor**는 긴 이력을 애초에 가능하게 해주는 값입니다. 원시 레코드는 dropAfter에 따라 사라지지만 다운샘플링된 사본은 자체 보존 기간을 가집니다. 원시 데이터를 30일, 다운샘플링된 데이터를 2년 보관하면, 보드는 스토리지의 일부만 사용하고도 2년치 시간별 평균을 계속 차트로 보여줄 수 있습니다. dropAfter보다 길게 설정하세요 — 그 반대는 플랫폼이 잘못된 구성으로 간주하여 거부합니다.

**paths**는 다운샘플링을 JSON 컬럼 내부의 값까지 확장합니다. 숫자 컬럼은 자동으로 포함되지만, JSON 컬럼에는 고정된 키 집합이 없으므로 JSON 필드는 명시적으로 지정해야 합니다. 선언하지 않은 필드도 대시보드에서 그대로 작동합니다 — 단지 원시 테이블에서 계산될 뿐입니다.

그 밖의 모든 것은 자동입니다. 모든 숫자 컬럼에 대해 통계(평균, 합계, 최솟값, 최댓값, 첫 번째 값, 마지막 값, 레코드 수)가 유지되며, 테이블의 엔티티 키(maintainLatestFlagFor, 없으면 발행 디바이스)로 그룹화됩니다. 대시보드는 아무런 설정도 필요 없고 이 기능을 인지할 필요도 없습니다: 위젯은 평소처럼 쿼리하고, 플랫폼이 쿼리마다 사전 집계된 사본으로 응답할 수 있는지 판단합니다 — 예를 들어 사본이 그룹으로 묶지 않는 컬럼을 필터가 참조하는 등 응답할 수 없는 경우에는 투명하게 원시 테이블로 되돌아갑니다.

스키마 변경은 사본을 재구축합니다. 다운샘플링된 테이블의 컬럼을 추가·제거하거나 타입을 바꾸는 것 — 또는 downsample 블록 자체를 수정하는 것 — 은 원시 테이블에서 사전 집계된 사본을 재구축합니다. dropAfter보다 오래된 것은 재구성할 수 없으므로 소실됩니다. 가능하다면 테이블을 만들 때 이 블록도 함께 설정하고, 오래 운영된 테이블에 대한 이후의 스키마 변경은 신중한 결정으로 다루십시오.

엣지 코드에서 데이터 발행

IronFlock SDK를 사용하여 앱에서 데이터를 전송합니다:

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" })

고빈도 데이터의 경우, 행마다 한 번씩 왕복하는 대신 publish_rows_to_table / publishRowsToTable(fire-and-forget) 또는 append_rows_to_table / appendRowsToTable(삽입 결과를 반환)을 사용하여 단일 메시지로 여러 행을 전송하십시오. 각 배치는 원자적으로 삽입됩니다 — 전부 아니면 전무입니다. 자세한 내용은 SDK 참조를 참조하십시오.

변환 테이블

원시 데이터를 자동으로 집계하거나 처리하는 SQL 변환을 정의할 수 있습니다:

data: tables: - tablename: sensordata 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: 시간 dataType: timestamp - id: avg_temp name: 평균 온도 dataType: numeric - id: avg_humidity name: 평균 습도 dataType: numeric
필드설명
tablename파생 테이블의 이름
materializetrue이면 결과가 테이블로 저장됨
schedule변환이 실행되는 크론 표현식
sql변환을 계산하는 SQL 쿼리
columns출력의 컬럼 정의

변환 테이블은 일반 테이블과 마찬가지로 보드 및 SDK를 통해 접근할 수 있습니다.

엔티티의 최신 상태 추적

실제 엔티티(기계, 자산, 생산 주문)의 현재 상태를 나타내는 테이블의 경우 IronFlock은 **최신 상태 추적(latest-state tracking)**이라는 패턴을 지원합니다.

무언가가 변경될 때 행을 덮어쓰는 대신 항상 새 행을 추가합니다. 고유한 엔티티를 식별하는 컬럼을 선언해 두면, IronFlock이 테이블을 읽을 때마다 엔티티별로 가장 최근 행을 도출합니다. 이를 통해 모든 변경의 전체 이력을 보유하면서 현재 상태만 쿼리하는 것도 쉽게 할 수 있습니다.

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는 고유한 엔티티를 함께 식별하는 컬럼 목록을 받습니다. 행 자체에는 아무것도 기록되지 않습니다. IronFlock은 해당 엔티티 키와 타임스탬프로 테이블을 인덱싱하고, 쿼리 시점에 엔티티별로 가장 최근 행을 선택합니다. 따라서 늦게 도착하거나 순서가 뒤바뀐 채 도착한 행이 오래된 표시를 남기는 일은 결코 발생하지 않습니다.

현재 기계 상태만 쿼리하려면:

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

특정 기계의 전체 이력을 보려면:

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

이 쿼리를 직접 작성할 일은 거의 없습니다. 이 테이블에 연결된 보드의 위젯에는 필터 설정에 latest 토글이 있어 추가 작업 없이 사용자가 항상 현재 값을 볼 수 있습니다. SDK에서는 filterAnd{"latest": true}를 추가하여 동일한 모드를 요청합니다 — getHistory를 참조하십시오.

latest_flag에서 업그레이드: 이전 버전의 IronFlock은 latest_flag라는 물리적 boolean 컬럼을 저장했습니다. 이 컬럼은 더 이상 존재하지 않습니다 — 대신 현재 상태를 SQL에서 도출하므로 행이 순서에 어긋나게 도착해도 결과가 정확하게 유지됩니다. latest_flag = true로 필터링하는 기존 보드와 SDK 호출은 계속 작동합니다: IronFlock이 이를 인식하여 최신 상태 모드를 적용합니다. 새 코드에서는 latest 토글 또는 {"latest": true} 필터 항목을 사용해야 합니다.

레코드 소프트 삭제

IronFlock의 추가 전용 모델에서는 레코드가 물리적으로 삭제되지 않습니다. 대신 deleted boolean 컬럼을 사용하여 레코드를 삭제된 것으로 표시합니다. 이는 전체 감사 추적을 보존하면서 삭제된 레코드를 대시보드에서 숨깁니다.

모든 엔티티 테이블에 deleted 컬럼을 추가합니다:

- id: deleted name: 삭제됨 dataType: boolean

사용자가 레코드를 삭제할 때(예: 보드의 양식을 통해) 앱은 해당 엔티티에 대해 deleted: true가 포함된 새 행을 발행합니다. maintainLatestFlagFor와 함께 사용하면 이 새 행이 최신 상태가 됩니다.

활성(삭제되지 않은) 현재 레코드만 쿼리하려면:

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

deleted 검사는 기계별 최신 행이 선택된 다음에 실행됩니다. 이 순서가 중요합니다: 삭제된 행을 먼저 걸러내면 이전의 삭제되지 않은 행이 현재 상태로 다시 나타나게 됩니다.

보드 위젯과 SDK는 동일한 순서를 자동으로 적용합니다 — latest 토글(또는 {"latest": true})을 deleted 필터와 조합하면 정확히 이 동작이 됩니다. 양식 제출 직후 삭제된 레코드가 대시보드에서 즉시 사라지지만, 이력 및 감사 목적으로 데이터베이스에는 남아 있습니다.

다른 앱과 데이터 공유하기

데이터 백엔드는 앱 전용입니다. 프로젝트에 설치된 다른 어떤 앱도 여러분의 테이블을 볼 수 없습니다. data-template.yml의 선택적 키 두 개가 이를 바꿉니다.

다른 앱의 데이터를 읽으려면, 읽어올 앱들을 최상위 consumes: 섹션에 나열합니다. data: 안이 아니라 그 옆에 둡니다.

consumes: - app: machine-monitor reason: "모니터의 기계 상태 및 카운터 스트림에서 OEE를 계산합니다" data: tables: - tablename: oee_results columns: # ... 평소와 같이 앱 자체 테이블

app은 제공 앱의 기술 이름이거나, 프로젝트 내 모든 앱을 의미하는 "*"(따옴표 필수)입니다. reason은 동의 대화상자에서 사용자에게 표시됩니다. 선언만으로는 아무것도 허용되지 않으며 사용자의 승인이 있어야 합니다.

개별 테이블을 비공개로 두려면 private: true로 표시합니다. 정의한 모든 것은 기본적으로 공유 가능하지만, 비공개 테이블이나 트랜스폼은 다른 앱이 보는 카탈로그에 전혀 나타나지 않습니다.

data: tables: - tablename: measurements # 공유됨(기본값) columns: [ ... ] - tablename: calibration_state # 내부용 — 다른 앱에는 절대 보이지 않음 private: true columns: [ ... ]

접근 권한은 읽기 전용이며, 프로젝트별로 사용자가 부여하고 언제든지 철회할 수 있습니다. 전체 모델과 제공 앱의 이력·라이브 스트림을 읽는 SDK 호출은 다른 앱의 데이터 사용을 참고하세요.

Last updated on