일상/IT

PostgreSQL 시계열 데이터 성능 저하 원인과 해결 방법 정리

얇은생각 2026. 4. 10. 07:30
반응형

PostgreSQL이 시계열 워크로드에서 느려지는 이유 — 그리고 더 나은 대안

PostgreSQL 데이터베이스가 갑자기 “망가진” 것은 아닙니다. 쿼리가 하루아침에 엉망이 된 것도 아니고, 엔지니어들이 인덱스 거는 법을 잊어버린 것도 아닙니다.

문제는 단순합니다.

테이블이 계속 커졌을 뿐입니다.

 

많은 팀이 바로 이 지점을 놓칩니다. 예전에는 50ms에 끝나던 쿼리가 어느 순간 2초, 3초, 심하면 5초까지 걸리기 시작합니다. 누가 뭘 잘못해서가 아닙니다. 조용히, 아주 자연스럽게, 워크로드의 성격이 바뀐 겁니다. 원래는 평범한 애플리케이션 테이블이었던 것이 어느새 시계열 시스템처럼 동작하기 시작한 것이죠. 그리고 PostgreSQL은 강력한 범용 데이터베이스이긴 하지만, 그런 일을 위해 처음부터 설계된 엔진은 아닙니다.

느려진 대시보드, 지연되는 알림, 끝없이 반복되는 최적화 작업. 그 배경에는 대개 비슷한 이야기가 있습니다. 인덱스를 추가하면 잠깐 빨라집니다. 몇 달 지나면 다시 느려집니다. 테이블을 파티셔닝하면 또 개선됩니다. 그런데 시간이 지나면 다시 무너집니다. RAM을 늘리고, autovacuum을 튜닝하고, 인스턴스 사양을 높이고, 플래너 설정을 만지며 조금 더 버팁니다.

 

그러다 어느 날 깨닫게 됩니다.
같은 쿼리를 비슷한 속도로 유지하려고 1년 넘게 점점 더 비싼 처방을 반복하고 있었다는 사실을요.

이건 지속 가능한 방식이 아닙니다.

그리고 더 중요한 건, 대부분의 경우 이 문제는 단순한 튜닝 문제가 아니라는 점입니다. 아키텍처가 워크로드와 맞지 않는 문제에 가깝습니다.

 

데이터가 시간에 따라 커진다면, 데이터베이스 전략도 시간 축을 기준으로 다시 설계되어야 합니다.

 

이 글에서는 PostgreSQL이 왜 시계열 워크로드에서 점점 버거워지는지, 흔히 쓰는 해결책이 왜 늘 임시 처방에 그치는지, 그리고 기존 PostgreSQL 스택을 버리지 않고도 어떻게 근본 원인에 접근할 수 있는지를 차근차근 풀어보겠습니다. 특히 시계열 워크로드를 위해 설계된 오픈소스 PostgreSQL 확장인 TimescaleDB가 실제로 어떤 방식으로 문제를 푸는지, 그리고 생각보다 적은 변경으로 왜 큰 성능 차이를 만들어내는지도 함께 살펴보겠습니다.

 

왼쪽에는 복잡하게 얽힌 케이블과 과부하된 저장 장치로 인해 병목이 발생한 전통적인 데이터베이스 서버가, 오른쪽에는 시간 단위로 정리된 블록과 빠른 데이터 흐름으로 구성된 시계열 최적화 데이터베이스가 대비되어 표현된 분할형 기술 일러스트

 


 

많은 팀이 거꾸로 풀고 있는 PostgreSQL 문제의 본질

PostgreSQL은 역사상 가장 뛰어난 범용 관계형 데이터베이스 중 하나입니다. 이 평가는 과장이 아닙니다. 안정적이고, 성숙했고, 강력합니다. 사용자, 주문, 상품, 거래, 권한, 결제 기록 같은 전통적인 애플리케이션 데이터를 다루는 데 있어서 오랜 시간 검증되어 왔습니다.

이런 데이터는 대개 익숙한 흐름을 따릅니다.

  • 생성된다.
  • 조회된다.
  • 수정된다.
  • 삭제된다.

 

즉, 전형적인 CRUD 중심 데이터입니다.

PostgreSQL은 바로 이런 세계를 다루는 데 탁월합니다. 안전한 동시성 처리, 트랜잭션 일관성, 유연한 인덱싱, 다양한 확장 기능, 견고한 운영 도구, 큰 생태계를 모두 갖추고 있죠. 여러분의 시스템이 전통적인 애플리케이션 데이터베이스에 가깝다면, PostgreSQL은 대체로 아주 좋은 선택입니다.

하지만 모든 데이터가 사용자나 주문처럼 움직이진 않습니다.

어떤 데이터는 거의 수정되지 않습니다. 그저 계속 들어오기만 합니다.

예를 들면 이런 것들입니다.

  • event stream
  • 로그
  • 센서 측정값
  • 사용자 활동 기록
  • 모니터링 지표
  • 거래 이력
  • 디바이스 텔레메트리
  • 기계가 생성하는 append-only 데이터

 

바로 여기서 상황이 바뀌기 시작합니다.
처음엔 조용하게. 그러다 어느 순간 확실하게.

처음에는 “그냥 Postgres 테이블 하나”였던 것이 시간이 지날수록 시계열 워크로드를 담는 저장소로 변해 갑니다. 그리고 그 순간부터 모든 것이 달라집니다.

 

 


 

시계열 워크로드란 정확히 어떤 모습인가

조금 더 구체적으로 보겠습니다.

전형적인 시계열 테이블은 events, logs, user_activity, sensor_readings 같은 이름을 갖는 경우가 많습니다. 거의 항상 timestamp 컬럼이 있고, 새 행이 끊임없이 들어옵니다. 초당 수백 건일 수도 있고, 수천 건일 수도 있고, 수만 건일 수도 있습니다.

이런 워크로드에는 몇 가지 뚜렷한 특징이 있습니다.

 

1. 데이터가 계속 들어옵니다

이건 야간 배치 작업이 아닙니다. 주기적인 ETL 창도 아닙니다. 데이터는 하루 종일, 매일, 계속 들어옵니다.

즉, 테이블은 쉴 틈이 없습니다. 계속 커지기만 합니다.

 

2. 쿼리의 중심은 대부분 시간입니다

이런 쿼리를 떠올려 보세요.

  • 최근 30분 데이터 보여줘
  • 지난 1시간의 이벤트를 가져와
  • 이번 주와 지난주를 비교해
  • 이 두 시각 사이의 데이터를 전부 반환해
  • 최근 활동을 디바이스, 서비스, 고객 단위로 집계해

 

여기서 timestamp는 부수적인 정보가 아닙니다.
조회 패턴의 중심축입니다.

 

3. 데이터는 append-only이거나 그에 가깝습니다

행이 한 번 들어오면, 그 뒤로 거의 바뀌지 않습니다.

특히 로그, 지표, 텔레메트리, 모니터링 이벤트, 사용자 행동 기록은 그렇습니다. 이런 행들은 어떤 시점에 무슨 일이 있었는지를 기록한 “사실”에 가깝습니다.

 

4. 데이터를 오래 보관합니다

이건 단순한 메모리 버퍼나 잠깐 쓰고 버리는 임시 저장소가 아닙니다. 많은 팀이 이 데이터를 수개월, 때로는 수년간 보관합니다. 분석, 규제 준수, 감사, 머신러닝, 보고서, 장기 추세 분석 등에 필요하기 때문입니다.

 

5. 그런데도 쿼리는 빨라야 합니다

이 부분이 중요합니다. 데이터는 크고 오래 쌓이지만, 활용 방식은 매우 실시간에 가깝습니다.

밤새 보고서를 기다리는 상황이 아닙니다. 대시보드, 알림, SRE 도구, 고객 분석, 이상 탐지 시스템, 제품 텔레메트리를 돌리고 있는 겁니다. 응답 시간은 보통 서브초에서 몇 초 이내여야 의미가 있습니다.

정리하면 이렇습니다.
지속적인 삽입, 시간 기반 조회, append-only 성격, 장기 보관, 빠른 읽기 요구.
이 조합이 바로 전형적인 시계열 패턴입니다.

 

데이터가 계속 쌓이고, 거의 바뀌지 않으며, 대부분 시간 범위로 조회된다면 그건 단순히 큰 테이블이 아닙니다. 시계열 워크로드입니다.

 

그리고 이 차이는 생각보다 훨씬 중요합니다.

 

 


 

반복되는 PostgreSQL 최적화 루프: 왜 모든 처방이 만료되는가

이 이야기가 익숙하다면, 이미 전형적인 최적화 루프를 겪어본 분일 가능성이 큽니다.

대개 흐름은 이렇습니다.

 

먼저: 쿼리 성능이 슬슬 무너지기 시작합니다

예를 들어 “최근 1시간의 이벤트를 디바이스별로 묶어서 집계해 달라”는 지극히 자연스러운 쿼리가 어느 순간 너무 오래 걸리기 시작합니다. 원래는 50ms였는데, 어느새 3초가 걸립니다.

그러면 충분히 손볼 수 있어 보입니다.
그래서 가장 먼저 떠오르는 해결책을 씁니다.

 

1단계: 인덱스를 추가합니다

이건 분명 효과가 있습니다.
잠깐은요.

B-tree 인덱스를 추가하면 PostgreSQL은 전체 테이블 스캔을 피하고 필요한 행을 훨씬 빨리 찾을 수 있습니다. 몇 초 걸리던 쿼리가 다시 100ms 안팎으로 떨어집니다.

그럼 끝일까요?

아직 아닙니다.

테이블이 계속 커지면 인덱스도 같이 커집니다. 이제 삽입할 때마다 인덱스도 함께 갱신해야 하니 쓰기 성능이 서서히 무거워집니다. 인덱스 크기가 커지고 깊어질수록 읽기 효율도 예전만 못해집니다.

몇 달 지나면 또 느려집니다.

 

2단계: 테이블을 파티셔닝합니다

이제 다음 카드로 넘어갑니다.

시간 기준으로 테이블을 나눕니다. 일 단위, 주 단위, 월 단위로 분할해서 PostgreSQL이 시간 범위 쿼리에서 관련 파티션만 보게 만드는 방식이죠. 효과가 꽤 큽니다.

그리고 또 빨라집니다.

하지만 이것도 오래가진 않습니다.

이제부터는 파티션을 직접 관리해야 합니다. 미리 새 파티션을 만들어야 하고, 오래된 파티션은 정리해야 합니다. 플래너가 pruning을 기대한 대로 하지 않는 예외 상황도 챙겨야 하죠. 파티션 수가 수백 개 수준으로 늘어나면 실행 전에 각 파티션을 검토하는 planning 단계 자체가 느려지기 시작합니다.

결국 문제를 해결한 게 아니라, 더 복잡한 형태로 옮겨놓은 셈이 됩니다.

 

3단계: 하드웨어와 설정으로 버팁니다

이쯤 되면 많은 팀이 다음 레버를 당깁니다.

  • shared_buffers를 늘린다
  • maintenance_work_mem을 키운다
  • autovacuum을 더 공격적으로 튜닝한다
  • CPU를 8코어에서 16코어로 올린다
  • 필요하면 32코어로 간다
  • RAM을 늘린다
  • 더 빠른 스토리지에 돈을 쓴다
  • 플래너와 유지보수 관련 설정을 더 손본다

 

이것도 효과가 있습니다.
역시 잠깐은요.

하지만 여기서부터는 비용의 성격이 바뀝니다. 인프라 비용이 늘어나고, 엔지니어링 시간도 최적화와 유지보수에 더 많이 들어갑니다. 그런데 결과는 씁쓸하게도 익숙합니다. 더 크고 더 비싼 환경에서 겨우 같은 성능을 유지하는 동안, 테이블은 아래에서 계속 자라고 있습니다.

멈추지 않는 러닝머신 위에서 더 빨리 달리고 있는 것과 비슷합니다.

 

 


 

왜 PostgreSQL의 구조는 시계열 데이터와 충돌하는가

여기서 가장 중요한 포인트가 있습니다.
지금까지 말한 대응이 틀린 건 아니라는 점입니다.

인덱싱도 맞습니다. 파티셔닝도 맞습니다. 설정 튜닝도 맞습니다. 하드웨어 확장도 맞습니다.

문제는 “잘못된 최적화”를 하고 있다는 데 있지 않습니다.

문제는 그 최적화들이 원인을 해결하는 것이 아니라 증상만 완화하고 있다는 것입니다.

근본 원인은 구조에 있습니다. PostgreSQL의 저장 방식과 트랜잭션 처리 방식은 범용 관계형 워크로드를 위해 설계되었습니다. 반면 우리가 다루는 것은 대규모 append-only, 시간 범위 중심 데이터 스트림입니다.

이게 조금 추상적으로 들릴 수 있으니 비유를 하나 해보겠습니다.

가족용 세단으로 공사 현장에서 하루 종일 모래자루를 나른다고 생각해 보세요. 차가 나쁜 건 아닙니다. 오히려 아주 좋은 차일 수 있습니다. 다만 그 용도에 딱 맞는 도구는 아닙니다. 서스펜션을 보강하고 타이어를 바꾸고 정비를 자주 하면 어느 정도 버틸 수는 있습니다. 하지만 어느 순간부터는 튜닝보다 차종 자체의 부적합성이 더 큰 문제가 됩니다.

표준 PostgreSQL을 대규모 시계열 엔진처럼 쓰는 상황이 딱 그렇습니다.

 

 


 

숨겨진 비용: 메타데이터, autovacuum, B-tree 인덱스

이 구조적 불일치가 시간이 갈수록 아프게 다가오는 이유는 크게 세 가지입니다.

 

1. 모든 행이 사실상 필요 없는 메타데이터를 짊어지고 있습니다

PostgreSQL은 MVCC를 사용합니다. 여러 트랜잭션이 동시에 안전하게 읽고 쓰도록 해주는 핵심 메커니즘이죠.

이건 PostgreSQL의 큰 강점입니다.

문제는 MVCC를 구현하기 위해 각 행에 관리용 메타데이터가 붙는다는 점입니다. 대략 행당 23바이트 정도의 메타데이터가 붙는데, 여기에는 다음과 같은 정보가 들어갑니다.

  • 어떤 트랜잭션이 이 행을 만들었는지
  • 다른 트랜잭션에서 이 행을 볼 수 있는지
  • 삭제되었는지
  • 어떤 행 버전이 유효한지

 

users 테이블이나 orders 테이블이라면 이게 아주 합리적입니다. 행이 수정되고 삭제되며, 동시에 읽고 쓰는 작업이 계속 일어나기 때문입니다. 이 관리 정보는 충분히 가치가 있습니다.

그런데 append-only인 events 테이블을 떠올려 보세요.

한 번 쓰고, 그 뒤로 거의 건드리지 않습니다.

그런데도 모든 행이 “언젠가 수정·삭제될 수도 있는 데이터”를 전제로 한 메타데이터를 계속 달고 다닙니다.

23바이트 자체는 작아 보일 수 있습니다. 하지만 초당 수만 건씩 쌓이면 이야기가 달라집니다. 시간이 지나면서 이건 기가바이트 단위의 저장 공간 낭비로 이어질 수 있습니다.

이 오버헤드만으로 바로 시스템이 무너지는 건 아닙니다. 하지만 규모가 커질수록, PostgreSQL이 원래 그런 워크로드를 위해 설계되지 않았다는 비용을 한 번 더 내는 셈이 됩니다.

 

2. autovacuum은 없는 쓰레기까지 찾으러 다닙니다

PostgreSQL에는 autovacuum이라는 백그라운드 유지보수 프로세스가 있습니다. 업데이트나 삭제로 인해 생긴 오래된 행 버전을 정리하는 역할을 하죠.

일반적인 CRUD 워크로드에서는 이게 필수입니다.

하지만 append-only 시계열 테이블에서는 애초에 정리할 대상이 별로 없습니다. 업데이트도 거의 없고, 삭제도 거의 없기 때문입니다.

그런데도 autovacuum은 테이블 전체를 돌며 “청소할 게 있나?”를 계속 확인합니다.

비유하자면 이렇습니다. 거대한 창고를 매일 돌아다니는 청소 담당자가 있습니다. 모든 구역에 쓰레기가 있는지 확인하지만, 실제로는 대부분 봉인된 상자만 쌓여 있고 아무도 그걸 열거나 건드리지 않습니다.

청소 담당자가 일을 못하는 게 아닙니다. 시스템이 망가진 것도 아닙니다. 다만 굳이 필요하지 않은 곳에 계속 자원을 쓰고 있는 것입니다.

행 수가 수억 건까지 커지면, 이런 불필요한 스캔이 CPU와 I/O를 먹고 실제 쿼리와 자원을 경쟁하게 됩니다.

 

3. B-tree 인덱스는 “최근 데이터”와 “오래된 데이터”를 구분하지 못합니다

B-tree 인덱스는 정말 훌륭한 구조입니다. PostgreSQL이 전통적인 데이터베이스 워크로드에서 강한 이유 중 하나이기도 하죠.

사용자 ID를 찾거나 주문 번호를 조회하는 데는 아주 빠르고 깔끔합니다.

하지만 시계열 워크로드는 특정 단일 행을 찾는 문제가 아닐 때가 많습니다. 핵심은 시간 범위를 조회하는 것입니다. 특히 최근 구간이 중요합니다.

예를 들면:

  • 최근 1시간의 모든 이벤트
  • 두 timestamp 사이의 데이터
  • 특정 디바이스의 최근 센서 값
  • 이번 주와 지난주 비교

 

B-tree에는 “뜨거운 데이터”와 “차가운 데이터”라는 개념이 없습니다. 첫 번째 행이든 5억 번째 행이든 동일한 방식으로 다룹니다.

테이블이 커지면 인덱스도 선형적으로 커지고, 시간 범위 조회는 점점 더 많은 일을 해야 합니다.

여기 함정이 있습니다.
중간 규모 테이블에서는 아주 잘 통하던 인덱스 전략이, 끝없이 자라는 대규모 시계열 데이터셋에서는 장기적으로 맞지 않을 수 있다는 점입니다.

 

고통스러운 진실은 PostgreSQL이 약해서가 아닙니다. 시계열 워크로드가 범용적인 가정을 가차 없이 무너뜨리기 때문입니다.

 

 


 

이건 실력 문제가 아닙니다

이 부분은 꼭 강조하고 싶습니다. 많은 엔지니어링 팀이 여기서 잘못된 결론을 내리기 때문입니다.

PostgreSQL이 시계열 부하 아래에서 느려지기 시작하면, 사람들은 쉽게 이렇게 생각합니다.

  • 스키마가 잘못됐나?
  • 쿼리가 허술한가?
  • 인덱스를 덜 걸었나?
  • 팀이 튜닝을 충분히 못했나?

 

물론 그런 경우도 있습니다. 하지만 생각보다 자주, 그건 핵심 원인이 아닙니다.

유능한 엔지니어, 합리적인 인덱스, 적절한 파티션, 꽤 괜찮은 설정이 모두 있어도 시간이 지나면 결국 성능 싸움에서 밀릴 수 있습니다.

왜냐하면 문제는 “PostgreSQL을 못 써서”가 아니기 때문입니다.
범용 트랜잭션 엔진에게 시계열 전용 시스템처럼 일해 달라고 요구하고 있기 때문입니다.

그건 무능력이 아닙니다.
워크로드와 아키텍처의 불일치입니다.

그리고 이 사실을 인식하는 순간 질문이 바뀝니다.

“이걸 언제까지 계속 땜빵하지?”

에서

“이 워크로드에 맞는 저장 구조는 무엇이지?”

로요.

 

 


 

더 나은 길: PostgreSQL을 버리는 대신 확장하기

다행히 이 문제를 해결하기 위해 PostgreSQL을 완전히 포기할 필요는 없습니다.

현실에서는 이 점이 매우 중요합니다.

대부분의 팀은 시계열 쿼리를 더 효율적으로 처리하겠다고 해서 전체 스택을 갈아엎고 싶어 하지 않습니다. 엔지니어를 다시 교육하고, 도구 체인을 다시 맞추고, ORM을 교체하고, 연결된 모든 시스템을 다시 마이그레이션하는 비용은 엄청나니까요.

그래서 더 현실적인 접근은 이겁니다.

PostgreSQL은 유지하되, 시계열 데이터를 저장하고 조회하는 방식을 아래쪽에서 바꾸는 것.

바로 이 지점에 TimescaleDB가 있습니다.

TimescaleDB는 시계열 워크로드를 위해 설계된 오픈소스 PostgreSQL 확장입니다. PostgreSQL 위에서 동작하며, 팀이 이미 좋아하고 익숙한 것들을 그대로 가져갈 수 있습니다.

  • SQL
  • PostgreSQL 드라이버
  • 익숙한 운영 도구
  • 기존 확장
  • ORM
  • 모니터링 연동
  • PostgreSQL 운영 경험

 

달라지는 건 시계열 데이터에 대한 내부 동작 방식입니다.

핵심은 이겁니다.
PostgreSQL을 버리는 게 아니라, 지금의 워크로드에 맞게 확장하는 것입니다.

 

 


 

TimescaleDB는 어떻게 동작하는가

큰 틀에서 보면 TimescaleDB는 시계열 데이터를 저장하는 방식, 파티셔닝하는 방식, 압축하는 방식, 조회하는 방식을 바꿉니다.

사용자 입장에서는 여전히 PostgreSQL처럼 느껴집니다. 하지만 내부적으로는 지속적으로 증가하는 timestamp 중심의 append-only 데이터를 훨씬 잘 처리하도록 설계된 메커니즘이 추가됩니다.

이게 TimescaleDB가 매력적인 이유입니다. 완전히 낯선 데이터베이스 모델로 억지로 옮겨가게 만들지 않습니다.

팀이 이미 PostgreSQL을 잘 알고 있다면, 전혀 다른 시스템으로 갈아타는 것보다 훨씬 부담이 적습니다.

그리고 이건 2026년 현재 더 중요해지고 있습니다. 이제는 더 많은 팀이 다음과 같은 요구를 동시에 받고 있기 때문입니다.

  • 실시간 관측성
  • AI/ML 학습 파이프라인
  • 제품 분석
  • 스트리밍 이벤트 아키텍처
  • 장기 보관 규제 요구사항
  • 운영용 + 분석용 혼합 워크로드

 

이런 요구는 줄어들지 않습니다. 오히려 계속 늘고 있습니다.
그래서 기존 PostgreSQL 스택을 깨뜨리지 않으면서 시계열 압박을 해결하는 방법이 점점 더 중요해지고 있습니다.

 

 


 

TimescaleDB의 핵심 기능을 쉽게 설명하면

이제 TimescaleDB가 실제로 어떤 기능으로 이 문제를 푸는지 하나씩 살펴보겠습니다.

 

Hypertable: 고통 없는 자동 시간 파티셔닝

첫 번째 핵심 기능은 hypertable입니다.

hypertable은 일반 PostgreSQL 테이블을 시계열에 맞는 구조로 바꿨을 때 얻는 논리적 테이블입니다. TimescaleDB는 데이터를 시간 기준으로 자동으로 chunk 단위로 나눕니다.

즉, 다음과 같은 일을 직접 할 필요가 없습니다.

  • 파티션을 수동으로 만들 필요가 없다
  • 파티션을 수동으로 관리할 필요가 없다
  • 파티션 pruning을 직접 챙길 필요가 없다
  • 오래된 시간 구간을 직접 잘라낼 필요가 없다

 

TimescaleDB가 chunk 분할을 알아서 처리하고, 쿼리 플래너도 관련 없는 chunk는 건너뛸 수 있습니다.

예를 들어 쿼리가 최근 1시간만 필요하다면, 전체 이력을 다 끌고 와서 고민할 필요가 없습니다. 관련 있는 chunk만 보면 됩니다.

이게 성능 차이를 크게 만드는 가장 중요한 이유 중 하나입니다.

날짜별로 서류를 정리한 파일 캐비닛을 떠올려 보세요. 어제 문서만 찾으면 되는데 창고 전체를 뒤질 필요는 없겠죠. 필요한 서랍 하나만 열면 됩니다.

시계열 쿼리에서 hypertable이 해주는 일이 바로 그겁니다.

 

컬럼 압축: 더 작게 저장하고, 분석은 더 빠르게

두 번째 핵심 기능은 컬럼 압축입니다.

TimescaleDB는 시계열 데이터를 90% 이상 압축할 수 있습니다. 이것만으로도 저장 비용 측면에서 상당히 매력적입니다. 그런데 장점은 단순히 디스크 절약에 그치지 않습니다.

분석성 쿼리에서는 압축된 데이터가 오히려 더 빠를 수 있습니다. 디스크에서 읽어야 할 데이터 양 자체가 줄어들기 때문입니다.

이게 중요한 이유는, 오래된 시계열 데이터는 보통 행 하나하나를 트랜잭션처럼 건드리는 것이 아니라 집계와 분석 형태로 읽히는 경우가 많기 때문입니다. 데이터를 촘촘하게 묶어두고 더 적게 스캔할 수 있다면 I/O를 줄이고 조회 효율을 높일 수 있습니다.

실무적으로 말하면 이렇습니다.
오래된 데이터는 더 싸게 보관할 수 있고, 때로는 더 빠르게 분석할 수도 있습니다.

이 두 가지를 동시에 얻는 경우는 흔치 않습니다.

 

 

Continuous Aggregate: Materialized View보다 현실적인 집계

PostgreSQL에도 materialized view가 있습니다. 분명 유용합니다. 하지만 시계열 데이터에는 치명적인 약점이 있습니다.

전통적인 materialized view는 refresh할 때 전체를 다시 계산하는 경우가 많습니다.

기반 테이블이 크고 계속 자라는 환경에서는 이 비용이 매우 빠르게 커집니다.

반면 TimescaleDB의 continuous aggregate는 다르게 동작합니다. 마지막 refresh 이후 새로 들어온 데이터만 증분 방식으로 반영합니다. 매번 전체 데이터를 다시 스캔하지 않습니다.

이건 다음과 같은 집계에서 특히 유용합니다.

  • 시간별 평균
  • 일별 건수
  • 디바이스별 요약 통계
  • 서비스 레벨 지표
  • 추세 대시보드

 

지속적으로 최신 상태에 가까운 집계를 유지하면서도, 매번 전체 테이블을 다시 읽는 비용을 피하고 싶다면 continuous aggregate는 실전에서 매우 강력한 기능입니다.

 

 

Retention Policy와 Data Tiering: 데이터 수명을 똑똑하게 관리하기

시계열 시스템은 결국 같은 질문과 마주합니다.

 

오래된 데이터는 어떻게 할 것인가?

어떤 데이터는 일정 기간 후 삭제해도 됩니다. 어떤 데이터는 규제 때문에 반드시 보관해야 합니다. 어떤 데이터는 조회 빈도는 낮지만 여전히 질의 가능해야 합니다. 어떤 데이터는 더 저렴한 저장소로 옮기는 편이 낫습니다.

 

TimescaleDB는 이를 위해 다음을 제공합니다.

  • retention policy: 일정 기간이 지난 chunk를 자동으로 삭제
  • data tiering: 오래된 데이터를 S3 같은 저비용 객체 스토리지로 옮기면서도 조회 가능하게 유지

 

시계열 시스템의 비용 구조는 데이터의 “나이”에 크게 좌우됩니다. 최근 데이터는 뜨겁고, 오래된 데이터는 차갑습니다. 둘을 똑같이 다루는 건 대부분 비효율적입니다.

성숙한 시계열 아키텍처는 단순히 쿼리 성능만 보는 것이 아니라, 데이터 생애주기 전체를 이해하고 설계합니다.

 

똑똑한 시계열 시스템은 데이터를 쌓기만 하지 않습니다. 시간이 흐름에 따라 데이터를 다르게 다룹니다.

 

 


 

실전 성능 예시: 260만 건의 행

아키텍처 이야기는 실제 성능으로 이어질 때 의미가 있습니다. 그래서 구체적인 예를 보겠습니다.

events 테이블에 약 260만 건의 행이 들어 있다고 가정해 보죠. 대략 30일 동안 1초에 1건씩 쌓인 규모입니다. 프로덕션 기준으로 엄청 큰 데이터는 아닐 수 있지만, 패턴을 드러내기에는 충분합니다.

이제 EXPLAIN ANALYZE로 최근 1시간 동안의 디바이스 평균 데이터를 계산하는 쿼리를 실행합니다.

일반 PostgreSQL 테이블에서 결과는 대략 이랬습니다.

  • 실행 시간: 549.5ms
  • 계획 시간: 0.9ms
  • 읽거나 스캔한 행 수: 약 260만 건

 

숫자만 보면 아주 끔찍하진 않아 보일 수 있습니다. 하지만 생각해 보면 최근 1시간이라는 좁은 구간을 보기 위해 너무 많은 데이터를 읽고 있습니다.

이제 TimescaleDB 확장을 활성화하고, 해당 테이블을 hypertable로 변환합니다.

그리고 똑같은 쿼리를 다시 실행합니다.

 

결과는 크게 달라집니다.

  • 실행 시간: 1.7ms
  • 계획 시간: 43ms
  • 읽은 행 수: 약 3,290건

 

실행 시간 기준으로 보면 대략 320배 향상입니다.

여기서 진짜 중요한 포인트는 SQL 자체가 크게 바뀐 것이 아니라는 점입니다. 애플리케이션 모델을 처음부터 다시 설계한 것도 아닙니다. 성능 향상의 핵심은 저장 구조를 바꿔서, 엔진이 정말 필요한 데이터만 보게 만들었다는 데 있습니다.

물론 이 예시에서는 계획 시간이 0.9ms에서 43ms로 늘었습니다. 하지만 실행 시간이 0.5초를 넘던 것이 2ms 아래로 떨어졌다면, 이 정도 trade-off는 사실상 무시해도 될 수준입니다.

그래서 표면적인 벤치마크 숫자만 보면 안 됩니다. planning cost가 조금 늘더라도 query execution에서 해야 할 일을 엄청나게 줄여준다면 그건 충분히 좋은 거래입니다.

그리고 테이블이 더 커질수록 이 차이는 더 극적으로 벌어질 수 있습니다.

 

 


 

내 문제가 시계열 문제인지 판단하는 법

모든 큰 테이블이 시계열 문제인 건 아닙니다. timestamp 컬럼이 있다고 해서 무조건 특화 솔루션이 필요한 것도 아닙니다.

하지만 다음 신호가 보인다면, 경계선을 넘었을 가능성이 큽니다.

 

 

이런 경우라면 시계열 워크로드일 가능성이 높습니다

  • 데이터가 24시간 계속 삽입된다
  • 삽입 후 행이 거의 수정되지 않는다
  • 대부분의 쿼리가 최근 시간 범위를 기준으로 한다
  • 테이블이 자연스럽게 계속 커진다
  • 대시보드, 알림, 실시간 분석이 빠른 쿼리에 의존한다
  • 인덱스가 잠깐 효과를 내지만 곧 다시 한계가 온다
  • 파티셔닝은 도움 되지만 운영 부담을 키운다
  • 기존 성능을 유지하기 위해 인프라 비용이 계속 오른다

 

 

특히 이런 경우라면 아키텍처 불일치 가능성이 큽니다

  • 어떤 “해결책”도 몇 달만 버텨준다
  • 엔지니어링 시간이 기능 개발보다 튜닝에 더 들어간다
  • autovacuum과 유지보수 작업이 점점 부담스럽다
  • 오래된 데이터와 최근 데이터를 접근 패턴이 다른데도 똑같이 다루고 있다

 

여기서 핵심은 진단입니다.

문제를 “쿼리가 나쁘다”로 오판하면 영원히 튜닝만 하게 됩니다.

반대로 “범용 구조 위에서 시계열 워크로드가 돌고 있다”고 정확히 이해하면 선택지가 훨씬 좋아집니다.

 

 


문제를 풀어가는 단계별 접근법

PostgreSQL에서 시계열 압박이 의심된다면, 곧바로 대규모 마이그레이션 공포에 빠질 필요는 없습니다. 단계적으로 접근하면 됩니다.

 

 

1. 먼저 워크로드를 냉정하게 점검하세요

다음 질문부터 시작해 보세요.

  • 데이터가 지속적으로 삽입되는가?
  • 거의 append-only에 가까운가?
  • 쿼리 대부분이 시간 범위 기반인가?
  • 보관 기간이 수개월 또는 수년 단위인가?
  • 사용자나 시스템이 빠른 응답을 요구하는가?

 

여러 항목에서 “예”가 나온다면, 더 이상 이걸 평범한 CRUD 테이블처럼 다루지 않는 것이 좋습니다.

 

 

2. 고통이 어떻게 커지고 있는지 측정하세요

다음 항목을 보세요.

  • 행 수 증가에 따라 쿼리 시간이 어떻게 변했는가
  • 시간에 따라 인덱스 크기가 얼마나 커졌는가
  • 인덱스 유지로 인한 write amplification이 커졌는가
  • 파티션 수와 플래너 동작은 어떤가
  • autovacuum 활동은 어떤가
  • 인프라 비용은 얼마나 늘었는가

 

목표는 단순히 “느리다”를 확인하는 것이 아닙니다.
성장과 함께 반복적으로 느려지는 패턴을 확인하는 것입니다.

그 패턴이 보이면 현재의 해결책은 장기적으로 टिक지 않는다는 뜻입니다.

 

 

3. PostgreSQL 기본 최적화가 이미 충분한지도 확인하세요

아키텍처를 바꾸기 전에 공정하게 점검해야 합니다.

다음은 기본적으로 갖춰져 있어야 합니다.

  • 합리적인 인덱스
  • 무리 없는 쿼리 형태
  • 적절한 하드웨어 기준선
  • 과하지도 부족하지도 않은 유지보수 설정
  • 적절한 테이블 설계

 

이게 하나도 없다면 먼저 그 부분부터 바로잡는 것이 맞습니다. 하지만 이미 꽤 잘해놨는데도 문제가 반복된다면, 그다음 층위는 아키텍처일 가능성이 큽니다.

 

 

4. TimescaleDB를 활성화하세요

워크로드가 시계열에 가깝다고 판단되면, 다음 현실적인 단계는 PostgreSQL 환경에서 TimescaleDB 확장을 활성화하는 것입니다.

보통은 다음과 같은 형태를 사용합니다.

CREATE EXTENSION IF NOT EXISTS timescaledb;

 

물론 실제 설정 절차는 환경에 따라 다릅니다. self-hosted PostgreSQL인지, 관리형 클라우드인지, 컨테이너 배포인지, 확장을 지원하는 호스팅형 PostgreSQL인지에 따라 세부 과정은 달라질 수 있습니다.

 

 

5. 적절한 테이블을 hypertable로 변환하세요

이제 시간 인식 구조가 시작됩니다.

테이블을 선택하고, 시계열의 기준이 되는 timestamp 컬럼을 지정합니다. 보통은 created_at, event_time, timestamp 같은 컬럼이 대상이 됩니다.

여기서 가장 중요한 결정은 올바른 시간 컬럼을 고르는 것입니다. 실제 쿼리가 어떤 시각 기준으로 데이터를 읽는지를 반영해야 합니다.

대부분의 조회가 이벤트 생성 시각 기준이라면 그 컬럼을 써야 합니다. 단지 timestamp 컬럼이 하나 있다는 이유만으로 아무 컬럼이나 고르면 안 됩니다.

 

 

6. 압축과 수명 주기 정책을 붙이세요

테이블이 hypertable로 잘 동작하기 시작하면, 이제 데이터의 “나이”를 기준으로 생각해야 합니다.

  • 최근 데이터는 빠르고 쉽게 접근 가능하게 유지한다
  • 오래된 데이터는 압축한다
  • 필요하면 retention window를 정의한다
  • 차가운 데이터는 더 저렴한 저장소로 옮긴다

 

이 단계부터는 모든 데이터를 똑같이 다루지 않습니다. 저장 비용을 데이터의 실제 가치와 맞추기 시작하는 겁니다.

 

 

7. 반복되는 집계에는 continuous aggregate를 도입하세요

대시보드에서 같은 시간별, 일별 요약을 계속 요청한다면, continuous aggregate는 중복 계산을 크게 줄여줄 수 있습니다.

특히 제품팀, 분석팀, 운영팀이 하나의 이벤트 파이프라인 위에 “대시보드 하나만 더요”를 계속 올리는 상황이라면 더욱 유용합니다.

 

 


 

꼭 알아야 할 개념, 쉬운 말로 다시 정리하면

아키텍처를 더 쉽게 이해할 수 있도록 용어를 평이하게 정리해 보겠습니다.

 

 

시계열 워크로드란 무엇인가요?

시간에 따라 데이터가 쌓이고, 대체로 timestamp와 함께 저장되며, 대부분 시간 범위 기준으로 조회되는 워크로드입니다.

로그, 지표, 이벤트, 텔레메트리, 센서 데이터가 대표적입니다.

 

 

MVCC란 무엇인가요?

MVCC는 여러 사용자가 동시에 안전하게 읽고 쓰도록 PostgreSQL이 사용하는 동시성 제어 방식입니다.

전통적인 데이터베이스에는 매우 뛰어난 방식입니다. 하지만 append-only 시계열 데이터에서는 행 단위 오버헤드가 부담으로 바뀔 수 있습니다.

 

 

autovacuum은 무엇인가요?

autovacuum은 PostgreSQL의 자동 정리 시스템입니다. 업데이트나 삭제 후 남는 오래된 행 버전을 찾아 정리합니다.

일반적인 트랜잭션 앱에서는 필수지만, append-only 시스템에서는 실제로 정리할 것이 거의 없는 큰 테이블을 계속 뒤지게 될 수 있습니다.

 

 

B-tree 인덱스란 무엇인가요?

B-tree 인덱스는 PostgreSQL의 기본 인덱스 구조입니다. 빠른 조회와 정렬된 접근에 강합니다.

많은 상황에서 아주 뛰어나지만, 시계열 쿼리의 핵심인 “최근 데이터 우선”이라는 특성을 구조적으로 이해하진 못합니다.

 

 

hypertable이란 무엇인가요?

hypertable은 TimescaleDB가 시간 기준으로 데이터를 자동으로 chunk로 나누는 구조입니다.

기존 PostgreSQL 파티셔닝의 장점을 얻으면서도, 수동 관리 부담을 크게 줄여줍니다.

 

 

continuous aggregate란 무엇인가요?

continuous aggregate는 증분 방식으로 갱신되는 미리 계산된 집계 결과입니다.

전체를 매번 다시 계산하지 않고, 마지막 갱신 이후 새로 들어온 데이터만 처리합니다.

 

 

data tiering이란 무엇인가요?

data tiering은 최근의 뜨거운 데이터와 오래된 차가운 데이터를 서로 다른 저장 계층에 두는 방식입니다.

매일 쓰는 도구는 책상 위에 두고, 오래된 문서는 더 저렴한 보관함에 넣어두는 것과 비슷합니다. 필요하면 꺼내 쓸 수 있지만, 비싼 공간을 계속 차지하진 않습니다.

 

 


 

실제로 어디에 이런 문제가 나타날까

이제 실무에서 어떤 시스템들이 이런 패턴을 가지는지 살펴보겠습니다.

 

 

모니터링 및 관측성 플랫폼

인프라 지표는 계속 들어오고, 엔지니어는 계속 최근 구간을 조회합니다. 장기 보관은 추세 분석과 장애 분석에 중요합니다.

전형적인 시계열 패턴입니다.

 

 

IoT 및 센서 네트워크

디바이스는 매초, 혹은 몇 밀리초마다 상태를 보냅니다. 대시보드는 최신 수치를 빨리 보여줘야 하고, 과거 데이터는 진단, 예측, 규제 준수에 필요할 수 있습니다.

 

 

제품 분석 파이프라인

사용자 이벤트는 끊임없이 쌓입니다. 제품팀은 시간별, 일별, 코호트 기반 요약을 원합니다. 과거 데이터는 실험 분석과 추세 확인에 필요합니다.

 

 

보안 및 감사 로그

로그는 append-only이고 timestamp 기반이며, 조사 시 특정 시간 구간으로 자주 조회됩니다. 보관 요구사항도 엄격한 경우가 많고, 사고 대응 중에는 조회 속도가 특히 중요합니다.

 

 

금융 및 거래 이벤트 스트림

비즈니스 자체는 트랜잭션 기반이더라도, 이벤트 로그 계층만 떼어놓고 보면 append-heavy, 시간 필터 중심, 장기 보관 지향의 시계열 데이터셋처럼 동작할 수 있습니다.

이 모든 사례에서 데이터는 단순히 “크다”가 아닙니다.
특정한 방식으로 커지고, 특정한 방식으로 조회됩니다.

그리고 바로 그 특성이 아키텍처를 결정해야 합니다.

 

 


 

한계와 꼭 알아야 할 주의사항

시계열 인식 확장은 강력하지만, 마법은 아닙니다. 몇 가지 중요한 경계는 반드시 이해해야 합니다.

 

 

TimescaleDB는 모든 PostgreSQL 워크로드의 만능 대체재가 아닙니다

사용자, 상품, 권한, 자주 수정되는 비즈니스 레코드처럼 전형적인 애플리케이션 테이블을 다루고 있다면, 순정 PostgreSQL이 이미 가장 적합한 선택일 수 있습니다.

핵심은 모든 걸 무작정 변환하는 데 있지 않습니다.
워크로드에 맞는 저장 모델을 선택하는 것이 중요합니다.

 

 

timestamp 컬럼이 있다고 해서 모두 시계열 문제는 아닙니다

많은 테이블이 timestamp를 포함합니다. 그렇다고 해서 모두 hypertable이나 압축이 필요한 건 아닙니다.

중요한 것은 데이터의 성격과 쿼리 패턴입니다.

 

 

스키마 품질은 여전히 중요합니다

TimescaleDB는 저장 구조와 조회 모델을 개선해주지만, 상위 레이어의 잘못된 설계까지 자동으로 해결해주진 않습니다.

예를 들어 다음 문제는 자동으로 사라지지 않습니다.

  • 잘못된 스키마 설계
  • 불필요한 중복
  • 비효율적인 집계 로직
  • 빈약한 보관 정책 설계
  • 비효율적인 애플리케이션 접근 패턴

 

즉, 이것은 강력한 아키텍처 도구이지, 기본적인 설계 원칙을 대신해 주는 만능 해법은 아닙니다.

 

 

planning time은 늘어날 수 있습니다

앞선 예시처럼 hypertable로 바꾼 뒤 planning time이 다소 증가할 수 있습니다.

하지만 그 자체가 꼭 문제라는 뜻은 아닙니다.

실행 시간이 수백 밀리초, 혹은 수초 단위로 줄어든다면 planning cost가 조금 늘어나는 건 충분히 감수할 만합니다.

언제나 전체 그림을 봐야 합니다.

  • planning time
  • execution time
  • 스캔한 행 수
  • 저장 비용
  • 운영 복잡도
  • 장기 확장성

 

 


 

왜 이 이야기가 2026년에 더 중요해졌는가

2026년 현재, 더 많은 기업이 더 많은 “이벤트 형태의 데이터”를 수집하고 있습니다.

AI 시스템은 학습과 평가를 위한 추적 데이터를 필요로 합니다. 제품팀은 실시간 분석을 원합니다. 인프라 팀은 더 깊은 관측성을 요구합니다. 보안 팀은 더 오래 로그를 보관하면서 더 빨리 검색하고 싶어 합니다. 규제 요구사항은 줄어들지 않고, 사용자는 즉시 반응하는 대시보드에 익숙해지고 있습니다.

즉, 시계열 압박은 더 이상 일부 조직만의 특수한 문제가 아닙니다.

과거에는 “특수한 텔레메트리 인프라”처럼 보였던 것이 이제는 제품 개발, 플랫폼, 데이터팀, AI 운영 전반에서 흔한 요구가 되어가고 있습니다.

그만큼 계산도 달라집니다. 일반 PostgreSQL 테이블이 서서히 시계열 시스템으로 변해가는데도 계속 튜닝만으로 버티는 방식은 점점 더 비싸지고, 점점 더 설득력이 떨어집니다.

더 좋은 전략은 패턴을 일찍 알아보고, 거기에 맞는 도구를 쓰는 것입니다.

 

 


마무리 정리

시계열 부하 아래에서 PostgreSQL이 느려지는 것은 대개 엔지니어가 실패했다는 신호가 아닙니다.
그보다 워크로드가 범용 저장 모델의 한계를 넘어섰다는 신호에 가깝습니다.

인덱스, 파티셔닝, 설정 튜닝, 인스턴스 확장은 모두 유효한 수단입니다. 하지만 그것들이 매번 잠깐의 유예만 준다면, 문제는 더 이상 전술적인 것이 아닙니다. 구조적인 것입니다.

TimescaleDB는 꽤 현실적인 중간 지점을 제공합니다. PostgreSQL도 유지하고, SQL도 유지하고, 기존 생태계도 유지하되, 아키텍처를 지금의 데이터 특성에 맞게 업그레이드하는 방식입니다.

생각의 전환은 바로 여기서 시작됩니다.

“이 테이블을 한 분기만 더 버티게 하려면 뭘 해야 하지?”가 아니라,

“이건 정말 어떤 워크로드인가? 그리고 그 워크로드에는 어떤 저장 구조가 맞는가?”

가장 빠른 데이터베이스 최적화는 더 복잡한 쿼리가 아니라, 워크로드와 엔진의 궁합을 맞추는 일일 때가 많습니다.

데이터가 시간 위에 살아간다면, 아키텍처도 시간 축을 이해해야 합니다.

 

 


 

FAQ

1. PostgreSQL 문제가 정말 시계열 문제인지 어떻게 알 수 있나요?

테이블 크기보다 동작 패턴을 보셔야 합니다. 데이터가 계속 삽입되고, 거의 수정되지 않으며, 대부분 시간 범위 기준으로 조회되고, 장기간 보관되며, 빠른 응답이 필요하다면 시계열 워크로드일 가능성이 높습니다.

 

2. TimescaleDB 없이도 PostgreSQL로 시계열 데이터를 처리할 수 있나요?

가능합니다. 특히 규모가 작을 때는 충분히 버틸 수 있습니다. 문제는 시간이 지날수록 테이블, 인덱스, 유지보수 비용, 운영 복잡도가 함께 커진다는 점입니다. 즉, 장기 효율성에서 한계가 드러나기 쉽습니다.

 

3. 왜 인덱스만으로는 영구적으로 해결되지 않나요?

인덱스도 테이블과 함께 커지기 때문입니다. 초기에는 읽기 성능을 크게 개선하지만, 삽입 시 인덱스 유지 비용이 추가되고, 규모가 커질수록 효율이 떨어질 수 있습니다. 특히 큰 시간 범위 스캔이 많은 워크로드에서는 더 그렇습니다.

 

4. 시계열 워크로드에서는 파티셔닝만으로 충분하지 않나요?

파티셔닝은 분명 큰 도움이 됩니다. 다만 일반 PostgreSQL 파티셔닝은 파티션 생성, 삭제, 예외 처리, 플래너 오버헤드 같은 수동 운영 부담을 함께 가져오는 경우가 많습니다.

 

5. 일반 파티션보다 hypertable의 가장 큰 장점은 무엇인가요?

자동화입니다. hypertable은 시간 기준 chunk 분할을 자동으로 처리하고, TimescaleDB 플래너는 관련 없는 chunk를 효율적으로 건너뛸 수 있습니다. 즉, 시간 기반 파티셔닝의 이점을 얻으면서도 수동 관리 부담을 크게 줄일 수 있습니다.

 

6. TimescaleDB를 쓰려면 애플리케이션을 대대적으로 다시 작성해야 하나요?

대체로 그렇지 않습니다. TimescaleDB의 큰 장점 중 하나는 PostgreSQL 위에서 동작한다는 점입니다. 그래서 SQL, PostgreSQL 드라이버, 많은 운영 도구, 기존 애플리케이션 로직을 상당 부분 그대로 유지할 수 있습니다.

 

7. TimescaleDB는 엄청나게 큰 데이터셋에서만 의미가 있나요?

그렇지는 않습니다. 데이터가 클수록 효과가 더 눈에 띄는 건 맞지만, 수백만 행 수준에서도 스캔 행 수나 실행 시간에서 꽤 큰 차이가 나타날 수 있습니다. 워크로드 패턴만 맞는다면 생각보다 이른 시점부터 가치가 드러날 수 있습니다.

 

8. timestamp 컬럼이 있는 테이블은 전부 TimescaleDB로 옮겨야 하나요?

아닙니다. timestamp 컬럼만 있다고 해서 충분한 이유가 되진 않습니다. append-heavy하고, 시간 범위 중심으로 조회되며, 장기 보관이 필요하고, 성장에 따른 성능 민감도가 큰 워크로드에 우선 적용하는 것이 좋습니다.

반응형