메인 콘텐츠로 건너뛰기
2026-09-23PurePlayer성능 실측대규모 라이브러리

PurePlayer 고성능 읽기 미러 실측: 50만 곡 대규모 라이브러리 심층 페이징 1초 이내 단축

PurePlayer는 대규모 음악 라이브러리를 위해 SQLite 기반 읽기 전용 미러 계층을 도입했습니다. 50만 곡 테스트 환경에서 심층 페이징 응답 시간을 1초 이내로 단축하고 A-Z 인덱스 이동을 한 자릿수 밀리초 수준으로 줄였습니다. 초기 인덱스 생성 시 약 30분 소요 및 약 2GB의 로컬 저장 공간이 필요하며 설정에서 직접 관리할 수 있습니다.

PurePlayer 고성능 읽기 미러 실측: 50만 곡 대규모 라이브러리 심층 페이징 1초 이내 단축

보관 곡 수가 수십만 곡 규모에 이르면 첫 화면 스크롤은 매끄럽더라도 목록 깊은 위치 탐색, 색인 문자 이동, 전체 검색 시 조회 지연이 크게 늘어납니다. 500,500곡 기준 데이터셋을 구성해 주요 읽기 경로의 실행 시간을 측정하고, 7.0 버전을 목표로 로컬 읽기 전용 미러 구조를 구축했습니다.

버전 안내: 6.0 버전에서 기초적인 성능 튜닝을 적용했습니다. 본문에 소개하는 고성능 읽기 미러는 7.0 버전을 목표로 내부 테스트를 진행 중이며, 제시된 수치는 모두 내부 개발 빌드 기준입니다.

이 구조는 로컬 SQLite 데이터베이스에 라이브러리의 읽기 전용 파생 사본을 생성합니다. 인덱스를 구축할 때 정렬 키, 필터 속성, A-Z 색인 앵커를 미리 계산하여 디스크에 저장합니다. 원본 데이터는 시스템 기본 저장소가 관리하며, 읽기 전용 미러는 빈번한 검색 요청을 전담하여 필요 시 독립적으로 재구축하거나 정리할 수 있습니다.

성능 실측 데이터

M4 Mac mini 및 500,500곡 데이터셋을 바탕으로 필드 단위 일치성 검증을 거친 결과입니다:

읽기 경로 규모 기본 저장소 미러 가속 비율
곡 목록 스냅샷 생성 10k 3030 ms 21 ms ~143×
목록 페이징 (필터 적용) 10k 1984 ms 8.2 ms ~242×
A-Z 앵커 이동 10k ~2050 ms 0.4–7.6 ms 274–5100×
심층 페이징 (오프셋 45만) 500k ~28분/페이지 ~0.9 s ~1800×
가시 행 유무 확인 (0건 조회) 100k 98.1 s 46 ms ~2100×
스마트 재생목록 다중 조건 후보군 100k 6.9 s 1.27 s ~5.4×
재생목록 화면 / 아티스트 상세 / 첫 렌더링 50k – – 4.5–7.3×

50만 곡 환경에서 기본 읽기 경로는 전체 테이블 순차 탐색에 의존하여 오프셋이 깊어질수록 소요 시간이 급격히 증가했습니다. 40만 번째 곡 부근의 페이지 1건을 가져오는 기준 작업에 약 1668초가 소요되었습니다. 읽기 전용 미러는 동일 위치의 페이징을 0.8~1.4초 수준으로 단축하여 오프셋 깊이와 무관하게 안정적인 응답을 제공합니다.

정렬 태그 및 뷰 호환성

클래식이나 재즈 라이브러리에서는 정렬 태그(Sort Tags)를 활성화하여 "The Beatles"를 B 문자(“Beatles, The”)로 정렬하는 경우가 흔합니다. 미러는 태그를 지원하는 5개 필드에 대해 2벌의 정렬 키를 미리 보관하며, 정렬 태그 전환 시 쿼리문에서 대상 열만 변경하므로 인덱스 재생성 없이 색인 표시줄이 즉시 연동됩니다.

앨범, 아티스트, 작곡가 집계 화면도 미러를 통해 가속을 지원합니다. 단, 집계 화면에서 정렬 태그가 활성화된 경우에는 원본 저장소 경로로 전환되어 엔티티 레벨의 태그 값을 정확히 반영합니다. 쓰기 측 집계 테이블 구현은 향후 로드맵에 포함되어 있습니다.

자원 소비 및 고려 사항

읽기 전용 미러를 운영하려면 인덱스 구축 시간과 디스크 저장 공간이 필요합니다:

  • 구축 시간: 전체 인덱스 생성은 백그라운드 작업으로 진행됩니다. 50만 곡 라이브러리 기준 초기 구축에 약 32분이 소요되며 작업 중에도 기본 모드로 라이브러리를 탐색할 수 있습니다. 5만 곡 규모는 약 35초 만에 완료됩니다.
  • 디스크 공간: 50만 곡 미러 파일은 약 2.1GB의 저장 공간을 차지합니다. 설정 화면에서 정확한 사용량을 확인할 수 있으며, 기능을 끌 때 인덱스를 유지하여 재활성화를 빠르게 하거나 데이터를 완전히 비울 수 있습니다.
  • 쓰기 부하: 곡 메타데이터를 수정할 때 미러에도 동기 방식으로 반영되므로 배치 커밋 1회당 약 20ms의 오버헤드가 발생합니다.

이러한 자원 요구량에 따라 해당 기능은 기본적으로 꺼져 있으며, 사용자가 라이브러리 규모에 맞추어 설정에서 선택적으로 켤 수 있습니다.

핵심 최적화 구조

  • 경량 값 매핑: 기본 경로는 각 행마다 관리 객체를 인스턴스화하지만, 미러는 SQLite 행에서 원시 데이터를 읽어 경량 값 구조체로 투영하여 객체 수명 관리 오버헤드를 낮춥니다.
  • 조건식 푸시다운: 필터링, 정렬, 중복 제거 작업을 데이터베이스 엔진 내부에서 처리하여 메모리에 적재하기 전에 데이터를 압축합니다.
  • 정렬 키 및 인덱스 사전 계산: 정규화된 정렬 키와 A-Z 색인 문자를 사전에 계산하여 저장합니다. 조회 시 키셋 페이징(Keyset Pagination)과 인덱스 스캔을 결합해 깊은 페이지에서도 균일한 응답 속도를 유지합니다.

데이터 일치성 및 구현 세부사항

미러와 기본 저장소의 조회 결과가 완벽히 일치하는지 확인하기 위해 19개 정렬 열, 오름차순/내림차순, 정렬 태그 상태, 복합 필터 조건의 조합을 망라한 검증 스위트를 운용했습니다. 실제 구현 과정에서 몇 가지 경계 조건을 처리했습니다:

  • 널 값 및 빈 문자열 대체 규칙: 정렬 태그 규칙은 태그가 존재하면 태그를 사용하고 없을 때 표시명으로 대체됩니다. Swift의 옵셔널 병합 연산자와 SQL 함수의 빈 문자열 처리 차이를 고려하여, 미러에서는 태그 키와 표시명 키를 별도 열로 분리해 쿼리 생성 시 명시적으로 선택합니다.
  • 특정 필드의 정렬 호환성: 기존 라이브러리의 재생시간 필드는 문자열 사전 순서로 비교되었습니다(예: "120.0"이 "90.0"보다 앞섬). 기존 정렬 순서를 보존하기 위해 텍스트 성격의 열은 전용 텍스트 정렬 키를 생성하고, 순수 숫자 열에만 숫자 인덱스를 구성했습니다.
  • 공백 문자 집합 정의 일치: Swift의 공백 정의(전각 공백, 탭, 줄바꿈 포함)와 표준 SQL 트림 함수의 처리 범위 차이를 해소하기 위해 쓰기 측에 상태 플래그 열을 두어 판단 기준을 일치시켰습니다.
  • 대용량 인덱스 분할 처리: 50만 곡 전량 구축 시 전체 텍스트 검색 트리거의 다중 인덱스 쓰기 작업으로 약 32분이 소요됩니다. 백그라운드 작업이 안정적으로 실행되도록 분할 단위 처리와 중단 후 재개 구조를 적용했습니다.
  • 동시 쓰기 조정: 인덱스 재구축 도중 메타데이터 수정이 발생할 경우를 대비하여 모든 쓰기 작업에 단조 증가 시퀀스 번호를 부여하고, 충돌 처리 절을 통해 최신 변경 사항이 올바르게 반영되도록 했습니다.

측정된 수치는 모두 디버그 빌드 환경에서 수집되었으며, 상세한 벤치마크 로그와 검증 코드는 저장소에 보관되어 있습니다.