커버링 인덱스(Covering Index)란 무엇인가?

커버링 인덱스는 쿼리가 필요로 하는 모든 컬럼을 포함하는 인덱스를 말합니다. 이 인덱스를 사용하면 테이블의 실제 데이터 페이지에 접근하지 않고도 인덱스만으로 원하는 데이터를 조회할 수 있어 디스크 I/O를 절약할 수 있습니다.

MySQL 공식 문서 정의:

*"쿼리에서 검색되는 모든 컬럼을 포함하는 인덱스입니다. 인덱스 값을 전체 테이블 행을 찾는 포인터로 사용하는 대신, 쿼리는 인덱스 구조에서 값을 반환하여 디스크 I/O를 절약합니다. InnoDB는 MyISAM보다 더 많은 인덱스에 이 최적화 기술을 적용할 수 있습니다. 왜냐하면 InnoDB의 보조 인덱스에는 기본 키 컬럼도 포함되어 있기 때문입니다. InnoDB는 해당 트랜잭션이 끝날 때까지 트랜잭션에 의해 수정된 테이블에 대한 쿼리에 이 기술을 적용할 수 없습니다."*

단일 컬럼 인덱스와 다중 컬럼 인덱스 모두 커버링 인덱스로 활용될 수 있습니다. 적절한 인덱스 설계와 쿼리 작성으로 이 최적화 기법을 최대한 활용할 수 있습니다.


커버링 인덱스를 사용하는 이유

  • 디스크 I/O 감소: 인덱스만으로 데이터를 조회하므로 디스크 접근 횟수가 줄어듭니다.
  • 성능 향상: 디스크 I/O 감소로 쿼리 응답 속도가 빨라집니다.
  • 잠금 경합 감소: 테이블 데이터 페이지에 대한 접근이 줄어들어 잠금 경합이 감소합니다.

클러스터드 인덱스와 비클러스터드 인덱스

클러스터드 인덱스 (Clustered Index)

  • 정의: 테이블의 실제 데이터가 인덱스와 동일한 구조로 저장되는 인덱스입니다.
  • 특징:
    • 테이블 당 하나만 존재합니다.
    • 기본 키(primary key)가 클러스터드 인덱스로 사용됩니다.
  • 장점:
    • 인덱스를 통해 바로 데이터에 접근하므로 조회 속도가 빠릅니다.

비클러스터드 인덱스 (Non-clustered Index)

  • 정의: 인덱스는 별도의 구조로 저장되고, 인덱스 엔트리는 데이터의 물리적 위치를 가리킵니다.
  • 특징:
    • 테이블 당 여러 개의 비클러스터드 인덱스를 가질 수 있습니다.
  • 단점:
    • 인덱스를 통해 데이터를 찾은 후 실제 데이터 페이지를 다시 조회해야 하므로 추가적인 I/O가 발생합니다.

MySQL 공식 문서 인용:

*"클러스터드 인덱스를 통해 행에 접근하는 것은 빠릅니다. 왜냐하면 인덱스 검색이 행 데이터가 있는 페이지로 직접 연결되기 때문입니다. 테이블이 큰 경우, 클러스터드 인덱스 아키텍처는 인덱스 레코드와 다른 페이지에 행 데이터를 저장하는 스토리지 구조와 비교했을 때 디스크 I/O 작업을 절약할 수 있습니다."*


커버링 인덱스 사용과 미사용 시 성능 비교

실험: 100만 건의 데이터로 임시 테이블 생성 후 성능 비교

  1. 데이터 준비

    CREATE TABLE test_table (
        id INT PRIMARY KEY,
        col1 VARCHAR(100),
        col2 VARCHAR(100),
        col3 VARCHAR(100)
    );
    
    INSERT INTO test_table (id, col1, col2, col3)
    SELECT
        t1.number AS id,
        MD5(RAND()) AS col1,
        MD5(RAND()) AS col2,
        MD5(RAND()) AS col3
    FROM
        numbers AS t1  -- numbers 테이블은 1부터 1,000,000까지의 숫자를 가진 테이블이라고 가정
    LIMIT 1000000;
  2. 인덱스 설정

    • 커버링 인덱스 생성

      CREATE INDEX idx_col1_col2 ON test_table (col1, col2);
    • 인덱스 미사용

      • 인덱스를 생성하지 않음.
  3. 쿼리 실행 및 성능 측정

    • 커버링 인덱스 사용 시

      SELECT col1, col2 FROM test_table WHERE col1 = 'some_value';
      • 인덱스만으로 쿼리를 처리하여 빠른 응답 속도를 보입니다.
    • 인덱스 미사용 시

      SELECT col1, col2 FROM test_table WHERE col1 = 'some_value';
      • 전체 테이블 스캔이 발생하여 응답 시간이 느려집니다.
  4. 결과

    • 커버링 인덱스를 사용하면 쿼리 성능이 현저히 향상됩니다.
    • 인덱스를 사용하지 않으면 대량의 데이터를 처리할 때 성능 저하가 발생합니다.

GROUP BY에서의 커버링 인덱스 활용

GROUP BY 절에서도 커버링 인덱스를 활용하여 성능을 개선할 수 있습니다.

  • 인덱스 생성

    CREATE INDEX idx_col1_col2 ON test_table (col1, col2);
  • 쿼리 실행

    SELECT col1, COUNT(*) FROM test_table GROUP BY col1;
  • 설명

    • col1col2를 포함하는 인덱스를 생성하여 GROUP BY 시 인덱스만으로 결과를 도출합니다.
    • 디스크 I/O를 최소화하여 쿼리 성능을 높입니다.

참고 자료

  1. MySQL 공식 문서 - InnoDB Index Types
  2. MySQL 공식 문서 - Covering Indexes

'Database > My SQL' 카테고리의 다른 글

MySQL Index 정리 및 팁  (0) 2022.05.16

데이터 복구 과정에서 학습한 내용 기록

이번에 회사에서 AWS RDS로 DB 서버를 단계적으로 이전하는 과정에서 프로덕션 데이터가 소실되는 상황이 발생하였습니다. 다행히 바이너리 로그(binlog)를 보관하는 기간 내에 있어서 MySQL binlog를 활용하여 데이터를 복구할 수 있었습니다. 이때 학습한 내용을 기록 차원에서 남깁니다.


데이터 복구 과정

  1. 프로덕션 서버에서 binlog 확인

    SHOW BINARY LOGS;

    결과는 다음과 같았습니다:

  2. +---------------+-----------+ | Log_name | File_size | +---------------+-----------+ | binlog.000015 | 724935 | | binlog.000016 | 733481 | +---------------+-----------+

  3. 먼저, 프로덕션 서버에 binlog가 존재하는지 확인하였습니다.

  4. binlog 덤프 진행

    mysqlbinlog --read-from-remote-server --host=<프로덕션 호스트> --raw --user=<계정> --password \
    binlog.000015 binlog.000016

    이 명령어는 지정한 binlog 파일들을 로컬 디렉토리로 가져옵니다.

  5. 프로덕션 호스트로부터 binlog를 로컬로 덤프하기 위해 mysqlbinlog 유틸리티를 사용하였습니다.

  6. binlog에서 INSERT 및 UPDATE 추출

    mysqlbinlog binlog.000015 binlog.000016 --database=<DB명> --base64-output=DECODE-ROWS -v > output.sql
    • --database 옵션은 특정 데이터베이스만 대상으로 합니다.
    • --base64-output=DECODE-ROWS-v 옵션은 binlog 이벤트를 사람이 읽을 수 있는 SQL 형식으로 변환합니다.
  7. 덤프한 binlog 파일에서 특정 데이터베이스의 DML 문을 추출하기 위해 다음과 같이 파싱하였습니다.

  8. SQL 문 실행을 통한 데이터 복구

  9. 추출된 output.sql 파일에서 필요한 INSERT와 UPDATE 문을 확인하고, 이를 데이터베이스에 적용하여 데이터를 복구하였습니다.


향후 개선 사항

이번 복구 과정을 통해 몇 가지 개선할 점을 발견하였습니다.

  1. 자동화 도구 활용

    python binlog2sql.py -h <프로덕션 호스트> -u <계정> -p<비밀번호> \
    --start-file='binlog.000015' --stop-file='binlog.000016' \
    --databases=<DB명> --output-file=output.sql
  2. 수동으로 binlog를 파싱하는 대신, binlog2sql과 같은 도구를 사용하면 더욱 효율적으로 복구 작업을 수행할 수 있습니다. 이 도구는 binlog를 직접 파싱하여 DDL과 DML 쿼리문을 생성해주므로, 수작업을 최소화할 수 있습니다.

  3. Point-in-Time Recovery 활용

    mysqlbinlog --start-datetime="2023-10-01 00:00:00" --stop-datetime="2023-10-01 23:59:59" \
    binlog.000015 binlog.000016 | mysql -u <계정> -p<비밀번호> -h <데이터베이스 호스트> <DB명>
  4. MySQL의 Point-in-Time Recovery 기능을 활용하면 특정 시점까지의 데이터를 복구할 수 있습니다. 이를 통해 binlog를 사용하여 원하는 시점까지 데이터를 복원할 수 있으며, 수동으로 SQL 문을 파싱하지 않아도 됩니다.


복구 진행 시 참고한 자료

  1. MySQL 공식 문서 - mysqlbinlog
  2. https://dev.mysql.com/doc/refman/8.0/en/mysqlbinlog.html
  3. Amazon RDS에서 MySQL 바이너리 로그 액세스하기
  4. https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_LogAccess.MySQL.Binarylog.html
  5. Percona 블로그 - binlog2sql을 활용한 포인트 인 타임 복구
  6. https://www.percona.com/blog/binlog2sql-binlog-to-raw-sql-conversion-and-point-in-time-recovery/

추가로 알아두면 좋은 점

  • binlog_format 설정 확인

    SHOW VARIABLES LIKE 'binlog_format';
  • MySQL의 binlog_format 설정에 따라 binlog에 기록되는 내용이 달라집니다. 일반적으로 복구를 위해서는 ROW 또는 MIXED 포맷이 더 유용합니다.

  • binlog 보관 기간 설정

    SET GLOBAL expire_logs_days = 7;
  • 데이터 복구를 대비하여 binlog의 보관 기간(expire_logs_days)을 적절히 설정하는 것이 중요합니다.

  • 정기적인 백업 수행

  • binlog를 이용한 복구는 최후의 수단일 수 있으므로, 정기적인 데이터베이스 백업을 통해 데이터 소실에 대비해야 합니다.


결론

이번 경험을 통해 binlog의 중요성과 이를 활용한 데이터 복구 방법에 대해 깊이 있게 이해하게 되었습니다. 향후에는 자동화 도구와 MySQL의 내장 기능을 적극 활용하여 더욱 효율적으로 데이터 복구를 진행할 수 있을 것으로 기대합니다.

+ Recent posts