클라우드 Mac에서 검증하는 iOS SQLite 복합 인덱스와 스캔 예산

클라우드 Mac에서 검증하는 iOS SQLite 복합 인덱스와 스캔 예산

테스트 데이터베이스에서는 고객의 주문 목록이 즉시 열리는데, 과거 데이터를 가져오고 나면 눈에 띄게 느려지는 경우가 있습니다. 클라우드 Mac에서 여러 프로젝트를 납품하는 개발 스튜디오라면, 기기를 바꿔 가며 실행 시간을 재기보다 고정된 데이터로 쿼리 계획을 먼저 검증하는 편이 낫습니다. 전자는 누락된 인덱스를 찾는 데 적합하지만, 후자는 캐시나 부하의 변동을 최적화 효과로 오인하기 쉽습니다. 이 글에서는 주문 조회 쿼리 하나로 명령행 사전 점검부터 실제 iOS 연결에서의 재검증까지 이어지는 절차를 정리합니다.

1. 인덱스보다 먼저 쿼리의 동작 조건을 확정하기

목록은 특정 고객의 완료된 주문만 조회하고, 생성 시각 내림차순으로 정렬하되 시각이 같으면 번호 내림차순으로 정렬하며, 한 번에 최대 스무 건만 반환하도록 정합니다. 테스트에서는 실행 시간뿐 아니라 필터링, 정렬 순서, 반환 건수를 함께 확인해야 합니다.

매개변수는 세 가지 경우를 준비합니다. 주문이 있는 고객, 존재하지 않는 고객, 주문이 집중된 고객입니다. 작고 균등한 데이터는 로직 검증에는 적합하지만 실제 업무의 데이터 분포를 대표하지는 못합니다. 운영 데이터에서는 소수의 대형 고객이 전체 기록의 대부분을 차지할 수 있습니다.

sqlite_version(), 테이블 구조, 인덱스 정의, 통계 정보 생성 방식을 기록합니다. Mac에 기본 제공되는 명령행 도구와 앱이 사용하는 엔진은 서로 다를 수 있습니다. 특히 프로젝트에 SQLite를 별도로 통합했다면 두 환경의 실행 계획을 같은 검증 결과로 취급해서는 안 됩니다.

쿼리 계획은 옵티마이저가 내린 선택이지, 형식이 고정된 텍스트 인터페이스가 아닙니다. 버전 간 검증에서 출력 전체가 글자 단위로 일치해야 한다는 통과 조건을 두지 마세요.

2. 고객 데이터베이스에 접근하지 않는 고정 테스트 데이터 만들기

터미널에서 다음 스크립트를 실행합니다. 별도의 임시 디렉터리를 만들며 운영 데이터베이스에는 연결하지 않습니다. 데이터의 번호가 고정되어 있어 반복 실행해도 논리적으로 동일한 내용이 만들어집니다. SQL 파일과 데이터베이스를 같은 위치에 두므로 납품 시에도 쉽게 다시 실행할 수 있습니다.

set -eu
work="$(mktemp -d)"
cat > "$work/check.sql" <<'SQL'
.bail on
SELECT sqlite_version();
CREATE TABLE orders (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  status TEXT NOT NULL,
  created_at INTEGER NOT NULL
);
WITH RECURSIVE seq(x) AS (
  SELECT 1
  UNION ALL
  SELECT x + 1 FROM seq WHERE x < 10000
)
INSERT INTO orders
SELECT x, x % 100,
       CASE WHEN x % 3 = 0 THEN 'done' ELSE 'open' END,
       x / 10
FROM seq;
EXPLAIN QUERY PLAN
SELECT id, created_at FROM orders
WHERE customer_id = 7 AND status = 'done'
ORDER BY created_at DESC, id DESC LIMIT 20;
CREATE INDEX orders_customer_status_time
ON orders(customer_id, status, created_at DESC, id DESC);
ANALYZE;
EXPLAIN QUERY PLAN
SELECT id, created_at FROM orders
WHERE customer_id = 7 AND status = 'done'
ORDER BY created_at DESC, id DESC LIMIT 20;
SQL
sqlite3 "$work/fixture.sqlite" < "$work/check.sql"
printf '%s\n' "$work"

먼저 터미널 출력을 저장한 다음, 실제 조회 쿼리를 실행해 결과를 확인합니다. 여기서는 두 번째 정렬 키가 실제로 적용되는지 검증할 수 있도록 생성 시각이 같은 기록을 의도적으로 만들었습니다. 가장 최근 기록 한 건만 확인하면 같은 시각의 기록들이 반환되는 순서가 바뀌는 문제를 놓칠 수 있습니다.

3. 계획 문자열을 외우지 말고 접근 경로 읽기

인덱스가 없으면 보통 주문 테이블을 스캔하고 정렬용 임시 구조를 만드는 계획이 나타납니다. 인덱스를 만든 뒤에는 고객과 상태를 기준으로 검색하는지, 추가 정렬이 여전히 필요한지 확인합니다. 구체적인 표현은 SQLite 버전에 따라 달라지므로 출력이 항상 같을 것이라고 가정해서는 안 됩니다.

복합 인덱스에는 동등 조건에 쓰이는 열을 먼저 놓고 정렬 열을 뒤에 둡니다. 엔진이 대상 범위를 따라 읽으면서 처음 스무 건을 가져오게 하려는 구성입니다. 이 인덱스는 예제 쿼리에서 조회하는 열도 모두 포함합니다. 나중에 크기가 큰 필드를 조회 대상에 추가하면 커버링 인덱스로 동작하지 않을 수 있으므로, 이전 보고서를 계속 인용하지 말고 다시 검증해야 합니다.

SCAN이 보인다고 바로 실패로 판단하지 마세요. 커버링 인덱스를 순서대로 스캔하는 것과 대형 테이블을 한 행씩 읽으며 필터링하는 것은 다릅니다. 반대로 SEARCH가 나타난다고 작업량이 적다는 보장도 없습니다. 선택도가 낮은 조건이라면 여전히 많은 기록을 읽을 수 있습니다.

인덱스는 자주 실행되는 목록 조회에 적합한 수단이지, 모든 필드에 무차별적으로 추가할 대상은 아닙니다. 쓰기와 저장 공간 비용이 늘어나므로, 변경 사항을 제출할 때 삽입·수정·삭제 테스트도 함께 실행해 읽기 최적화가 납품 시 합의한 성능·용량 예산을 벗어나게 하지 않았는지 확인합니다.

4. 실제 연결에서 스캔 예산 확인하기

명령행 검증을 통과하면 고정 테스트 데이터를 앱 테스트 환경으로 가져오고, 프로젝트에서 실제로 사용하는 데이터베이스 래퍼로 같은 매개변수화 쿼리를 실행합니다. 바인딩하는 매개변수의 타입을 동일하게 유지하고, 문장은 끝까지 실행해야 합니다. 첫 번째 행을 가져온 직후에 집계를 멈춰서는 안 됩니다.

기저 연결에 접근할 수 있다면 실행 전에 sqlite3_stmt_status로 SQLITE_STMTSTATUS_FULLSCAN_STEP을 0으로 초기화하고, 실행 후 값을 읽습니다. 별도로 SQLITE_STMTSTATUS_SORT를 사용해 정렬 연산 횟수도 확인합니다. 전자는 전체 테이블 스캔과 관련된 스텝 수를 집계합니다. 후자는 실행 시간도, 정렬한 행 수도 아닙니다.

두 카운터가 모두 0이라고 해서 쿼리 비용이 없는 것은 아닙니다. 인덱스 접근이 대량으로 발생할 수도 있습니다. SQLITE_STMTSTATUS_VM_STEP을 추가로 기록하고 반환 건수와 함께 판단할 수 있습니다. 래퍼가 문장 핸들을 노출하지 않는다면 먼저 테스트용 어댑터 계층을 추가하세요. 업무 로그의 실행 시간을 파싱한 값을 스캔 통계인 것처럼 사용해서는 안 됩니다.

예산은 일반적인 임계값을 그대로 가져오는 대신, 고정 테스트 데이터에서 검토를 마친 기준값을 바탕으로 정해야 합니다. prepared statement를 재사용할 때 이전 실행의 값이 누적되지 않도록 매번 측정 전에 카운터를 초기화합니다.

5. 데이터 분포 변화도 납품 점검에 포함하기

검수용 데이터베이스는 최소 두 종류를 유지합니다. 균등 분포 데이터베이스로 기본 접근 경로를 확인하고, 편향된 분포의 데이터베이스로 대형 고객을 재현합니다. 두 데이터베이스 모두에서 스무 건 상한, 안정적인 정렬, 빈 결과, 서로 다른 상태 매개변수를 점검합니다.

통계 정보는 쿼리 계획에 영향을 줍니다. 데이터베이스를 새로 만든 직후의 결과와 ANALYZE 실행 후의 결과를 각각 기록해야 합니다. 제품 실행 중 통계 정보를 생성하지 않는다면, 통계가 갖춰진 이상적인 상태만 검수해서는 안 됩니다. 보기 좋은 계획을 얻으려고 인덱스를 강제로 지정해 실제 선택을 가리는 일도 피해야 합니다.

마지막으로 SQL, 엔진 버전, 데이터 분포 설명, 인덱스 정의, 실행 계획과 카운터 값을 보관합니다. 새로운 회귀 문제가 생기면 실행 시간부터 보기 전에 이 근거들을 먼저 비교합니다. 스튜디오에서 클라우드 Mac을 교체하거나 프로젝트 일정이 연장된 뒤에도 같은 고정 테스트 데이터를 바탕으로 설명할 수 있어야 합니다. 변경으로 접근 경로가 달라진 것인지, 아니면 우연히 캐시가 워밍업된 상태에서 실행된 것인지 구분할 수 있어야 합니다.

자주 묻는 질문

쿼리 계획에 SCAN이 있으면 무조건 실패인가요?

아닙니다. 작은 테이블이나 커버링 인덱스의 순차 스캔은 적절할 수 있습니다. 접근 대상, 결과 규모, 고정 데이터의 스캔 예산을 함께 확인하세요.

Mac의 sqlite3 결과를 iOS 검증으로 대신해도 되나요?

안 됩니다. 명령행은 SQL과 인덱스 설계 사전 점검용입니다. 최종 검증은 앱이 사용하는 SQLite 엔진과 연결 설정에서 수행해야 합니다.

프로젝트 일정에 맞춰 필요한 기간만 이용하세요

임시 빌드, 납품 주간의 추가 용량, 여러 고객 프로젝트를 위한 장비 분리에 적합합니다. 팀의 빌드 작업이 연중 계속되고 이미 보유 장비가 있다면, 구매와 임대 비용을 먼저 비교해 보세요.

VMSSD 클라우드 Mac은 가상 머신이 아닌 독립형 물리 Mac mini를 제공합니다. 일·주·월·분기 단위로 이용 기간을 선택할 수 있으며, 만료 후 연장하지 않으면 서비스가 종료됩니다. 이용 가능한 모델과 노드는 콘솔에서 실시간으로 확인할 수 있습니다.

읽고 난 뒤, 다음 단계를 계획하세요

프로젝트 일정과 빌드 부하를 먼저 확인한 다음 모델과 이용 기간을 선택하세요. 처음 이용하신다면 주문 체크리스트부터 살펴보세요.