#Backend

슬로우 쿼리 해결기: 함수형 인덱스로 비트 연산 쿼리 최적화하기

슬로우 쿼리 해결기: 함수형 인덱스로 비트 연산 쿼리 최적화하기
01

Summary

비트 연산 때문에 죽어가는 쿼리를 살려라! MySQL 함수형 인덱스로 타임아웃 해결하기

LINE VOOM 팀이 7개월간 사투하며 찾아낸 비트 플래그 쿼리 최적화와 실전 DB 운영 노하우

데이터 효율을 위해 비트 플래그를 사용했지만, 정작 쿼리 성능 때문에 골머리를 앓는 백엔드 개발자들을 위한 실전 가이드입니다. MySQL 8.0의 함수형 인덱스를 활용해 비트 연산의 한계를 극복하고, 수십만 건의 데이터를 단숨에 처리하는 최적화 과정을 상세히 담았습니다. 단순한 기술 적용을 넘어 대규모 샤드 환경에서 겪은 복제 지연과 논리 오류 등 생생한 트러블슈팅 경험을 공유합니다.

  • 01비트 연산 결과를 인덱싱하는 함수형 인덱스의 핵심 원리와 설정 방법
  • 02쿼리 플랜을 최적화하기 위해 비트 연산을 10진수 동등 비교로 변환하는 트릭
  • 03Online DDL과 샤드별 점진적 적용을 통한 무중단 DB 스키마 변경 전략
  • 04복제 지연으로 인한 사용자 경험 저하 문제 해결 및 캐시 정책 조정 사례
  • 05비트 연산의 AND/OR 논리 함정을 피하는 실무 관점의 주의사항

RECOMMENDATION

비트 마스크나 JSON 필드 등 동적 컬럼을 검색 조건으로 사용하며 성능 저하를 겪고 있는 시니어 백엔드 엔지니어에게 추천합니다. 대규모 트래픽 환경에서 무중단으로 DB 인덱스를 구조조정해야 하는 개발자라면 반드시 읽어보아야 할 사례입니다.

The Problem

비트 플래그(bit(64))를 사용하는 컬럼에 비트 연산 조건을 적용할 경우, 인덱스가 컬럼 원본 값을 기준으로 정렬되어 있어 인덱스를 타지 못하고 전체 행을 스캔하게 됩니다. 이로 인해 수십만 건의 데이터를 보유한 헤비 유저의 프로필 조회 시 30초 이상의 타임아웃이 발생하는 슬로우 쿼리 문제가 발생했습니다.

The Solution

MySQL 8.0.13에서 도입된 함수형 인덱스(Functional Index)를 사용하여 비트 연산 결과를 미리 인덱싱하고, 쿼리 조건을 비트 truthy 체크에서 10진수 동등 비교 방식으로 변경했습니다. 서비스 중단을 방지하기 위해 Online DDL 방식을 채택하고 수십 개의 샤드 테이블에 대해 점진적 롤아웃을 진행했습니다.

The Result

스캔 행 수가 테스트 기준 805건에서 31건으로 약 97% 감소하였으며, 실무 환경에서 30초에 달하던 타임아웃 오류가 완전히 제거되었습니다. 시스템 응답 속도가 안정화되면서 모니터링 시스템의 이상치 알림 혼선이 해소되는 정성적 효과도 거두었습니다.

Trade-off

새로운 인덱스 추가로 인해 인덱스 저장 용량이 약 24% 증가했으며, 대규모 DDL 작업 과정에서 마스터-슬레이브 간 복제 지연이 발생하여 일시적인 데이터 정합성 이슈가 있었습니다. 또한 비트 마스크의 AND/OR 논리 차이에 따라 쿼리를 엄격하게 관리해야 하는 복잡도가 늘어났습니다.

03

Key Concepts

Concept · 01

함수형 인덱스 (Functional Index)

MySQL 8.0.13부터 지원되는 기능으로, 컬럼의 원본 값이 아닌 특정 함수나 표현식의 결과를 인덱스로 저장하는 방식입니다.

  • 내부적으로 숨겨진 가상 컬럼을 생성하여 비트 연산 등의 결과를 미리 계산
  • 쿼리의 표현식이 인덱스 정의와 정확히 일치해야만 최적화 효과 발생
Concept · 02

Online DDL

데이터베이스 운영 중에 테이블 구조를 변경할 때, 읽기/쓰기 작업을 차단하지 않고 무중단으로 수행하는 기술입니다.

  • 임시 테이블 생성 후 데이터를 복사하고 이름을 변경하는 방식으로 작동
  • 대규모 데이터 이동 시 마스터와 슬레이브 간의 복제 지연(Replication Lag) 유발 가능성 존재
Concept · 03

비트 플래그 (Bit Flag)

하나의 컬럼에 여러 개의 이진 상태 값을 비트 단위로 압축하여 저장하는 데이터 구조입니다.

  • 64비트 정수형 하나에 최대 64개의 상태 정보를 담을 수 있어 공간 효율성 극대화
  • 조회 시 비트 연산(&)이 필요하며 일반 인덱스로는 성능 최적화가 어려움