sharding

3 개의 포스트

슬로우 쿼리 해결기: 함수형 인덱스로 비트 연산 쿼리 최적화하기 (새 탭에서 열림)

LINE VOOM 서비스는 헤비 유저의 프로필 조회 시 비트 연산 조건으로 인해 발생하는 30초 이상의 슬로우 쿼리 문제를 해결하기 위해 MySQL 8.0.13의 함수형 인덱스(functional index)를 도입했습니다. 기존의 비트 연산 조건은 인덱스를 무력화하여 수십만 건의 데이터를 풀 스캔하게 만들었으나, 연산 결과를 인덱싱하고 이를 10진수 동등 비교 쿼리로 전환함으로써 스캔 범위를 3% 수준으로 대폭 축소했습니다. 이 과정에서 무중단 인덱스 생성과 점진적 롤아웃을 통해 운영 안정성을 확보하며 시스템 성능을 성공적으로 최적화했습니다. ### 비트 연산 조건에 의한 성능 저하 원인 * LINE VOOM의 포스트 메타데이터 테이블은 `category_flag`와 `access_flag`라는 `bit(64)` 타입 컬럼에 서비스 상태 정보를 압축 저장합니다. * 특정 상태를 필터링하기 위해 `category_flag & 0x0100`과 같은 비트 연산을 사용했는데, 이는 인덱스에 저장된 원본 값이 아닌 연산 결과를 조건으로 활용하므로 기존 인덱스를 타지 못합니다. * 결과적으로 특정 사용자의 모든 포스트를 일일이 순회하며 연산을 수행해야 했고, 포스트가 수십만 건에 달하는 헤비 유저의 경우 쿼리 타임아웃이 빈번하게 발생했습니다. ### 함수형 인덱스를 활용한 최적화 설계 * MySQL 8.0.13에서 도입된 함수형 인덱스는 표현식의 결과를 가상 컬럼 형태로 저장하고 인덱싱하는 기능을 제공합니다. * 팀은 `user_id`와 비트 연산 표현식을 결합한 복합 인덱스 `idx_user_premium_searchable (user_id, (category_flag & 0x0100), (access_flag & 0x0001))`를 설계했습니다. * 이 방식은 기존 테이블 스키마를 크게 변경하지 않으면서도 특정 비트 패턴에 대해 즉각적인 인덱스 조회를 가능하게 합니다. ### 인덱스 활성화를 위한 쿼리 튜닝 및 검증 * 개발 환경 테스트 결과, 단순히 비트 연산을 포함하는 것만으로는 인덱스가 작동하지 않는 것을 확인했습니다. * 인덱스를 타기 위해서는 쿼리의 표현식이 인덱스 정의와 완벽히 일치해야 하며, 특히 비트 연산 결과를 **10진수 값으로 동등 비교**(`= 256`)했을 때만 인덱스가 정상적으로 적용되었습니다. * 최적화 후 스캔 행 수가 805건에서 31건으로 줄어드는 등 극적인 성능 향상을 보였으며, 인덱스 용량 증가(약 24%)는 운영 환경에서 수용 가능한 수준으로 판단되었습니다. ### 운영 환경 적용 및 시행착오 해결 * **무중단 인덱스 생성**: DBA 팀과 협업하여 Online DDL 방식을 사용함으로써 서비스 중단 없이 수십 개의 테이블에 인덱스를 추가했습니다. * **복제 지연 대응**: 인덱스 생성 중 발생한 마스터-슬레이브 복제 지연으로 인해 최신 글이 보이지 않는 이슈가 발생했으나, 캐시 만료 시간을 단축하여 사용자 불편을 최소화했습니다. * **논리 오류 방지**: 비트 연산을 동등 비교로 바꿀 때 OR 조건과 AND 조건의 의미 차이로 인한 버그를 발견했습니다. 이를 방지하기 위해 동적 설정을 활용해 샤드별로 쿼리를 점진적으로 적용하며 안전하게 검증을 마쳤습니다. 비트 연산과 같이 컬럼 가공이 필요한 조건을 상시로 사용해야 한다면, MySQL의 함수형 인덱스는 스키마 변경 부담을 최소화하면서 성능을 극대화할 수 있는 강력한 도구입니다. 다만, 인덱스가 적용되는 정확한 표현식(10진수 비교 등)을 사전에 검증하고, 대규모 테이블 작업 시 발생할 수 있는 복제 지연과 논리적 조건 변화를 세심하게 모니터링하는 것이 중요합니다.

디스코드가 수조 개의 (새 탭에서 열림)

디스코드(Discord)는 수십억 개의 메시지를 효율적으로 검색하기 위해 엘라스틱서치(Elasticsearch)를 기반으로 한 고성능 검색 인프라를 구축했습니다. 초기 설계는 길드(서버)나 DM 단위로 데이터를 샤딩하여 쿼리 속도를 높이고 운영 관리를 용이하게 하는 데 집중했으며, 리소스를 절약하기 위해 지연 인덱싱(Lazy Indexing) 방식을 채택했습니다. 하지만 서비스가 급격히 성장함에 따라 초기 설계의 효율성보다는 대규모 확장성 측면에서 구조적인 한계가 나타나기 시작했습니다. **엘라스틱서치 기반의 샤딩 및 저장 구조** - 데이터의 논리적 네임스페이스인 인덱스를 두 개의 엘라스틱서치 클러스터에 분산 배치하여 관리했습니다. - 모든 메시지는 길드(Guild) 또는 DM ID를 기준으로 샤딩되어, 특정 그룹의 메시지가 동일한 인덱스에 저장되도록 설계했습니다. - 이러한 샤딩 전략은 관련 데이터를 한데 모아 쿼리 실행 속도를 최적화하고, 클러스터 규모를 제어 가능한 수준으로 유지하는 데 기여했습니다. **메시지 큐와 대량 인덱싱 처리** - 모든 사용자가 검색 기능을 사용하는 것이 아니라는 점에 착안하여, 메시지를 즉시 처리하지 않고 필요할 때 인덱싱하는 '지연 인덱싱' 방식을 도입했습니다. - 메시지 큐를 구축하여 워커(Worker)들이 메시지 덩어리(Chunks)를 가져와 처리할 수 있도록 시스템을 구성했습니다. - 엘라스틱서치의 벌크 인덱싱(Bulk-indexing) 기능을 활용하여 대량의 메시지를 한 번에 처리함으로써 인덱싱 효율을 극대화했습니다. 초기 설계 단계에서 데이터 응집도와 리소스 효율성을 고려한 샤딩 및 인덱싱 전략은 시스템의 성능과 비용 효율성을 잡는 데 효과적입니다. 그러나 서비스의 성장에 따라 기존 아키텍처에서 발생하는 병목 현상을 미리 예측하고, 성능 저하가 시작되는 시점에 맞추어 인프라 고도화를 준비하는 과정이 필수적입니다.

Introducing Glommio, a thread-per-core crate for Rust and Linux (새 탭에서 열림)

현대적인 클라우드 비용 절감과 성능 최적화를 위해서는 단순히 코드의 병목점을 찾는 것을 넘어, 하드웨어 성능을 극대화할 수 있는 '코어당 스레드(thread-per-core)' 아키텍처로의 전환이 필요합니다. 이 아키텍처는 애플리케이션의 꼬리 지연 시간(tail latency)을 최대 71%까지 개선할 수 있지만, 구현이 복잡하고 개발 생산성을 떨어뜨릴 수 있다는 단점이 있습니다. Datadog은 이러한 문제를 해결하기 위해 Rust 개발자들이 코어당 스레드 모델을 쉽게 구현할 수 있도록 돕는 오픈소스 라이브러리 'Glommio'를 제안합니다. **기존 멀티스레드 모델의 한계와 비용** * 전통적인 멀티스레딩 방식은 여러 스레드가 동일한 데이터에 접근할 때 데이터 정합성을 보장하기 위해 락(Lock)을 사용하며, 이는 실행을 멈추고 대기하는 시간을 발생시켜 비용을 증가시킨다. * 스레드 간 컨텍스트 스위칭(Context Switching)은 약 5마이크로초의 비용이 드는데, 이는 io_uring과 같은 최신 커널 인프라의 I/O 처리 시간(4마이크로초 미만)보다 길다. 즉, 스레드 전환 비용이 실제 I/O 작업보다 비싸지는 역전 현상이 발생한다. * 기존의 비동기 프로그래밍 방식 또한 파일 I/O 등을 처리할 때 내부적으로 스레드 풀을 사용해야 하는 경우가 많아 완전한 효율성을 달성하기 어렵다. **코어당 스레드(Thread-per-core)의 동작 원리** * 각 CPU 코어에 단 하나의 스레드만 할당하고 이를 특정 코어에 고정(Pinning)함으로써, OS 스케줄러에 의한 스레드 이동과 컨텍스트 스위칭을 원천적으로 차단한다. * 하드웨어 인터럽트나 시스템 에이전트와 같은 외부 작업을 위해 특정 코어를 전용으로 분리하고, 나머지 코어를 애플리케이션 스레드에 할당하여 간섭을 최소화한다. * 모든 작업은 협력적 스케줄링(Cooperative Scheduling) 방식으로 동작하며, 실행 중인 작업이 명시적으로 제어권을 양보하거나 완료될 때까지 CPU를 점유하여 선점형 스케줄링의 오버헤드를 없앤다. **데이터 샤딩을 통한 락 프리(Lock-free) 구현** * 각 스레드가 전체 데이터의 특정 부분집합(Shard)만을 담당하도록 설계하여, 서로 다른 스레드가 동일한 데이터에 접근할 가능성을 차단한다. * 예를 들어 특정 카프카 파티션이나 데이터베이스 키 범위별로 담당 스레드를 지정함으로써, 스레드 간의 데이터 공유를 최소화한다. * 데이터 업데이트 작업이 해당 스레드 내에서 자연스럽게 직렬화(Serialized)되므로, 복잡하고 비용이 큰 락(Lock) 메커니즘 없이도 안전하게 데이터를 조작할 수 있다. **Glommio를 통한 Rust 애플리케이션 최적화** * Glommio는 C++의 Seastar 프레임워크와 유사한 철학을 Rust 환경에 가져와, 개발자가 코어당 스레드 아키텍처의 복잡한 세부 사항을 직접 다루지 않고도 고성능 애플리케이션을 작성할 수 있게 한다. * 성능 민감도가 높은 대규모 데이터 스토어(Datastores)나 고처리량 메트릭 수집 시스템을 운영하는 팀에게 Glommio는 하드웨어 효율성과 개발 편의성을 동시에 잡을 수 있는 실질적인 대안이 된다.