Redshift 의 구조와 컬럼 지향 저장
이번 포스팅부터는 Amazon Redshift 를 정리해보려 합니다. 대용량 데이터를 SQL 로 분석하는 방법에는 여러 갈래가 있는데, Redshift 는 그것을 관리형 데이터 웨어하우스 라는 방식으로 풉니다. 운영 데이터베이스와는 요구가 정반대인 자리라, 겉으로는 같은 SQL 을 쓰면서도 안은 전혀 다르게 생겼습니다. 그 차이를 따라가보는 것이 이번 시리즈의 목표입니다.
첫 편에서는 Redshift 가 어떤 구조로 되어 있고, 왜 그런 구조를 골랐는지를 다루겠습니다.
1. 데이터 웨어하우스는 무엇이 다른가
1.1. OLTP 와 OLAP
먼저 용도가 다릅니다.
- OLTP(Online Transaction Processing) : 주문을 넣고, 회원 정보를 고치고, 잔액을 조회합니다. 적은 행을 자주, 빠르게 다룹니다. MySQL 이나 PostgreSQL 이 이 자리에 있습니다.
- OLAP(Online Analytical Processing) : "지난 3년간 지역별 월 매출 추이" 같은 것을 봅니다. 아주 많은 행을 훑어 몇 줄로 요약합니다.
같은 SQL 을 쓰지만 요구가 정반대입니다. OLTP 는 특정 행 하나를 빨리 찾아야 하고, OLAP 는 수억 행을 빨리 훑어야 합니다.
1.2. 왜 운영 DB 에서 분석하면 안 되는가
- 성능이 서로를 방해합니다. 무거운 집계 쿼리 하나가 운영 트랜잭션을 느리게 만듭니다.
- 구조가 맞지 않습니다. 정규화된 스키마는 조인이 많아 대량 집계에 불리합니다.
- 이력이 없습니다. 운영 DB 는 현재 값만 들고 있습니다. 분석에는 과거가 필요합니다.
- 여러 출처를 합칠 수 없습니다. 분석은 여러 시스템의 데이터를 함께 봐야 합니다.
그래서 데이터를 따로 모으는 곳을 둡니다. 그것이 데이터 웨어하우스입니다.
2. Redshift 의 뿌리
Redshift 는 AWS 가 2012년에 내놓았습니다. 바닥부터 만든 것은 아니고 ParAccel 이라는 MPP 데이터베이스 기술을 기반으로 합니다. 그리고 ParAccel 은 PostgreSQL 8.0.2 에서 갈라져 나왔습니다.
이 뿌리가 실무에서 그대로 드러납니다.
- PostgreSQL 클라이언트와 드라이버로 접속됩니다. 포트도 기본 5439 로 비슷한 자리입니다.
- 문법 상당 부분이 PostgreSQL 과 같습니다.
- 그런데 최신 PostgreSQL 기능은 대부분 없습니다. 20년 전 버전에서 갈라져 나왔기 때문입니다.
없는 것들을 알아두면 헤매지 않습니다. 인덱스가 없고, 외래 키 제약이 강제되지 않으며(선언은 되지만 검사하지 않는다), 저장 프로시저는 나중에 제한적으로 추가되었고, 배열이나 일부 데이터 타입도 지원하지 않습니다.
특히 인덱스가 없다는 것이 중요합니다. 처음 Redshift 를 쓰는 사람이 가장 먼저 찾는 것이 인덱스인데, 아예 개념이 없습니다. 그 자리를 무엇이 대신하는지가 다음 편의 내용입니다.
3. 구조
3.1. 리더 노드와 컴퓨트 노드
Redshift 클러스터는 두 종류의 노드로 이루어집니다.
- 리더 노드(Leader Node) : 클라이언트 접속을 받고, SQL 을 파싱해 실행 계획을 세우고, 그것을 컴퓨트 노드가 실행할 코드로 컴파일해 배포합니다. 각 노드의 결과를 모아 최종 결과를 돌려줍니다. 데이터를 저장하지 않습니다.
- 컴퓨트 노드(Compute Node) : 실제 데이터를 들고 있고 연산을 수행합니다. 여러 대가 병렬로 돕니다.
노드가 한 대뿐인 구성(single-node)에서는 그 한 대가 두 역할을 겸합니다.
3.2. 슬라이스
여기서 한 겹 더 들어갑니다. 각 컴퓨트 노드는 슬라이스(Slice) 라는 단위로 다시 나뉩니다. 슬라이스는 노드의 메모리와 디스크를 나눠 가진 병렬 처리 단위이고, 노드 타입에 따라 개수가 정해져 있습니다.
병렬도의 실제 단위는 노드가 아니라 슬라이스입니다. 노드가 4대이고 노드당 슬라이스가 8개면 병렬도는 32 입니다. 다음 편에서 볼 데이터 분산도 슬라이스 단위로 이루어집니다.
3.3. MPP
이 구조를 MPP(Massively Parallel Processing) 라고 부릅니다. 데이터를 여러 노드에 나눠 두고, 각 노드가 자기 몫을 동시에 처리한 뒤 결과를 합칩니다. 하둡 시리즈에서 본 MapReduce 와 발상이 비슷합니다. 다만 차이가 분명합니다.
- MapReduce 는 단계마다 디스크에 씁니다. MPP 는 노드 간에 필요한 데이터만 네트워크로 주고받으며 메모리에서 처리합니다.
- MapReduce 는 범용 연산을 표현할 수 있지만, MPP 데이터베이스는 SQL 에 특화되어 있습니다.
- MapReduce 는 장애에 강합니다. 태스크 하나가 죽어도 그것만 다시 돌립니다. MPP 는 대체로 쿼리 전체를 다시 실행합니다.
"SQL 만 잘하면 되는 대신 그것을 아주 빠르게 한다" 가 MPP 의 선택입니다.
4. 컬럼 지향 저장
Redshift 가 빠른 가장 큰 이유입니다. 하둡 시리즈에서 Parquet 과 ORC 를 보며 다뤘던 개념이 여기서 저장 엔진 자체에 들어가 있습니다.
4.1. 행 기반과 열 기반
주문 테이블에 컬럼이 20개 있고 행이 1억 개라고 하겠습니다. 여기서 SELECT SUM(amount) FROM orders 를 실행합니다.
- 행 기반 저장 :
한 행의 모든 컬럼이 붙어 있습니다.
amount하나를 읽으려 해도 그 행 전체를 디스크에서 읽어야 합니다. 20개 컬럼 중 1개만 쓰는데 20개를 다 읽습니다. - 열 기반 저장 :
같은 컬럼의 값들이 모여 있습니다.
amount블록만 읽으면 됩니다. 읽는 양이 20분의 1 로 줄어듭니다.
분석 쿼리는 대체로 전체 컬럼 중 일부만 씁니다. 그래서 이 차이가 그대로 성능 차이가 됩니다.
4.2. 압축이 잘 된다
같은 컬럼의 값은 성질이 비슷합니다. 상태 컬럼이면 몇 가지 값이 반복되고, 날짜 컬럼이면 인접한 값들이 가깝습니다. 그래서 압축률이 훨씬 좋습니다. 압축이 잘 되면 디스크에서 읽어야 할 바이트 수가 줄어들고, 그것이 다시 성능으로 이어집니다. 분석 워크로드에서 병목은 대체로 I/O 이기 때문입니다.
Redshift 는 컬럼마다 인코딩(압축 방식) 을 따로 정할 수 있습니다. 이 부분은 다음 편에서 다루겠습니다.
4.3. 존 맵
Redshift 는 데이터를 1MB 블록 단위로 저장하고, 블록마다 그 안의 최솟값과 최댓값을 따로 기록해둡니다. 이것을 존 맵(Zone Map) 이라고 합니다. WHERE order_date >= '2025-01-01' 같은 조건이 오면, 존 맵을 먼저 보고 조건에 해당하는 값이 있을 수 없는 블록은 아예 읽지 않습니다.
이것이 인덱스가 없는 Redshift 에서 인덱스 비슷한 역할을 합니다. 다만 조건이 있습니다. 데이터가 그 컬럼 기준으로 정렬되어 있어야 효과가 큽니다. 정렬되어 있지 않으면 모든 블록에 모든 값이 섞여 있어, 어느 블록도 건너뛸 수 없습니다.
다음 편에서 볼 정렬 키(Sort Key) 가 바로 이 정렬을 정하는 장치입니다.
4.4. 대신 포기한 것
열 기반 저장은 한 행을 통째로 다루는 일에 불리합니다.
- 단건 조회가 느립니다. 컬럼이 20개면 20곳에서 값을 모아야 합니다.
- 단건 삽입이 비쌉니다. 한 행을 넣으려고 20개 블록을 건드려야 합니다.
- 수정과 삭제가 특히 비쌉니다. 이 부분은 3편에서 자세히 다루겠습니다.
Redshift 를 OLTP 처럼 쓰면 안 되는 이유가 여기에 있습니다. INSERT 를 한 건씩 반복하는 코드는 Redshift 에서 재앙에 가깝습니다.
5. 노드 타입과 아키텍처의 변화
5.1. 예전 구조의 문제
초기 Redshift 는 컴퓨트 노드의 로컬 디스크에 데이터를 저장했습니다. 그러면 저장 용량과 연산 능력이 묶여버립니다. 데이터가 늘어 용량이 부족하면, 연산은 충분한데도 노드를 늘려야 합니다. 반대로 쿼리가 느려 연산을 늘리고 싶으면 필요 없는 디스크까지 함께 사야 합니다. 하둡에서 HDFS 와 연산이 같은 노드에 묶여 있던 것과 같은 제약입니다.
5.2. RA3 와 관리형 저장소
RA3 노드 타입은 이 결합을 끊었습니다. 데이터의 본체는 S3 기반의 Redshift Managed Storage 에 두고, 노드의 로컬 SSD 는 자주 쓰는 데이터의 캐시로 씁니다. 이렇게 되면서 몇 가지가 달라졌습니다.
- 용량과 연산을 따로 늘릴 수 있습니다.
- 데이터 공유(Data Sharing) 가 가능해졌습니다. 저장소가 분리되어 있으니, 복사하지 않고 다른 클러스터에서 같은 데이터를 읽을 수 있습니다.
- 리사이즈가 훨씬 빨라졌습니다.
하둡 에코시스템 정리에서 본 "연산과 저장의 분리" 흐름이 여기서도 똑같이 나타납니다. 오브젝트 스토리지가 충분히 빨라지면서 가능해진 변화입니다.
5.3. Serverless
노드 개수를 정하지 않고 RPU(Redshift Processing Unit) 단위로 필요한 만큼 쓰는 방식입니다. 쿼리가 없으면 연산 비용이 발생하지 않습니다. 워크로드가 간헐적이거나 예측하기 어려울 때 유리하고, 하루 종일 꾸준히 쿼리가 도는 환경에서는 프로비저닝된 클러스터가 더 저렴할 수 있습니다.
6. 쿼리가 실행되는 과정
지금까지 본 것을 이어 붙이면 이렇습니다.
- 파싱과 계획 : 리더 노드가 SQL 을 받아 실행 계획을 세웁니다.
- 코드 생성과 컴파일 : 계획을 C++ 코드로 만들어 컴파일합니다. 인터프리터로 해석하는 대신 기계어로 만들어 실행하기 때문에 빠릅니다. 다만 처음 실행하는 형태의 쿼리는 컴파일 시간이 더해집니다. 같은 쿼리를 두 번째 실행할 때 갑자기 빨라지는 이유입니다. 컴파일 결과는 캐시되고, 클러스터 간에도 공유됩니다.
- 배포와 실행 : 컴파일된 코드가 각 슬라이스로 전달되어 병렬로 실행됩니다.
- 데이터 이동 : 조인이나 집계를 위해 노드 간에 데이터를 주고받는 일이 생깁니다. 이 부분이 성능의 핵심이고, 다음 편의 주제입니다.
- 결과 취합 : 리더 노드가 모아 클라이언트에 돌려줍니다.
4번을 얼마나 줄이느냐가 Redshift 튜닝의 대부분입니다.
7. 조금 더 들어가보기 : 디스크와 CPU, 그리고 네트워크
여기까지 컬럼 지향 저장이 왜 빠른지를 봤습니다. 그런데 "읽는 양이 줄어서 빠르다" 는 설명만으로는 부족한 부분이 있습니다. 디스크와 CPU, 그리고 네트워크에서 실제로 무슨 일이 벌어지는지까지 내려가 보겠습니다.
7.1. 디스크가 싫어하는 것은 양이 아니라 흩어짐이다
저장 장치의 성능은 두 가지로 나뉩니다. 한 번에 얼마나 많이 옮길 수 있는가(대역폭) 와 얼마나 자주 위치를 옮겨 다닐 수 있는가(IOPS) 입니다. 분석 쿼리는 수억 행을 훑으므로 전자가 중요할 것 같지만, 실제로 발목을 잡는 것은 후자인 경우가 많습니다. 여기저기 흩어진 곳을 조금씩 읽는 것이 한곳을 크게 읽는 것보다 훨씬 비쌉니다.
행 기반 저장에서 특정 컬럼 하나만 필요한 경우를 생각해보면 분명해집니다. 필요한 값들이 행 크기만큼씩 떨어져 있습니다. 결국 전체를 다 읽어야 하고, 그중 대부분은 버립니다. 컬럼 기반에서는 같은 컬럼 값이 디스크에 연달아 놓여 있습니다. 한 번 위치를 잡고 길게 이어 읽으면 됩니다. 읽는 양이 줄어드는 것보다, 읽는 방식이 순차로 바뀌는 것이 더 큰 이득입니다.
7.2. 존 맵은 메타데이터만 먼저 읽는다
1MB 블록마다 최솟값과 최댓값을 따로 기록해둔다고 했는데, 이 값들은 데이터와 떨어진 곳에 아주 작게 모여 있습니다. 그래서 조건이 걸린 쿼리는 이런 순서로 처리됩니다.
- 먼저 메타데이터만 읽습니다. 아주 작으므로 거의 공짜입니다.
- 조건에 해당하는 값이 있을 수 없는 블록을 걸러냅니다.
- 남은 블록만 실제로 읽습니다.
인덱스가 하는 일과 비슷해 보이지만 방향이 반대입니다. 인덱스는 "어디에 있는지" 를 찾아가는 장치이고, 존 맵은 "어디에 없는지" 를 알아내 건너뛰는 장치입니다. 앞의 것은 적은 행을 찾는 데 유리하고, 뒤의 것은 큰 구간을 통째로 버리는 데 유리합니다.
정렬이 전제되어야 한다고 한 이유도 여기서 분명해집니다. 값이 흩어져 있으면 모든 블록의 최솟값과 최댓값이 비슷해져서, 어느 블록도 버릴 수 없습니다. 메타데이터는 있는데 쓸모가 없어집니다.
7.3. 압축은 I/O 를 CPU 와 맞바꾸는 것이다
압축을 풀려면 CPU 를 씁니다. 그런데도 압축하는 편이 빠른 이유는, 분석 워크로드에서 디스크가 CPU 보다 훨씬 느리기 때문입니다. CPU 를 조금 더 쓰고 읽을 바이트를 크게 줄이면 전체가 빨라집니다. 다만 이 균형은 상황에 따라 뒤집힙니다. 이미 캐시에 올라와 있는 데이터를 반복해서 훑는다면, 압축을 푸는 비용만 남습니다. 인코딩마다 압축률과 해제 속도가 다른 것도 이 때문이고, 숫자에 AZ64 를, 문자열에 ZSTD 를 권장하는 것도 각 타입에서 그 균형이 가장 잘 맞는 지점이기 때문입니다.
여기서 한 가지 더 중요한 성질이 있습니다. 일부 인코딩은 압축된 상태 그대로 비교 연산을 할 수 있습니다. 사전 방식으로 압축했다면 값을 되돌리지 않고 사전 번호끼리 비교하면 됩니다. 압축이 단순히 저장 공간을 줄이는 것을 넘어 연산량 자체를 줄이는 경우입니다.
7.4. 컴파일과 벡터 처리
앞에서 리더 노드가 실행 계획을 C++ 코드로 만들어 컴파일한다고 했습니다. 이것이 왜 중요한지는 그 반대를 생각해보면 보입니다. 전통적인 데이터베이스는 실행 계획을 한 행씩 해석하며 처리합니다. 행 하나마다 "다음 연산자를 호출하고, 타입을 확인하고, 함수를 부르는" 과정을 반복합니다. 1억 행이면 이 오버헤드가 1억 번 발생합니다.
컴파일된 코드는 이 해석 단계를 통째로 없앱니다. 타입과 연산이 이미 코드에 박혀 있으므로 값만 흘려보내면 됩니다. 여기에 한 번에 여러 값을 묶어 처리하는 방식이 더해집니다. 같은 컬럼의 값들은 타입도 크기도 같으므로, 메모리에 나란히 놓인 값들을 덩어리로 가져와 한꺼번에 다룰 수 있습니다. CPU 캐시에도 유리합니다. 다음에 쓸 값이 방금 읽은 값 바로 옆에 있기 때문입니다.
컬럼 지향 저장이 디스크에서만 유리한 것이 아니라 CPU 안에서도 유리한 이유가 이것입니다. 처음 실행하는 형태의 쿼리가 느린 것도 이제 설명됩니다. 그 쿼리를 위한 코드를 만들어 컴파일하는 시간이 앞에 붙기 때문입니다. 캐시된 뒤로는 사라집니다.
7.5. 노드 사이를 오가는 비용
MPP 에서 가장 비싼 일은 디스크도 CPU 도 아니고 네트워크입니다. 조인을 하려면 짝이 되는 행들이 같은 곳에 있어야 합니다. 그렇지 않으면 한쪽을 옮겨야 하는데, 이때 벌어지는 통신은 모든 슬라이스가 모든 슬라이스에게 보내는 형태입니다. 슬라이스가 32개면 통신 경로가 수백 갈래로 갈라집니다.
그래서 실행 계획에서 재분배와 방송이 보이는지를 확인하는 것이 중요합니다. 방송은 특히 비쌉니다. 한 테이블을 모든 노드에 통째로 복사하는 것이라, 그 테이블이 크면 네트워크가 곧바로 병목이 됩니다. 앞으로 볼 분산 키 설계는 결국 이 통신을 아예 일어나지 않게 만드는 작업입니다. 조인 키로 함께 분산되어 있으면, 각 슬라이스가 자기 안에서 짝을 찾아 끝냅니다. 네트워크를 타지 않습니다.
7.6. 그래서 단건 조회가 느린다
지금까지의 설명을 뒤집으면 Redshift 가 못하는 일도 설명됩니다. 한 행 전체를 가져오려면 컬럼 개수만큼 서로 다른 곳을 읽어야 합니다. 컬럼이 20개면 20곳입니다. 순차 읽기의 이점이 사라지고, 앞서 말한 임의 접근이 됩니다.
압축도 불리하게 작용합니다. 한 값을 꺼내려고 그 값이 속한 블록을 통째로 풀어야 합니다. 대량으로 훑을 때 가장 빠른 구조가, 한 건을 찾을 때는 가장 느린 구조가 됩니다. 저장 방식을 고르는 것이 곧 워크로드를 고르는 것이라는 사실이 여기서 분명해집니다.
결론
Redshift 를 처음 보면서 가장 놀란 것은 인덱스가 없다는 사실이었습니다. 관계형 데이터베이스의 성능은 인덱스에서 나온다고 알고 있었는데, 그것이 아예 없는 데이터베이스가 있다는 것이 낯설었습니다. 그런데 이유를 따라가보니 납득이 되었습니다. 인덱스는 적은 행을 빨리 찾기 위한 장치인데, 수억 행을 훑는 것이 일인 시스템에서는 쓸모가 없습니다. 대신 읽는 양 자체를 줄이는 쪽으로 문제를 풀었고, 그것이 컬럼 저장과 존 맵이었습니다.
두 번째로 인상 깊었던 것은 PostgreSQL 8.0 에서 갈라져 나왔다는 사실이었습니다. 덕분에 접속과 문법은 익숙한데 기대하는 기능이 없는 경우가 생깁니다. 오래된 조상에서 갈라져 나온 뒤 전혀 다른 방향으로 자란 셈인데, "익숙해 보이는 것이 더 헷갈릴 수 있다" 는 것을 보여주는 예 같았습니다.
세 번째는 RA3 에서 연산과 저장을 분리한 것이었습니다. 하둡 에코시스템을 정리하면서 HDFS 에서 오브젝트 스토리지로 무게 중심이 옮겨가는 흐름을 봤는데, Redshift 도 정확히 같은 길을 걸었습니다. 서로 다른 진영의 시스템이 같은 제약에 부딪혀 같은 해법으로 수렴하는 것이 흥미로웠습니다.
공부 참고 자료
📑 2. Redshift - 데이터 웨어하우스 시스템 아키텍처