CHAPTER 02 / SECTION 04
2.4 연구 방식에 맞는 데이터 저장소 고르기
파일과 데이터베이스의 읽기·쓰기 시험을 해석하고 시점 기준 조인에 맞는 구성을 비교합니다.
먼저 어떤 데이터를 얼마나 자주 읽는지 정한다
지우는 매일 여러 종목의 봉을 갱신하고, 지난 1년의 종가와 거래량만 읽어 전략을 다시 계산한다. 혼자라면 날짜별 파일과 분석 도구로 처리할 수 있다. 여러 사람이 같은 자료를 수정하고 서비스가 특정 종목의 최근 30일 값을 계속 조회한다면 읽기·쓰기 충돌과 장애 복구도 고려한다.
파일 형식 시험은 디스크 크기, 쓰기 시간과 전체 읽기 시간을 비교한다. 데이터베이스 시험은 데이터를 적재하고 질의를 수행하는 시간을 비교한다. 예를 들어 종목 100개의 봉 10,000개씩이면 100만 행이다. 비교에 사용한 자료는 메모리에서 약 64MB였다. 실제 업무가 1,000만 행 또는 1억 행이라면 같은 열, 조회 기간, 동시 작업 수를 넣어 다시 측정한다.
파일을 여는 시각과 모든 행·열을 읽어 계산하는 시각은 다르다. 한 번 읽은 내용이 운영체제 캐시에 남으면 다시 읽을 때 빨라질 수 있다. 지우는 첫 실행과 반복 실행을 나눠 기록하고, 어떤 열을 읽었는지, 읽은 뒤 어디까지 처리했는지 맞춰 비교한다.
종가와 거래량만 읽을 때 열 단위 저장이 유리한 이유
CSV(Comma-Separated Values, 쉼표 구분 값)는 보통 한 행의 여러 필드를 문자열로 잇는다. 종가와 거래량만 써도 파일을 읽고 구분자를 해석해 해당 필드를 찾아야 한다. Parquet는 열 단위로 값을 모은다. 비슷한 값끼리 압축하기 쉽고 필요한 열만 읽을 수 있다. HDF5(Hierarchical Data Format 5, 계층형 데이터 형식)는 큰 배열을 블록으로 보관할 수 있어, 자주 읽는 구간과 블록 경계가 맞는지에 따라 결과가 달라진다.
| 형식 | 크기(MB) | 쓰기(초) | 전체 읽기(초) |
|---|---|---|---|
| CSV | 136 | 0.13 | 0.05 |
| Parquet | 40 | 0.07 | 0.02 |
| HDF5 | 71 | 0.13 | 0.40 |
파일 크기 136MB를 40MB로 나누면 3.4다. CSV가 Parquet보다 3.4배 큰 이 결과는 해당 자료의 값과 압축 조건에서 나온다. 행 수를 1천·1만·100만으로 늘린 별도의 규모 시험에서는 작은 파일에서 CSV가 빠른 구간이 있었고 100만 행에서 Parquet가 더 빨랐다. 행 수에 비례하는 참고선은 데이터가 10배로 늘 때 시간이 10배가 되는 경우를 뜻한다. 로그 축의 같은 간격은 같은 배수다. 크기가 작은 시험에서는 파일을 여는 비용이, 큰 시험에서는 압축 해제와 읽는 양이 결과에 더 영향을 줄 수 있다.
가로축 행 수 · 세로축 밀리초(ms) · 두 축 모두 로그
- Parquet
- CSV
- HDF5
- 행 수 비례 참고선
가장 오른쪽의 100만 행에서 Parquet 선이 CSV보다 아래에 있다. 점의 값은 규모 비교 차트에서 읽은 근삿값이다.
지우는 CSV를 사람이 열어 보거나 도구끼리 교환하는 데 쓴다. 기존 과학 데이터가 배열 블록으로 잘 나뉘어 있다면 HDF5의 부분 읽기도 측정해 볼 수 있다. 장기간 쌓고 열이나 기간을 골라 읽는 연구 데이터는 Parquet로 저장한다. Arrow IPC(Arrow 프로세스 간 교환 형식, Feather)는 프로세스 사이에 표를 빨리 전달하고 파일을 메모리에 매핑해 필요한 부분에 접근하는 데 적합하다. 여러 파일에서 조건과 파티션으로 읽을 양을 줄이고 버전을 관리하는 장기 저장에는 별도의 구성이 필요하다.
파일만으로 시작하고 여러 쓰기 작업을 조정할 때 형식을 확장한다
지우가 10년치 파일을 연도·월별로 나누면 지난달 값만 필요한 조회는 해당 월부터 읽는다. 이런 Partitioning(파티션 분할)에서 종목과 날짜를 지나치게 잘게 나누면 작은 파일을 열고 목록을 읽는 비용이 커진다. 자주 거르는 조건을 기준으로 나누고 원본과 정제본을 구분한다.
여러 팀이 같은 표를 동시에 고치면, 한 작업이 절반만 끝난 상태를 다른 작업이 읽을 수 있다. Delta Lake, Iceberg, Hudi 같은 Lakehouse Format(레이크하우스 형식)은 데이터 파일과 함께 어떤 파일 묶음이 하나의 유효한 버전인지 관리한다. 완료된 변경을 한꺼번에 보이게 하고, 열 구조 변경과 동시 쓰기를 조정할 수 있다. 혼자 추가 위주로 운영한다면 단순 분할 파일부터 쓰고, 여러 작성자와 잦은 갱신이 실제로 필요해지면 운영 비용을 비교한다.
내장 엔진은 파일을 둔 채 계산하는 데 쓴다
지우는 지난달 파일에서 거래량 상위 종목만 SQL(Structured Query Language, 구조화 질의 언어)로 고른다. Embedded Database(내장 데이터베이스)는 별도 서버에 접속하는 대신 현재 프로그램에서 질의를 실행한다. DuckDB는 Parquet 파일을 직접 읽어 큰 표에서 열을 고르고 집계하며 시점 기준 결합도 수행할 수 있다. SQLite는 구성이나 종목 정보처럼 작은 관계형 표의 조회와 갱신에 적합하다. ArcticDB는 표 형태 데이터의 버전을 보존해 이전 시점 상태를 조회할 수 있다. 실제 도입 시에는 사용 환경에 해당하는 라이선스와 저장소 요구를 확인한다.
실험의 출발점은 원시·정제 자료를 분할 Parquet로 보존하고, DuckDB로 SQL 조회를 하거나 Polars 같은 DataFrame(표 형태 자료구조) 엔진에서 계산하는 구성이다. pandas와 Polars를 비교할 때는 파일 읽기, 종목별 집계, 조인, 실제 실행을 뒤로 미루는 방식까지 같은 단계에서 재 본다.
서버는 동시 접근과 장애 복구가 필요할 때 비교한다
장중 여러 수집 작업이 적재하고, 다른 사용자가 조회한다면 중앙 권한, 동시 접근, 실패 후 복구와 서비스 수준이 중요하다. Server Database(서버 데이터베이스)는 이런 업무를 맡을 수 있지만 운영과 백업도 필요하다. 아래는 같은 100만 행에 대한 한 시험의 쓰기·읽기 시간이다.
| 엔진 | 쓰기(초) | 읽기(초) | 살펴볼 업무 |
|---|---|---|---|
| kdb+/PyKX | 0.03 | 0.04 | 매우 높은 시계열 처리량. 사용 조건과 운영 비용을 확인한다. |
| ClickHouse | 1.47 | 0.83 | 열 중심 저장과 대량 분석용 SQL 집계. |
| QuestDB | 1.77 | 3.34 | 고속 시계열 수집, 시각 기준 조인과 오래된 데이터의 별도 저장. |
| PostgreSQL | 120 | 3.58 | 관계형 제약, 여러 사용자의 갱신과 일반 서비스. |
| TimescaleDB | 132 | 3.54 | PostgreSQL의 시각 분할과 시간대별 집계. |
| InfluxDB | 34 | 47 | 수집 상태 등 모니터링 시계열. 시험 환경에서는 시점 기준 조인을 별도 처리한다. |
읽기와 쓰기는 각각 비교한다. 예를 들어 PostgreSQL과 TimescaleDB의 읽기는 약 3.6초로 비슷했지만 쓰기는 각각 120초와 132초였다. ClickHouse의 읽기는 0.83초로 PostgreSQL의 약 4분의 1이었다. 인덱스, 일괄 적재 크기, 네트워크, 장애 때 보존할 기록의 범위에 따라 결과가 바뀐다. 장중 서비스를 고를 때는 실제로 사용할 적재 방식과 동시 조회를 넣어 다시 측정한다.
매일 증분 업데이트를 운영할 때는 달력으로 휴장과 누락을 구분하고, 마지막 정상 시각부터 다시 받아도 중복이 생기지 않도록 한다. 수집·정제·저장과 검증 결과를 연결하고 장애가 난 구간만 재실행할 수 있게 한다.
거래와 호가가 다른 시각에 기록될 때 맞추는 법
한 종목의 체결은 09:00:03이고, 호가는 09:00:01과 09:00:04에 기록됐다고 하자. 시각이 똑같은 행만 연결하면 체결에 붙는 호가가 없다. As-of Join(시점 기준 조인, ASOF 조인)은 같은 종목에서 체결 시각 이하인 호가 중 가장 최근 것을 고른다.
- 09:00:01
매수 9,990원, 매도 10,010원 호가가 도착한다. - 09:00:03
10,010원에 체결된다. 앞선 09:00:01 호가를 연결한다. - 09:00:04
새 매도호가 10,020원이 도착한다. 앞선 체결에는 사용하지 않는다.
직전 호가가 너무 오래됐다면 허용 간격이나 세션 경계를 적용한다. 조건을 만족하는 호가가 없으면 연결값은 비운다.
지우는 먼저 종목과 세션을 맞춘다. 호가가 전략에 도착한 시각을 기준으로 연결하며, 기록 시간이 같은 행이 여러 개라면 메시지 순서를 확인한다. pandas와 Polars로 실행할 때는 입력을 시각 순으로 정렬한다. 이 비교에서는 메모리에 올린 시계열의 조인에 Polars가 대체로 가장 빨랐고, pandas도 DuckDB의 SQL 경로보다 빨랐다. 이미 읽고 정렬해 둔 표를 조인하는 시간과 파일을 열고 변환하는 시간은 구분해야 한다. 체결 1건당 호가가 2건일 때와 100건일 때도 검사할 양이 다르다.
업무가 바뀔 때 저장소를 선택하는 기준
| 가장 중요한 목표 | 첫 시험 | 함께 비교할 구성 |
|---|---|---|
| 연구 반복 속도 | Parquet와 Polars | Parquet와 DuckDB |
| 파일에서 SQL 분석 | DuckDB와 Parquet | Polars에서 필요한 열과 조건만 읽기 |
| 공용 서비스의 신뢰성 | PostgreSQL | TimescaleDB |
| 매우 높은 시계열 처리량 | kdb+/KDB X | ClickHouse |
| 대량 연속 수집 | QuestDB | ClickHouse |
| 메모리에서 시점 조인 | Polars | pandas |
| SQL에서 시점 조인 | DuckDB | QuestDB |
| 운영 부담 최소화 | Parquet와 DuckDB | 관리형 PostgreSQL |
지우가 매일 혼자 과거를 훑는 동안에는 분할 파일을 쓰고, 필요한 열만 읽는 작업의 시간을 잰다. 여러 사용자가 같은 데이터에 쓰기 시작하거나 조회 지연을 일정 수준 안에 유지해야 할 때는 서버의 동시 처리와 복구를 함께 시험한다. 저장소가 바뀌어도 원본, 버전과 공개 시각이 보존돼야 과거 판단을 같은 입력으로 재현할 수 있다.