목표
생성기에서 나간 심전도가 서버까지 어떻게 갈지, 어떤 형식으로 보낼지 수신부 담당 팀원과 정한다. (10.5)
구성
생성기 → TCP 8000 (NDJSON) → 수신부 → Redis Streams → 워커 1개 (채널별 10초 모음) → R파 검출 → AI 판정
- 수신부: 상자를 받아 검사하고 큐에 넣는 접수 창구
- Redis Streams: 상자를 줄 세워두는 컨베이어(큐). 몰려도 서버가 안 죽게 해줌
- 워커: 큐에서 상자를 꺼내 분석하는 일꾼
- 채널: 환자 한 명의 심전도 줄. 채널 = 환자로 봐도 됨
- R파: 심전도에서 가장 뛰어오른 뾰족한 봉우리. 박동 하나에 하나
A. 전송 방식: TCP
- 연결을 한 번 열어두고 계속 보냄. 순서가 지켜지고 중간에 빠지면 다시 보내줌
- TCP: 컴퓨터끼리 데이터를 주고받는 규칙 중 하나. 도착 순서와 누락 재전송을 보장함. 반대로 UDP는 빠르지만 빠져도 모름
- 8000: 포트 번호. 건물(컴퓨터) 안의 문 번호라고 보면 됨
- 여러 환자 메시지를 한 연결로 이어서 보내기 때문에 메시지 사이 경계가 필요 → NDJSON
자세히 보기
비유는 “심전도 택배 물류센터”로 통일함.
1. 연결 방식을 왜 먼저 정해야 하나
- 보내는 쪽(내 생성기)과 받는 쪽(수신부)을 다른 사람이 만듦. 도로와 송장 양식을 안 정하면 각자 만들고 나서 안 맞음
- 정해두면 상대가 끝날 때까지 기다리지 않고 동시에 만들 수 있음 (실제로 10.8에 따로 만든 두 코드가 바로 붙음)
2. TCP = 도로
- 한 번 길을 뚫어두면(연결) 상자가 보낸 순서대로, 빠짐없이 도착함. 중간에 사라지면 TCP가 알아서 다시 보냄
- 8000 = 문 번호(포트). 수신부가 8000번 문 앞에서 기다리고, 생성기가 그 문으로 찾아감
- 환자마다 도로를 따로 내지 않고 도로 하나에 전원 상자를 실음
- 응답(“잘 받았어”)을 안 받기로 한 이유: 한 건마다 답을 기다리면 왕복 시간이 매번 붙음. 1ms만 잡아도 1초에 1,000건이 한계인데 환자 100명이 딱 1초에 1,000건이라 여유가 0. 빠진 건 송장 번호로 셈
B. NDJSON
- 한 줄에 JSON 하나, 줄바꿈(
\n)이 메시지 끝 표시- JSON:
{"이름": 값}모양으로 데이터를 적는 글자 형식. 사람도 읽을 수 있음 - NDJSON: Newline Delimited JSON. 줄바꿈으로 구분한 JSON
- JSON:
- 받는 쪽은 줄 단위로 자르기만 하면 됨
자세히 보기
3. NDJSON = 상자 사이 칸막이
- 도로 하나에 상자가 줄줄이 붙어서 오니까 어디서 끊을지 약속이 필요함
- “줄바꿈 = 상자 하나 끝”. 받는 쪽은 줄 단위로 자르기만 하면 됨
- 덕분에
wc -l(줄 수 세기)만 해도 몇 개 왔는지 알 수 있음 (3단계)
C. 메시지 형식 (임시안)
{"channel_id": 0, "seq": 0, "t_start": 0.0, "sent_at": 1760000000.0, "fs": 250, "duration": 0.1, "leads": ["V4", "V3", "II"], "samples": [[...25개], [...], [...]]}
| 항목 | 뜻 | 쓰는 곳 |
|---|---|---|
| channel_id | 환자(채널) 번호 | 누구 것인지, 워커가 채널별로 모을 때 |
| seq | 채널별 순번 | 빠진 번호 = 유실 |
| t_start | 이 조각의 측정 시작 시각 | 순서 맞추기 |
| sent_at | 보낸 시각 | 지연 계산 |
| fs, duration | 샘플링 속도, 조각 길이 | 받는 쪽에서 검사 |
| leads, samples | 리드 이름과 값 | R파 검출 입력 |
- 리드: 전극 조합으로 본 심장 방향. 3리드면 세 방향에서 본 그래프 3줄
- samples: 리드별 숫자 25개씩.
[[25개], [25개], [25개]]= 3줄 - 임시안: 기업 장비 문서가 아직 없어서 우리끼리 정한 것. 문서 오면 바뀔 수 있음
자세히 보기
4. 상자 하나 뜯어보기
{"channel_id": 3, "seq": 57, "t_start": 5.7, "sent_at": 1791432323.07,
"fs": 250, "duration": 0.1, "leads": ["V4","V3","II"], "samples": [[25개],[25개],[25개]]}
| 칸 | 택배로 치면 | 왜 필요한가 |
|---|---|---|
| channel_id | 받는 사람(환자 번호) | 누구 심전도인지. 워커 배정 기준이기도 함 |
| seq | 송장 번호 | 0, 1, 3이 오면 2번이 빠진 거 → 유실을 셀 수 있음 |
| sent_at | 발송 시각 | 받은(처리 끝난) 시각 − 발송 시각 = 지연 |
| t_start | 상자 안 내용물의 날짜 | 파형상 시각. 파일을 재생하니까 보낸 시각과 다름 |
| fs, duration | 내용물 규격 | 1초에 250번 측정, 이 상자는 0.1초 분량. 받는 쪽에서 검사용 |
| leads, samples | 내용물 | 리드 3개 × 25개 숫자. leads 순서가 samples 줄 순서 |
D. 측정 방식
- 지연 = 워커 처리 완료 시각 −
sent_at, p95(느린 쪽 5%를 뺀 최대값)로 봄- p95: 100개를 빠른 순서로 줄 세웠을 때 95번째. 평균은 아주 느린 몇 개를 가려버려서 p95를 봄
- p50: 중간값. 절반은 이것보다 빠름
- 유실 = 채널별
seq가 빈 개수- 예: 환자 3번 상자가 0, 1, 2, 4로 오면 3이 빠짐 → 유실 1
- 개발 중에는 생성기도 같은 VM에서 돌림 → 시계가 같아야 시각 빼기가 맞음
자세히 보기
5. 예상 질문
- seq, sent_at은 왜 넣었나? → seq는 순번이라 빠진 번호로 유실을 세고, sent_at은 보낸 시각이라 받은 시각에서 빼서 지연을 잼. 둘 다 없으면 부하테스트 자체가 불가능
- 생성기를 서버랑 같은 VM에서 돌리는 이유? → 시계가 같아야 sent_at 빼기가 맞음. 개발 단계는 같이, 최종 측정만 분리 (조교 피드백)
- 왜 MQTT 같은 걸 안 썼나? → 기업 장비가 실제로 뭘 쓰는지 문서가 아직 없음. 추측으로 정하지 않고 가장 단순한 TCP로 임시 연결. 문서 오면 수신부의 전송 부분만 교체
배운 점
직접 작성목표
- 생성기에서 나간 심전도가 서버까지 어떻게 갈지, 어떤 형식으로 보낼지 수신부 담당 팀원과 협의
- 보내는 쪽과 받는 쪽이 같은 규칙을 써야 연결가능
- 나 = 보내는 쪽 (생성기) , 팀원 = 받는 쪽 (수신부)
- 전체 흐름 : 생성기 → TCP 8000 (NDJSON) → 수신부 → Redis Streams → 워커 → R파 검출 → AI판정
- 수신부 : 상자를 받아서 검사하고 큐에 넣는 접수 창구
- redis Stream : 상자를 줄 세워두는 큐
- 상자가 한 번에 몰려와도 서버가 안 죽게 해줌
- 워커 : 큐에서 상자를 꺼내 분석해줌
- 채널 : 환자 한 명의 심전도 줄 (=환자)
- R파 : 심전도에서 가장 높이 뾰족하게 솟은 봉우리.
- 박동 하나씩 존재
A. 전송 방식 : TCP
- 목적 : 생성기와 서버를 잇는 통신 방식 결정
- 심전도는 중간에 빠지거나 순서가 섞이면 판정이 틀어진다.
- 기업도 틀리는 것보다 놓치는 걸 더 치명적으로 봤다.
- 심전도는 중간에 빠지거나 순서가 섞이면 판정이 틀어진다.
- 설명 : 연결을 한 번 열어두고 그 위로 계속 보냄. 순서가 지켜지고 중간에 빠지면 다시 보냄
- TCP : 컴퓨터끼리 데이터를 주고받는 규칙.
- 도착 순서와 빠진 것을 재전송 보장
- UDP : TCP의 반대
- 빠르지만 빠져도 모름 —> 이번 프로젝트와는 어울리지 않음
- 연결을 열어두는 이유 : 0.1초마다 새로 연결하면 연결하는 데 시간이 계속 듦
- 연결을 열어둔다는 뜻은 TCP는 데이터를 보내기 전에 요청→응답→확인 과정을 거치는데 그 인사를 모든 상자에서 하지 않고 한 번 연결한 뒤 끊지 않고 계속 사용
- 포트 8000 : 컴퓨터 안의 문 번호. (우리가 정한 번호)
- 서버가 8000번 문을 열어두고 기다린다.
- 문제 : 연결 하나로 여러 환자 상자를 이어보내니깐 상자 사이 경계가 필요함
- TCP : 컴퓨터끼리 데이터를 주고받는 규칙.
B. NDJSON
-
목적 : 연결 하나로 이어서 보내는 상자들 사이에 경계를 정함
- TCP는 데이터를 순서대로 흘려보내기만 하고 상자 하나가 어디까진지 설명해주지 않는다.
- 문제점 : 받는 쪽이 상자를 나누지 못 한다
- TCP는 데이터를 순서대로 흘려보내기만 하고 상자 하나가 어디까진지 설명해주지 않는다.
-
설명 : 상자 하나를 한 줄로 적고 줄바꿈을 상자의 끝 표시로 사용한다. 수신부에서도 줄바꿈이 나올 때마다 자르면 됨
- JSON : {”이름”: 값} 모양으로 데이터를 적는 글자 형식
- NDJSON : Newline Delimited JSON
- 줄바꿈으로 구분한 JSON
- 한 줄 = JSON 하나 = 상자 하나 = 환자 한 명의 0.1초치 (환자 1명 전체가 아님)
- 환자 한 명이 0.1초마다 상자를 하나씩 계속 보냄 —> 1초에 10개
- 줄바꿈(\n) : 화면엔 보이지 않지만 글자로는 들어가져 있음
-
EX
{환자0, 0.0~0.1초}\n {환자1, 0.0~0.1초}\n {환자0, 0.1~0.2초}\n {환자1, 0.1~0.2초}\n- 받는 쪽은 \n 기준으로 잘라서 상자 4개로 나눔 (환자0 거 2개, 환자1 거 2개)
- 줄바꿈은 상자를 나누고 , 환자는 channel_id로 나눔 (C단계)
- 받는 쪽은 \n 기준으로 잘라서 상자 4개로 나눔 (환자0 거 2개, 환자1 거 2개)
C. 메시지 형식 (임시)
-
목적 : 상자에 붙일 송장 양식을 수신부 팀원과 결정
- 줄바꿈으로 상자는 나눌 수 있어도, 누구 건지, 몇 번째인지, 언제 보냈는지 확인 불가능
- 받는 쪽이 분류하고 검사하고 측정할라면 필요
- 임시안으로 결정. 문서 오고 추후 추가할거나 제거할 거 결정
- 줄바꿈으로 상자는 나눌 수 있어도, 누구 건지, 몇 번째인지, 언제 보냈는지 확인 불가능
-
설명 : 상자 하나 (한 줄)에 정한 값 —> 스키마
- channel_id : 환자 번호
- 누구 건지 확인 가능. 워커가 환자별로 모을 때 사용
- seq : 환자별 순번 0,1,2 …
- 중간에 빠진 번호 있으면 유실
- t_start : 이 상자가 심전도의 몇 초 지점부터인지
- 순서 확인
- sent_at : 보낸 시각
- 받은 시각에서 빼면 지연 (D에서 사용)
- fs, duration : 250Hz, 0.1초
- 받는 쪽이 개수가 맞는지 검사
- leads, samples : 리드 이름과 실제 값
- R파 검출에 들어가는 값
- 3리드라서 samples = [[25개], [25개], [25개]] = 리드 3개 X 25개
- 스키마 : 이렇게 칸을 정한 양식을 부르는 공식 용어
- 스키마 = 빈 양식 / 메시지 = 양식에 값을 채운 것
- schema_version : 양식 버전 번호. 칸을 추가·삭제하거나 단위를 바꿀 때만 올림 (메시지마다 올라가는 건 seq)
- 받는 쪽이 버전을 보고 옛날 양식이랑 새 양식을 구분해서 읽으려고 씀
- channel_id : 환자 번호
-
EX
{"channel_id": 0, "seq": 2, "t_start": 0.2, "sent_at": 1760000000.2, "fs": 250, "duration": 0.1, "leads": ["V4", "V3", "II"], "samples": [[25개], [25개], [25개]]}- 이 코드는 생성기 코드 (send 함수)가 0.1초마다 환자별로 자동으로 만들어서 보낸다.
- 터미널에선 python stream_multi.py 100 만 작성
- send 함수는 3단계에서 채움
- 이 코드는 생성기 코드 (send 함수)가 0.1초마다 환자별로 자동으로 만들어서 보낸다.
D. 측정 방식
- 목적 : 서버가 잘 버티는지 판단할 기준을 미리 정함
- 기준 없이 돌리면 잘 되는 것처럼 보임. 기준을 정해야 5단계에서 몇 명까지 버티는지 말할 수 있음
- 설명 : 지연이랑 유실 두 가지를 측정. 둘 다 sent_at, seq으로 계산
- 지연 : 워커가 처리를 끝낸 시각 빼기 sent_at (보낸 시각) = 보내고 나서 처리까지 걸린 시간
- ex. 10.00초에 보내고 10.35초에 끝나면 지연 0.35초
- 유실 : 환자별로 seq가 빈 개수
- ex. 환자 3번 상자가 0,1,2,4로 도착 —> 3빠졌으므로 유실 1
- p95 : 100개를 빠른 순서로 줄 세웠을 때 95번째 값.
- 느린 쪽 5%를 빼고 가장 느린 값
- 평균을 사용하면 아주 느린 몇 개로 가려버려서 환자 몇 명이 늦게 받아도 평균은 괜찮아 보임
- 기업도 부하테스트는 p95을 보라고 함
- p50 : 중간값.
- 절반은 이것보다 빠름
- 같은 컴퓨터로 실행
- 지연은 두 시각을 빼는 거므로 시계가 같아야 정확
- 지연 : 워커가 처리를 끝낸 시각 빼기 sent_at (보낸 시각) = 보내고 나서 처리까지 걸린 시간