카테고리 : MySQL/기술노트

Adaptive Hash Index의 원리와 비활성화를 검토해야 하는 상황

InnoDB Adaptive Hash Index가 반복 조회를 가속하는 방식과 경합이 커질 때 비활성화를 검토하는 기준을 정리한다.

저자: MySQL 기술 노트 작성: 2026.06.05 약 13분 7,474자
다운로드

1. 왜 Adaptive Hash Index를 별도로 보아야 하는가

InnoDB의 기본 인덱스 구조는 B+Tree다. B+Tree는 범위 검색, 정렬된 순회, prefix 검색, 동등 조건 검색을 모두 안정적으로 처리한다. 그러나 특정 인덱스 page와 특정 key 조합이 매우 반복적으로 조회되는 환경에서는 매번 B+Tree root에서 branch page를 거쳐 leaf page까지 내려가는 비용도 무시할 수 없다. 메모리에 있는 page를 읽는 작업이라도 latch 획득, page 탐색, record 비교가 반복되면 CPU와 동기화 비용이 누적된다.

Adaptive Hash Index, 이하 AHI는 이런 반복 탐색을 줄이기 위해 InnoDB가 내부적으로 구성하는 메모리 기반 hash lookup 계층이다. 사용자가 별도 CREATE INDEX 문으로 만드는 인덱스가 아니라, InnoDB가 buffer pool에 올라온 index page의 접근 패턴을 관찰하다가 자주 쓰이는 key prefix를 hash entry로 만든다. 조건이 맞으면 다음 조회는 B+Tree 경로를 일부 생략하고 hash table을 통해 대상 record에 더 빠르게 접근할 수 있다.

운영자가 AHI를 이해해야 하는 이유는 양면성 때문이다. 읽기 중심 OLTP에서 hot point lookup이 많으면 AHI는 CPU 비용을 낮출 수 있다. 반대로 여러 CPU core가 동시에 같은 AHI partition 또는 내부 latch를 두고 경쟁하면, 원래 줄이려던 탐색 비용보다 hash index 유지와 latch 경합 비용이 더 커질 수 있다. 특히 “쿼리는 단순한데 CPU 사용률과 mutex 대기가 높다”는 형태의 성능 문제에서는 AHI가 원인 후보가 될 수 있다.

2. B+Tree 탐색과 AHI의 위치

일반적인 secondary index 동등 조건 조회를 단순화하면 다음과 같다.

  1. optimizer가 사용할 index를 선택한다.
  2. InnoDB가 해당 index의 root page에서 탐색을 시작한다.
  3. branch page를 따라 leaf page를 찾는다.
  4. leaf record에서 secondary key와 primary key 값을 확인한다.
  5. 필요한 경우 primary key로 clustered index를 다시 조회한다.

AHI는 이 과정에서 B+Tree 탐색 일부를 hash lookup으로 우회한다. 다만 모든 조회가 AHI를 타는 것은 아니다. AHI는 buffer pool에 있는 page를 기반으로 만들어지며, InnoDB가 반복 접근 패턴을 감지해야 한다. 또한 hash entry는 실제 row 사본을 저장하는 것이 아니라, 이미 buffer pool에 있는 index page와 record 위치를 빠르게 찾기 위한 보조 구조에 가깝다.

flowchart TD
    A[SQL 동등 조건 조회] --> B[Optimizer가 B+Tree index 선택]
    B --> C{AHI entry가 유효한가?}
    C -- 예 --> D[Adaptive Hash Index hash lookup]
    D --> E[buffer pool의 index page/record 접근]
    C -- 아니오 --> F[B+Tree root/branch/leaf 탐색]
    F --> E
    E --> G{secondary index만으로 충분한가?}
    G -- 예 --> H[결과 반환]
    G -- 아니오 --> I[primary key로 clustered index 조회]
    I --> H

이 그림에서 핵심은 AHI가 optimizer가 선택하는 논리적 실행 계획을 대체하지 않는다는 점이다. EXPLAIN 결과에 “AHI 사용” 같은 접근 방식이 명시적으로 나타나지 않는다. optimizer는 여전히 B+Tree index를 기준으로 비용을 계산하고, InnoDB storage engine 내부에서 실제 page 접근 시 hash lookup 기회가 있으면 이를 활용한다.

3. AHI가 만들어지는 방식과 내부 비용

AHI는 이름 그대로 adaptive하다. 서버 시작 시 전체 인덱스를 hash table로 미리 만들지 않는다. InnoDB는 index page 접근 패턴을 관찰하면서 특정 page의 특정 prefix가 반복적으로 탐색된다고 판단하면 hash entry를 만든다. 이 방식은 메모리와 구축 비용을 필요한 곳에 집중시키는 장점이 있다.

그러나 adaptive하다는 것은 유지 비용도 계속 발생한다는 뜻이다. AHI는 buffer pool의 page 상태와 밀접하게 연결된다. page가 eviction되거나, B+Tree page split/merge가 일어나거나, record가 이동하거나, 인덱스 구조가 바뀌면 관련 hash entry는 무효화되거나 재구성되어야 한다. 즉 AHI는 순수한 읽기 캐시가 아니라, InnoDB page 생명주기와 함께 관리되는 내부 자료구조다.

운영 관점에서 비용은 크게 세 가지다.

  • 메모리 비용: hash table과 entry가 buffer pool 주변 메모리를 사용한다.
  • CPU 비용: 반복 접근을 감지하고 hash entry를 만들며 무효화한다.
  • 동기화 비용: 여러 thread가 AHI를 조회하거나 수정할 때 내부 latch 또는 partition 경합이 발생한다.

MySQL 8.0에서는 AHI가 partitioning되어 과거보다 단일 latch 병목이 완화되었지만, 모든 경합이 사라지는 것은 아니다. innodb_adaptive_hash_index_parts로 partition 수를 조절할 수 있으나, 이 값은 시작 시점에 결정되는 성격이 강하고 동적 튜닝만으로 모든 워크로드를 해결하는 만능 스위치는 아니다. AHI 자체를 사용할지 여부는 innodb_adaptive_hash_index로 제어한다.

4. 현재 설정과 상태를 확인하는 기본 SQL

다음 쿼리는 MySQL 8.0 테스트 인스턴스에서도 실행 가능한 기본 점검이다. 운영 서버에서는 결과 값을 단독으로 판단하지 말고, CPU 사용률, query latency, performance_schema 대기 이벤트, workload 특성과 함께 해석해야 한다.

SELECT VERSION() AS mysql_version;
SHOW VARIABLES LIKE 'innodb_adaptive_hash_index';
SHOW VARIABLES LIKE 'innodb_adaptive_hash_index_parts';
SHOW GLOBAL STATUS LIKE 'Innodb_adaptive_hash%';

실행 결과(MySQL 8.0.46):

mysql> SELECT VERSION() AS mysql_version;

+---------------+
| mysql_version |
+---------------+
| 8.0.46        |
+---------------+
1 row in set (0.00 sec)

mysql> SHOW VARIABLES LIKE 'innodb_adaptive_hash_index';

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_adaptive_hash_index | ON    |
+----------------------------+-------+
1 row in set (0.01 sec)

mysql> SHOW VARIABLES LIKE 'innodb_adaptive_hash_index_parts';

+----------------------------------+-------+
| Variable_name                    | Value |
+----------------------------------+-------+
| innodb_adaptive_hash_index_parts | 8     |
+----------------------------------+-------+
1 row in set (0.00 sec)

mysql> SHOW GLOBAL STATUS LIKE 'Innodb_adaptive_hash%';

Empty set (0.00 sec)

adaptive_hash_searchesadaptive_hash_searches_btree는 AHI 관련 탐색 통계를 보는 출발점이다. 전자는 hash index를 통한 탐색, 후자는 B+Tree 경로로 수행된 탐색으로 해석할 수 있다. 다만 이 값만으로 “AHI가 반드시 효과적이다” 또는 “반드시 꺼야 한다”고 결론 내리면 위험하다. 서버가 오래 떠 있었는지, workload가 최근에 바뀌었는지, buffer pool warmup 상태가 어떤지에 따라 누적 counter의 의미가 달라진다.

AHI와 관련될 수 있는 wait instrument는 서버 버전과 빌드, 활성화 상태에 따라 다르게 보일 수 있다. 다음 쿼리는 현재 인스턴스에서 hash 또는 adaptive hash 관련 instrument 이름이 보이는지 확인하는 안전한 조회다.

SELECT NAME, ENABLED, TIMED
FROM performance_schema.setup_instruments
WHERE NAME LIKE '%adaptive%hash%'
   OR NAME LIKE '%btr%search%'
   OR NAME LIKE '%hash%'
ORDER BY NAME
LIMIT 20;

실행 결과(MySQL 8.0.46):

mysql> SELECT NAME, ENABLED, TIMED
    -> FROM performance_schema.setup_instruments
    -> WHERE NAME LIKE '%adaptive%hash%'
    ->    OR NAME LIKE '%btr%search%'
    ->    OR NAME LIKE '%hash%'
    -> ORDER BY NAME
    -> LIMIT 20;

+---------------------------------------------------------------+---------+-------+
| NAME                                                          | ENABLED | TIMED |
+---------------------------------------------------------------+---------+-------+
| memory/innodb/adaptive hash index                             | YES     | NULL  |
| memory/innodb/hash0hash                                       | YES     | NULL  |
| memory/innodb/ut_lock_free_hash_t                             | YES     | NULL  |
| memory/innodb/ut0lock_free_hash                               | YES     | NULL  |
| memory/mysys/SAFE_HASH_ENTRY                                  | YES     | NULL  |
| memory/sql/db_worker_hash_entry                               | YES     | NULL  |
| memory/sql/hash_index_key_buffer                              | YES     | NULL  |
| memory/sql/hash_join                                          | YES     | NULL  |
| memory/sql/HASH_ROW_ENTRY                                     | YES     | NULL  |
| memory/sql/Owned_gtids::sidno_to_hash                         | YES     | NULL  |
| memory/sql/THD::handler_tables_hash                           | YES     | NULL  |
| wait/io/file/sql/hash_join                                    | YES     | YES   |
| wait/synch/cond/sql/Relay_log_info::replica_worker_hash_cond  | NO      | NO    |
| wait/synch/mutex/innodb/buf_pool_zip_hash_mutex               | NO      | NO    |
| wait/synch/mutex/innodb/hash_table_mutex                      | NO      | NO    |
| wait/synch/mutex/innodb/lock_free_hash_mutex                  | NO      | NO    |
| wait/synch/mutex/sql/hash_filo::lock                          | NO      | NO    |
| wait/synch/mutex/sql/Relay_log_info::replica_worker_hash_lock | NO      | NO    |
| wait/synch/rwlock/mysys/SAFE_HASH::lock                       | NO      | NO    |
| wait/synch/rwlock/sql/LOCK_system_variables_hash              | NO      | NO    |
+---------------------------------------------------------------+---------+-------+
20 rows in set (0.00 sec)

위 쿼리가 빈 결과를 반환하더라도 “AHI가 없다”는 뜻은 아니다. performance_schema instrument 명칭과 노출 범위는 MySQL minor version에 따라 차이가 있고, 일부 내부 경합은 더 일반적인 mutex/rwlock 이름으로 집계될 수 있다. 따라서 운영 진단에서는 events_waits_summary_global_by_event_name, events_waits_summary_by_instance, CPU profiling, 쿼리 지연 분포를 함께 보아야 한다.

5. AHI가 도움이 되는 워크로드

AHI가 효과를 내기 쉬운 패턴은 비교적 명확하다.

첫째, hot key 또는 hot key range의 반복 동등 조회가 많다. 예를 들어 사용자 세션, 계정 상태, 상품 재고처럼 같은 index prefix를 매우 자주 읽는 OLTP workload에서는 B+Tree 탐색 경로를 줄이는 이점이 생길 수 있다. 단, 결과 row가 너무 많거나 범위 스캔이 지배적인 경우 AHI의 이점은 줄어든다.

둘째, working set이 buffer pool에 잘 머문다. AHI는 buffer pool에 올라온 index page를 대상으로 한다. 디스크 I/O가 병목이고 page miss가 잦은 환경에서는 AHI보다 buffer pool 크기, 인덱스 설계, 쿼리 조건, I/O latency가 먼저다. AHI는 disk read를 마법처럼 없애는 기능이 아니라, 메모리에 있는 B+Tree page 탐색 비용을 줄이는 기능이다.

셋째, thread 수가 적당하고 AHI 경합이 낮다. hash lookup 자체는 빠르지만, 내부 자료구조 접근에는 동기화가 필요하다. 읽기 thread 수가 많아질수록 AHI 이점과 latch 경합 위험이 동시에 커진다. 작은 서버에서 좋아 보인 설정이 더 큰 core 수의 서버에서 나빠질 수 있는 이유가 여기에 있다.

6. 비활성화를 검토해야 하는 상황

AHI를 끄는 결정은 “성능이 나쁘니 일단 꺼 본다”가 아니라, 관찰 가능한 증거와 되돌릴 계획을 갖고 해야 한다. 다음 상황에서는 비활성화 또는 partition 조정 실험을 검토할 만하다.

6.1 CPU와 동기화 대기가 높고 I/O 병목은 낮은 경우

스토리지 read latency가 낮고 buffer pool hit ratio도 높지만, SQL latency가 증가하고 CPU 사용률이 높다면 메모리 내부 경합을 의심할 수 있다. 이때 performance_schema에서 InnoDB mutex/rwlock 대기가 두드러지고, workload가 단순 point lookup 위주라면 AHI가 후보가 된다.

다만 단순히 CPU가 높다는 이유만으로 AHI를 끄면 안 된다. 쿼리 수 자체가 증가했거나, 잘못된 실행 계획으로 row examination이 늘었거나, application connection pool이 과도하게 커졌을 수도 있다. AHI는 원인 후보 중 하나로 놓고 비교 실험해야 한다.

6.2 write가 많고 index page 변화가 잦은 경우

AHI는 index page와 record 위치에 의존한다. 대량 write, page split, purge, secondary index 변경이 많은 workload에서는 hash entry 유지와 무효화 비용이 커질 수 있다. 특히 읽기 이점은 크지 않은데 write로 인한 구조 변경이 많은 경우에는 AHI가 순이익을 내기 어렵다.

예를 들어 대량 적재, queue table, time-series성 insert/update, 인덱스가 많은 테이블의 지속적인 변경 부하에서는 AHI보다 redo log, change buffer, flush, purge, secondary index 설계가 더 중요한 병목일 수 있다. 이때 AHI 관련 경합까지 보인다면 비활성화 실험의 가치가 있다.

6.3 benchmark와 운영 workload의 결과가 다른 경우

AHI 효과는 접근 locality에 민감하다. 균일 분포 random key benchmark에서는 이점이 작거나 경합만 보일 수 있고, 실제 서비스에서는 일부 hot key 때문에 이점이 클 수 있다. 반대로 개발 환경에서는 빠르게 보였지만 운영에서는 thread 수와 데이터 변경량이 커져 경합이 나타날 수 있다.

따라서 AHI 판단은 단일 microbenchmark보다 운영에 가까운 workload replay, read/write 비율, 동시성 수준, buffer pool warm 상태를 반영해야 한다.

7. 설정 변경과 실험 절차

innodb_adaptive_hash_index는 동적으로 변경할 수 있는 변수로 취급되지만, 운영에서는 변경 범위를 명확히 하고 짧은 관찰 창을 잡아야 한다. 다음 SQL은 현재 값을 확인하고 세션에서 실수로 바꾸지 않도록 global 변수임을 명시한다.

SHOW VARIABLES LIKE 'innodb_adaptive_hash_index';
SHOW VARIABLES LIKE 'innodb_adaptive_hash_index_parts';

실행 결과(MySQL 8.0.46):

mysql> SHOW VARIABLES LIKE 'innodb_adaptive_hash_index';

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_adaptive_hash_index | ON    |
+----------------------------+-------+
1 row in set (0.00 sec)

mysql> SHOW VARIABLES LIKE 'innodb_adaptive_hash_index_parts';

+----------------------------------+-------+
| Variable_name                    | Value |
+----------------------------------+-------+
| innodb_adaptive_hash_index_parts | 8     |
+----------------------------------+-------+
1 row in set (0.00 sec)

운영 변경의 예시는 다음과 같다. 실제 운영에서는 change window, rollback 기준, 모니터링 지표를 정한 뒤 수행한다.

SET GLOBAL innodb_adaptive_hash_index = OFF;
SHOW VARIABLES LIKE 'innodb_adaptive_hash_index';
SET GLOBAL innodb_adaptive_hash_index = ON;
SHOW VARIABLES LIKE 'innodb_adaptive_hash_index';

실행 결과(MySQL 8.0.46):

mysql> SET GLOBAL innodb_adaptive_hash_index = OFF;

Query OK, 0 rows affected (0.00 sec)

mysql> SHOW VARIABLES LIKE 'innodb_adaptive_hash_index';

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_adaptive_hash_index | OFF   |
+----------------------------+-------+
1 row in set (0.00 sec)

mysql> SET GLOBAL innodb_adaptive_hash_index = ON;

Query OK, 0 rows affected (0.00 sec)

mysql> SHOW VARIABLES LIKE 'innodb_adaptive_hash_index';

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_adaptive_hash_index | ON    |
+----------------------------+-------+
1 row in set (0.00 sec)

위 예제는 기능 토글이 가능한지를 확인하기 위한 축소 예제다. 실제 운영에서 바로 OFF로 고정하라는 의미가 아니다. 실험 전후로 최소한 다음 지표를 비교한다.

  • 주요 API 또는 batch의 p50/p95/p99 latency
  • QPS/TPS와 read/write 비율
  • CPU 사용률과 run queue
  • InnoDB row read, logical read, buffer pool hit 관련 지표
  • performance_schema wait event의 상위 항목
  • AHI 관련 status counter 증가율
  • 오류율, timeout, deadlock 등 부작용

실험은 가능하면 같은 시간대의 유사 traffic에서 진행한다. workload가 급변하는 시간대에 켜고 끄면 AHI 효과와 트래픽 변화가 섞여 해석이 어려워진다.

8. 진단 쿼리: 누적 counter를 rate로 해석하기

상태 변수는 누적값이므로 한 번 조회한 숫자만 보아서는 부족하다. 아래 쿼리는 현재 누적 counter를 정렬해 보여 주는 기본 형태다. 운영에서는 30초 또는 60초 간격으로 두 번 수집해 차이를 rate로 계산하는 것이 좋다.

SELECT
    NAME,
    SUBSYSTEM,
    COUNT,
    STATUS,
    COMMENT
FROM information_schema.INNODB_METRICS
WHERE NAME IN (
    'adaptive_hash_searches',
    'adaptive_hash_searches_btree'
);

실행 결과(MySQL 8.0.46):

mysql> SELECT
    ->     NAME,
    ->     SUBSYSTEM,
    ->     COUNT,
    ->     STATUS,
    ->     COMMENT
    -> FROM information_schema.INNODB_METRICS
    -> WHERE NAME IN (
    ->     'adaptive_hash_searches',
    ->     'adaptive_hash_searches_btree'
    -> );
+------------------------------+---------------------+-----------+---------+---------------------------------------------------------+
| NAME                         | SUBSYSTEM           | COUNT     | STATUS  | COMMENT                                                 |
+------------------------------+---------------------+-----------+---------+---------------------------------------------------------+
| adaptive_hash_searches       | adaptive_hash_index | 212813120 | enabled | Number of successful searches using Adaptive Hash Index |
| adaptive_hash_searches_btree | adaptive_hash_index |  16138111 | enabled | Number of searches using B-tree on an index search      |
+------------------------------+---------------------+-----------+---------+---------------------------------------------------------+
2 rows in set (0.01 sec)

AHI가 켜져 있는데 adaptive_hash_searches 증가가 거의 없고 adaptive_hash_searches_btree만 증가한다면, 현재 workload에서는 hash lookup 이점이 작을 수 있다. 반대로 hash searches가 많아도 latency가 나쁘고 mutex 대기가 크다면, 이점과 경합 비용을 같이 비교해야 한다.

다음 쿼리는 InnoDB 관련 wait event 중 시간이 큰 항목을 줄여서 확인하는 예시다. 특정 서버에서 event name은 다를 수 있으므로, 결과를 이름 하나에 고정해 자동 판정하지 말고 상위 대기 패턴을 해석해야 한다.

SELECT EVENT_NAME,
       COUNT_STAR,
       ROUND(SUM_TIMER_WAIT / 1000000000000, 6) AS total_wait_seconds,
       ROUND(AVG_TIMER_WAIT / 1000000000, 6) AS avg_wait_ms
FROM performance_schema.events_waits_summary_global_by_event_name
WHERE EVENT_NAME LIKE 'wait/synch/%/innodb/%'
  AND COUNT_STAR > 0
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

실행 결과(MySQL 8.0.46):

mysql> SELECT EVENT_NAME,
    ->        COUNT_STAR,
    ->        ROUND(SUM_TIMER_WAIT / 1000000000000, 6) AS total_wait_seconds,
    ->        ROUND(AVG_TIMER_WAIT / 1000000000, 6) AS avg_wait_ms
    -> FROM performance_schema.events_waits_summary_global_by_event_name
    -> WHERE EVENT_NAME LIKE 'wait/synch/%/innodb/%'
    ->   AND COUNT_STAR > 0
    -> ORDER BY SUM_TIMER_WAIT DESC
    -> LIMIT 10;

+--------------------------------------------------+---------------+--------------------+-------------+
| EVENT_NAME                                       | COUNT_STAR    | total_wait_seconds | avg_wait_ms |
+--------------------------------------------------+---------------+--------------------+-------------+
| wait/synch/sxlock/innodb/hash_table_locks        | 1389509577168 |        187059.2068 |      0.0001 |
| wait/synch/mutex/innodb/trx_sys_mutex            |   48716926029 |         32221.0670 |      0.0007 |
| wait/synch/sxlock/innodb/lock_sys_global_rw_lock |  349103118881 |         30212.2850 |      0.0001 |
| wait/synch/mutex/innodb/trx_mutex                |  503479963421 |         28681.7789 |      0.0000 |
| wait/synch/sxlock/innodb/index_tree_rw_lock      |  324605748123 |         26676.2368 |      0.0001 |
| wait/synch/mutex/innodb/lock_sys_page_mutex      |  351541228357 |         20333.7874 |      0.0001 |
| wait/synch/mutex/innodb/dict_sys_mutex           |   14811074987 |         14633.2214 |      0.0010 |
| wait/synch/mutex/innodb/trx_pool_mutex           |     797598854 |          6614.5324 |      0.0083 |
| wait/synch/sxlock/innodb/dict_operation_lock     |     413661291 |          3506.4680 |      0.0085 |
| wait/synch/mutex/innodb/srv_sys_mutex            |     912917274 |          2245.6447 |      0.0025 |
+--------------------------------------------------+---------------+--------------------+-------------+
10 rows in set (0.40 sec)

이 쿼리는 작은 테스트 컨테이너에서는 빈 결과 또는 낮은 값을 보일 수 있다. 운영 서버에서는 performance_schema instrumentation, consumer 설정, workload 실행 여부에 따라 의미가 달라진다. 중요한 것은 AHI 하나만 보는 것이 아니라 InnoDB 내부 동기화 대기 전체에서 AHI가 차지하는 위치를 보는 것이다.

9. Aurora MySQL에서의 해석

Aurora MySQL도 MySQL 호환 엔진으로서 InnoDB 기반 동작을 제공하지만, storage 계층과 운영 지표 해석은 Community MySQL과 다르다. Aurora의 분산 storage 구조 때문에 redo, page cache, I/O latency, failover 후 cache warm 상태를 해석하는 방식이 다르며, Performance Insights와 CloudWatch 지표가 중요한 관찰 도구가 된다.

AHI 자체는 buffer pool에 올라온 index page 접근 최적화라는 점에서 Aurora에서도 같은 방향으로 이해할 수 있다. 그러나 Aurora에서는 다음 차이를 고려해야 한다.

  • failover 또는 reader promotion 이후 buffer pool과 AHI warm 상태가 달라질 수 있다.
  • writer와 reader instance의 workload가 다르면 AHI 효과도 instance별로 다르다.
  • Performance Insights에서 DB load의 wait 분류가 SQL, lock, IO, CPU로 어떻게 나뉘는지 함께 봐야 한다.
  • parameter group 변경이 동적인지, 재시작이 필요한지, cluster parameter와 instance parameter 중 어디에 속하는지 확인해야 한다.

따라서 Aurora에서 AHI를 비활성화하는 실험은 writer와 reader를 구분하고, failover 직후의 일시적 cold 상태와 정상 steady state를 분리해 비교해야 한다. reader endpoint 뒤에 여러 reader가 있다면 instance별 지표를 따로 보지 않으면 평균값에 병목이 묻힐 수 있다.

10. 흔한 오해와 주의점

AHI는 사용자가 만든 hash index가 아니다

MySQL에는 InnoDB 테이블에 대해 사용자가 일반적인 의미의 hash index를 생성하는 기능이 없다. AHI는 InnoDB 내부 최적화이며, DDL로 설계하거나 EXPLAIN에서 직접 선택되는 인덱스가 아니다. 따라서 “AHI를 쓰도록 쿼리를 힌트로 강제한다”는 접근은 맞지 않다.

AHI는 나쁜 인덱스 설계를 보정하지 못한다

조건에 맞는 B+Tree index가 없거나, cardinality가 낮고 많은 row를 읽는 쿼리는 AHI로 해결되지 않는다. AHI는 반복적인 index page 탐색을 줄이는 장치이지, 잘못된 access path를 올바른 access path로 바꾸는 optimizer 기능이 아니다.

AHI hit가 많다고 항상 좋은 것은 아니다

hash search가 많아도 내부 latch 경합이 더 크면 전체 latency는 나빠질 수 있다. 특히 높은 동시성에서 p99 latency가 늘고 CPU가 포화된다면 평균 처리량만 보고 판단해서는 안 된다.

비활성화는 영구 처방이 아닐 수 있다

오늘의 workload에서 AHI를 끄는 것이 유리하더라도, 인덱스 설계 변경, 트래픽 패턴 변화, MySQL minor version 업그레이드, 서버 core 수 변경 이후에는 결과가 달라질 수 있다. 설정 결정은 정기적으로 재검토해야 한다.

11. 운영 점검표

AHI 변경을 검토할 때는 다음 항목을 순서대로 확인한다.

  • Innodb_adaptive_hash%
  • performance_schema

12. 결론

Adaptive Hash Index는 InnoDB가 B+Tree의 반복 탐색 비용을 줄이기 위해 제공하는 내부 최적화다. hot point lookup이 많고 buffer pool locality가 좋은 workload에서는 도움이 될 수 있지만, 높은 동시성이나 write가 많은 환경에서는 hash index 유지와 latch 경합이 오히려 비용이 될 수 있다.

운영 판단의 핵심은 AHI를 신념의 문제가 아니라 실험과 관찰의 문제로 다루는 것이다. 현재 설정, 누적 counter의 rate, InnoDB wait event, SQL latency 분포, Aurora instance별 특성을 함께 보고, 변경 전후를 같은 기준으로 비교해야 한다. 다음 글에서는 InnoDB 내부 자료구조와 성능 지표를 계속 연결해, buffer pool과 index access path가 실제 운영 지표로 어떻게 드러나는지 더 구체적으로 다룬다.