1억 개의 심장 박동 수집: 파산 없이 Wearable Tech 확장
Source: Dev.to
번역할 텍스트가 제공되지 않았습니다. 번역이 필요한 전체 내용을 알려주시면 도와드리겠습니다.
“Continuous”의 수학
숫자를 현실적으로 보자. 장치가 1초에 한 번(1 Hz) 하트비트 페이로드를 전송한다면:
- 1 사용자 = 하루에 86,400 쓰기.
- 1,000 사용자 = 하루에 86.4 백만 쓰기.
페이로드 크기: 100바이트짜리 작은 JSON 패킷이라도 매일 수 기가바이트 단위의 수집 트래픽을 의미한다.
표준 RDBMS(예: MySQL 또는 일반 Postgres)는 트랜잭션 무결성(ACID)에 최적화되어 있으며, 초당 수백만 개의 작은 쓰기를 처리하도록 설계되지 않았다. B‑Tree 인덱싱 오버헤드만으로도 쓰기 처리량을 크게 저하시킨다.
Strategy 1: Stop the “Chatty” Protocol (Batching)
첫 번째 실수는 웨어러블을 채팅 앱처럼 다루는 것입니다. 각 심박마다 WebSocket이나 API 요청을 열지 마세요. 네트워크 오버헤드(TCP 핸드셰이크, HTTP 헤더)가 데이터 자체보다 더 클 때가 많습니다.
The Fix: 디바이스에서 버퍼링합니다. 이상적으로는 디바이스가 60 초 분량의 데이터를 수집한 뒤 하나의 압축된 청크로 전송하도록 합니다.
백엔드에서는 INSERT‑를 한 번에 한 행씩 수행하는 대신 대량 삽입을 사용합니다. 차이를 보여주는 다소 조잡하지만 효과적인 Node.js 예제가 아래에 있습니다:
// DON'T DO THIS
data.points.forEach(async (point) => {
await db.query('INSERT INTO heartbeats VALUES (...)', [point]); // RIP Database
});
// DO THIS
const format = require('pg-format');
const values = data.points.map(p => [p.userId, p.value, p.timestamp]);
const query = format('INSERT INTO heartbeats (user_id, bpm, time) VALUES %L', values);
await db.query(query); // One network round trip, one transaction
이 간단한 변경만으로 IOPS(초당 입출력 작업 수)를 약 50–100배 감소시킬 수 있습니다.
전략 2: 시계열 데이터베이스(TSDB) 사용
나는 Postgres를 사랑한다. 하지만 원시 메트릭의 경우 추가 전용이며 시간 순으로 정렬된 데이터를 처리할 수 있는 것이 필요하다.
TimescaleDB(Postgres 위에 얹힌)나 InfluxDB 같은 도구가 여기서는 구세주다. 이들은 “하이퍼테이블”이나 시간 청크별로 데이터를 파티셔닝하는 특정 스토리지 엔진을 사용한다.
왜 비용을 절감할 수 있을까? 압축 때문이다. 시계열 데이터는 매우 반복적이다:
{ "time": "10:00:01", "bpm": 70 }
{ "time": "10:00:02", "bpm": 70 }
{ "time": "10:00:03", "bpm": 71 }
TSDB는 델타‑오브‑델타 압축을 사용한다. 매번 전체 타임스탬프와 전체 정수를 저장하는 대신, 그 사이의 작은 변화를 저장한다. 우리는 일반 Postgres 테이블에서 압축된 하이퍼테이블로 전환함으로써 스토리지 비용이 90 % 감소하는 것을 확인했다.
데이터베이스 선택에 대한 보다 기술적인 심층 분석을 읽고 싶다면, 내 기술 가이드 및 튜토리얼을 확인해 보라.
전략 3: 다운샘플링의 예술 (롤업)
여기서 솔직히 말하자면: 3개월 전의 초단위 해상도가 필요한 사람은 없습니다. 의사는 어제 발생한 부정맥 사건에 대해 세밀한 데이터가 필요할 수 있지만, 작년 트렌드 보고서에는 일일 평균, 최소값, 최대값만 있으면 충분합니다.
해결책: 연속 집계. 실시간으로 평균을 계산하지 마세요(느립니다). 데이터가 들어올 때 미리 평균을 계산해 두고, 이후에 원시 데이터를 삭제합니다.
SQL (Timescale 구문)에서는 다음과 같이 작성합니다:
-- 자동으로 업데이트되는 뷰 생성
CREATE MATERIALIZED VIEW hourly_heartrate
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS bucket,
user_id,
avg(bpm) AS avg_bpm,
max(bpm) AS max_bpm
FROM heartbeats
GROUP BY bucket, user_id;
-- 원시 데이터를 7일 후에 삭제하는 보존 정책 추가
SELECT add_retention_policy('heartbeats', INTERVAL '7 days');
이제 저장소가 무한히 커지지 않습니다. 고해상도 데이터는 일주일 동안만 보관하고(즉각적인 알림용), 저해상도 데이터는 장기 트렌드 분석을 위해 영구히 보관합니다.
결론
“Continuous Monitoring”을 구축하는 것은 단순히 코드만이 아니라 물리와 경제학에 관한 것이다.
- Buffer 를 클라이언트에 두어 네트워크 호출을 절감한다.
- Batch 를 서버에 두어 IOPS 를 절감한다.
- Compress 를 TSDB와 함께 사용해 디스크 공간을 절감한다.
- Downsample 으로 오래된 데이터를 축소해 데이터베이스를 빠르게 유지한다.
과도하게 설계하고 싶어지지만, 체계적인 데이터 수명 주기 정책이 멋진 Kubernetes 클러스터보다 더 큰 가치를 제공하는 경우가 많다.
행복한 코딩 되세요. 🚀