random-access-parquet

1 개의 포스트

spotify

온라인 포인트 쿼리를 위한 데이터 레이크 인덱싱 | Spotify 엔지니어링 (새 탭에서 열림)

Spotify처럼 대규모 사용자 데이터를 온라인 서비스와 AI 에이전트가 빠르게 조회해야 하는 환경에서는, 모든 데이터를 Bigtable이나 DynamoDB 같은 KV 저장소에 보관하기 어렵습니다. 데이터 레이크의 Parquet 파일과 객체 스토리지는 충분히 빠르지만, Trino·BigQuery 같은 분석 엔진은 단일 행 조회에도 작업 계획과 스케줄링 오버헤드가 발생합니다. Random Access Parquet(RAP)은 외부 인덱스로 키와 파일·행 위치를 직접 연결하고 필요한 바이트만 범위 읽기하여, 데이터 레이크에서 저지연 포인트 조회를 가능하게 합니다. ## 데이터 레이크에서 온라인 조회가 어려운 이유 - 사용자 청취 이력처럼 데이터 규모가 매우 큰 경우, 특정 사용자의 데이터를 찾기 위해 수천 개의 Parquet 파일을 조사해야 합니다. - 예를 들어 90일 동안 하루 1,000개의 파일이 생성되면 조회 후보가 약 90,000개에 달합니다. - Trino와 BigQuery는 분석 처리량에 최적화되어 있어 단일 사용자 조회에도 수 초의 쿼리 계획 및 작업 스케줄링 시간이 발생할 수 있습니다. - GCS나 S3의 읽기 지연 시간이 계속 줄어들고 있으므로, 병목은 저장소보다 저장소 위의 쿼리 엔진과 파일 내부 탐색 과정으로 이동하고 있습니다. ## 기존 파일 필터링의 한계 - 날짜별 파티션 안에서 사용자 ID를 기준으로 해시 버킷을 만들면 파일명만으로 해당 사용자가 들어 있을 가능성이 없는 파일을 제거할 수 있습니다. - 하루 1,000개 버킷을 사용하면 90,000개 파일이 약 90개로 줄어듭니다. - 사용자 ID 컬럼의 Bloom filter를 메타데이터 저장소에 캐시하면 실제 사용자가 활동한 날짜에 해당하는 약 12개 파일까지 후보를 좁힐 수 있습니다. - 하지만 후보 파일 내부에서 실제 행을 찾으려면 다음과 같은 의존적인 읽기가 필요합니다. - Parquet footer 읽기 - row group 메타데이터 파싱 - 키 컬럼 스캔 - column index와 page index 확인 - 값 컬럼의 해당 페이지 읽기 - 각 단계는 이전 읽기의 결과를 기다려야 하므로 클라우드 저장소에서는 파일과 컬럼마다 여러 번의 왕복 지연이 발생합니다. - 파티션, 버킷, Bloom filter는 “읽을 파일”을 줄일 뿐, 파일 내부에서 “읽을 위치”를 직접 알려주지는 못합니다. ## RAP의 핵심 방식 - RAP는 스캔 대신 조회를 사용합니다. - 외부 인덱스가 특정 키를 다음 위치와 직접 매핑합니다. - 해당 Parquet 파일 - 파일 안의 행 번호 - 선택적으로 해당 값의 개수 - 조회 과정은 다음처럼 단순화됩니다. - 키로 외부 인덱스 조회 - 캐시된 파일 메타데이터로 행 번호를 페이지 위치로 변환 - 필요한 컬럼 페이지에 대해 정확한 범위 읽기 수행 - 인덱스 조회는 O(1)에 가깝고, 여러 범위 읽기를 병렬로 실행할 수 있어 종속적인 읽기 체인을 제거합니다. - 이 원리는 클라우드 객체 스토리지뿐 아니라 SSD와 메모리에서도 동일하게 적용됩니다. 저장장치가 빠를수록 절대 지연은 줄지만, 종속 읽기를 제거하는 효과는 유지됩니다. ## 외부 인덱스의 구조와 특성 - RAP는 기존 Parquet 파일을 수정하지 않고도 적용할 수 있습니다. - 인덱스 빌더는 다음 작업을 수행합니다. - 파일 footer와 필요한 컬럼의 페이지 위치 읽기 - 키 컬럼 스캔 - 키와 파일·행 위치의 매핑 생성 - 인덱스 조각 저장 - 새 데이터가 들어오면 기존 인덱스를 수정하기보다 새로운 인덱스 fragment를 append합니다. - 인덱스는 multimap 구조이므로 하나의 키가 여러 파일과 파티션에 존재할 수 있습니다. - 주요 필드는 다음과 같습니다. - `key`: 사용자 ID 또는 복합 키 - `file`: 대상 Parquet 파일 식별자 - `row numbers`: 해당 파일 안의 행 번호 - `value count`: 페이지네이션에 사용할 값 개수 - 일반적으로 테라바이트 데이터를 인덱싱하면 기가바이트 규모의 인덱스가 생성되고, 페타바이트 데이터에서는 테라바이트 규모가 됩니다. - 대규모 인덱스는 해시 버킷으로 자연스럽게 분산할 수 있습니다. - Parquet의 PageIndex나 Bloom filter가 후보를 좁히는 확률적·보조적 장치라면, 외부 인덱스는 키에 대한 정확한 파일과 행 위치를 반환해 스캔 자체를 없앱니다. ## 준비되지 않은 Parquet의 읽기 비용 - 외부 인덱스가 정확한 행을 알려주더라도, 기존 Parquet에서는 해당 행이 포함된 전체 페이지를 읽어야 할 수 있습니다. - 예를 들어 실제 필요한 데이터가 100바이트뿐이어도 페이지 크기가 4MB라면 4MB 전체를 가져와야 합니다. - 따라서 지연 시간과 비용이 중요한 환경에서는 인덱스뿐 아니라 쓰기 시점의 Parquet 레이아웃 최적화도 필요합니다. ## 준비된 Parquet 파일 최적화 RAP를 위해서는 파일 내부 탐색을 돕는 구조보다 최종적으로 읽어야 할 데이터의 양과 읽기 횟수를 줄이는 구조가 중요합니다. ### 키 데이터를 한곳에 모으기 - 키 기준 정렬을 적용하면 같은 키의 행이 파일 안에서 연속적으로 배치되어 필요한 페이지 수가 줄어듭니다. - 해시 버킷을 사용하면 동일한 키가 각 파티션에서 결정적으로 하나의 파일에 배치되도록 보장할 수 있습니다. - Spark, Scio SMB, Iceberg bucket transform 등이 활용될 수 있습니다. - Co-grouping 방식으로 키마다 하나의 행만 만들고, 관련 데이터를 반복 또는 중첩 컬럼에 저장할 수도 있습니다. - 예를 들어 다음 쿼리는 사용자별 청취 이력을 하나의 배열로 묶습니다. ```sql SELECT user_id, ARRAY_AGG(STRUCT(timestamp, track_uri, duration_ms)) FROM streams GROUP BY user_id ``` - 이 방식은 정렬에 의존하지 않고도 키별 데이터를 집중시킬 수 있으며, 사용자별 포인트 조회에 자연스럽습니다. - 파티션을 지나치게 세분화하면 하나의 키가 많은 파일에 분산됩니다. 예를 들어 일별 파티션은 한 사용자의 연간 데이터를 최대 365개 파일에 나눌 수 있으므로, 더 적절한 파티션 단위를 선택하면 조회 시 파일 수를 줄일 수 있습니다. ## 실용적인 적용 방향 RAP는 분석용 Parquet를 별도의 온라인 서빙 시스템으로 복제하지 않고도 온라인 포인트 조회를 지원하는 접근입니다. 먼저 외부 인덱스로 파일·행 위치를 직접 매핑하고, 지연 시간이 중요한 컬럼에는 키 정렬, 해시 버킷, 사용자별 집계와 같은 쓰기 시점 최적화를 적용하는 것이 효과적입니다. 이를 통해 데이터 저장은 한 번만 유지하면서 분석, ML, 노트북, 온라인 서비스, AI 에이전트가 동일한 데이터를 활용할 수 있습니다.