Overview

MySQL 테이블을 설계할 때 보통 자주 사용하는 조건 컬럼에는 Index 를 추가합니다.

다양한 쿼리를 사용하는 경우 인덱스를 여러 개 추가하는데, 인덱스의 컬럼이 겹치면 원하지 않는 인덱스를 타서 Slow Query 가 발생할 수 있습니다.

Slow Query 의 발생가능한 원인과 해결 방법에 대해 간단히 알아봅니다.


1. Optimizer

SQL 쿼리를 호출하면 DBMS 에 존재하는 Optimizer 가 SQL 실행계획을 만들어줍니다.

옵티마이저는 두 가지 종류로 나뉩니다.

  • RBO (Rule Based Optimizer)
    • 미리 정해진 규칙대로만 쿼리를 수행
    • 인덱스가 존재하면 무조건 인덱스를 탐
    • 예측 가능하기 때문에 설계하기 쉽지만 그만큼 쿼리 자체를 잘 짜야함
  • CBO (Cost Based Optimizer)
    • 통계 정보 (Record 수, Index 컬럼 값 갯수) 를 기반으로 옵티마이저가 생각하는 가장 효율적인 쿼리를 수행
    • 인덱스가 존재해도 Table Full Scan 이 더 효율적이라고 생각하면 Full Scan 함
    • 예측이 힘들기 때문에 우리가 생각한 실행계획과 달라 Slow Query 가 발생할 수 있음
      • 특히 로컬, 개발 환경과 실제 운영 환경에서는 데이터의 갯수가 달라 같은 쿼리여도 다르게 수행될 수 있음

최근 대부분의 RDBMS 는 CBO 를 사용한다고 합니다.

예를 들어 우리가 WHERE ~ AND ~ 조건을 사용할 때 Index 컬럼 순서를 지키지 않았지만 자동으로 인덱스를 태우는 것도 다 CBO 덕분입니다.

하지만 CBO 를 사용하는 경우 우리 생각과는 다르게 동작할 수 있습니다.

여러 인덱스가 존재할 때 A 인덱스를 타는게 더 효율적임에도 B 인덱스를 타는 경우가 있습니다.

이런 경우에 사용하는 것이 바로 Index Hint 입니다. (쿼리 최적화의 또다른 방법으로 Optimizer Hint 라는 것도 있는데 이번 포스팅에서는 다루지 않습니다)


2. Index Hint

인덱스 힌트는 이름 그대로 옵티마이저가 인덱스를 선택할 때 도움을 줍니다.

특정 인덱스를 사용하지 않길 원하면 IGNORE, 사용하길 원하면 USE, FORCE 를 사용할 수 있습니다.

그렇다면 USEFORCE 의 차이는 뭘까요?

Stackoverflow - MySQL : FORCE INDEX vs USE INDEX 를 참고해보면 둘은 다음과 같은 차이가 있습니다.

  • USE INDEX
    • DB 옵티마이저에게 지정한 인덱스를 사용하라고 권장
    • 하지만 만약 Table Scan 이 더 빠르다면 옵티마이저는 인덱스 대신 Table Scan 수행 가능
  • FORCE INDEX
    • Table Sacan 이 더 효율적이어도 무조건 인덱스 사용
    • Index 를 사용할 수 없는 쿼리 (인덱스가 걸려있지 않은 컬럼이 조건) 인 경우에만 다른 방법 선택 가능

대부분의 경우에는 USE INDEX 만 사용해도 되지만 Full Scan 도 허용하지 않는 경우에는 FORCE INDEX 를 사용하면 될 것 같습니다.


Reference

'공부 > Database' 카테고리의 다른 글

MySQL Index 특징 및 유의사항 정리  (2) 2022.05.22
Cache 전략  (0) 2022.05.20
Redis 설치 및 명령어  (0) 2021.08.07

인덱스에 대해 알고 있는 정보를 정리합니다.

잘못된 정보가 있으면 지적 부탁드립니다.

Overview

인덱스는 "목차" 라고 할 수 있습니다.

예를 들어 책에서 어떤 단어의 위치를 찾으려면 책을 전부 다 봐야하지만 가나다순으로 목차를 만들어두면 쉽게 찾을 수 있습니다.

목차는 순서대로 정렬되어 있기 때문에 데이터를 더 빠르게 찾을 수 있게 도와줍니다.

하지만 목차를 넣으면 별도 페이지를 만들어야 하기 때문에 책이 조금 더 두꺼워집니다.

그리고 새로운 단어가 추가되면 목차에도 추가해줘야 하고 중간 단어를 넣는다면 순서를 조정해서 페이지도 다시 만들어야 합니다.


1. 인덱스란

인덱스는 위에서 설명한 목차와 비슷합니다.

어떤 데이터를 찾을 때 DB 테이블을 찾는 대신 미리 만들어둔 인덱스를 먼저 탐색합니다.

인덱스를 설정하면 특정 컬럼들을 키 값으로 메모리 영역에 트리 구조로 저장해둡니다.

그리고 디스크 저장소에 바로 접근하는 대신 메모리 저장소에 있는 인덱스를 먼저 조회해서 빠르게 데이터를 가져올 수 있습니다.

인덱스가 데이터를 빠르게 가져올 수 있는 이유는 항상 정렬된 상태를 유지하기 때문입니다.

이를 위해 데이터가 추가/삭제 될 때마다 자료구조를 정렬하기 때문에 인덱스는 SELECT 성능을 향상시키는 대신 INSERT, UPDATE, DELETE 의 성능이 떨어지게 됩니다.


2. 인덱스 종류

인덱스 종류는 어떤 자료구조를 사용하냐에 따라 나눌수도 있고, 데이터 저장 방식에 따라 클러스터드 (Clustered) 인덱스와 넌클러스티드 (Non Clustered) 인덱스로 나누기도 합니다.


2.1. Clustered Index vs Non-Clustered Index

  • Clustered Index
    • 이름 그대로 인접한 데이터들을 한곳으로 모았다는 뜻
    • PK 설정 시 자동으로 클러스터드 인덱스로 만들어짐
    • 테이블당 1개씩만 허용
    • 물리적인 데이터를 갖고 있음
    • 항상 정렬된 상태를 유지하고 노드 내에서도 정렬되어 있음
    • Non Clustered 인덱스에 비해 조회 속도가 빠르지만 삽입/수정/삭제는 더 느림
  • Non Clustered Index
    • UNIQUE 로 설정된 컬럼에 자동으로 생성됨
    • 인덱스 페이지는 로그 파일에 저장됨
    • 레코드의 원본은 정렬되지 않고 인덱스 페이지만 정렬됨

2.2. Primary Index vs Secondary Index

  • Primary Index
    • PK (기본키) 를 기반으로 만들어진 인덱스
    • PK 는 하나만 존재할 수 있기 때문에 Primary Index 도 단 하나만 존재
  • Secondary Index
    • 기본키는 아니지만 성능 향상을 위해 임의의 컬럼을 지정해서 만든 인덱스
    • 여러 개의 Secondary Index 가 존재할 수 있음

2.3. 자료구조에 따른 분류

  • B-Tree 인덱스
    • 가장 많이 사용되는 구조
  • Hash 인덱스
    • 컬럼 값을 해싱해서 사용하며 매우 빠른 검색 가능
    • 컬럼 값과 인덱스 키 값이 일치하지 않기 때문에 문자열 검색과 같이 일부 일치에 대해 검색 불가능
  • Fractal-Tree 인덱스
    • B-Tree 인덱스의 단점을 보완하기 위해 만듬
    • 컬럼 값을 변형하지 않으며 데이터의 저장/삭제 처리 비용을 많이 줄임
    • 아직 많이 사용되지는 않음

3. B-Tree 인덱스

Balanced Tree 의 약자로서 데이터베이스 인덱싱 알고리즘 가운데 가장 일반적으로 사용되는 알고리즘 입니다.

B-Tree 인덱스는 컬럼의 값을 변경시키지 않고 구조체 내에서 항상 정렬된 상태를 유지합니다.

B-Tree 는 최상위에 루트 (Root) 노드가 존재하고 하위에 브랜치 (Branch) 노드, 마지막에 리프 (Leaf) 노드로 되어 있습니다.

부모 노드를 기준으로 왼쪽 자식 노드는 더 작은 값 오른쪽 자식 노드는 더 큰값을 갖고 있습니다.


3.1. 왜 B-Tree 인가?

인덱스로 사용할 수 있는 자료구조는 여러 개가 있을 겁니다.

Tree 구조의 Worst 시간복잡도는 한쪽으로 모든 자식 노드가 쏠려있는 형태인 O(n) 입니다.

그래서 우리는 자식 노드가 양쪽에 골고루 퍼져있는 Balanced Tree 를 사용합니다.

Balanced Tree 중에는 RedBlack Tree 도 있는데 왜 사용하지 않을까요?

해시 테이블은 O(1) 인데 왜 사용하지 않을까요?


3.2. 다른 트리 구조를 사용하지 않는 이유

위에서 잠시 언급한 RedBlack Tree 는 B-Tree 와 마찬가지로 정렬된 상태와 밸런스를 유지합니다.

그럼 B-Tree 와 차이가 없을 것 같은데 왜 사용하지 않을까요?

가장 큰 차이점은 B-Tree 는 노드 하나에 여러 개의 데이터를 저장할 수 있다는 겁니다.

노드에서 배열 형태로 여러 데이터를 저장할 수 있기 때문에 트리 포인터를 참조해서 계속 depth 를 타고 들어가는 것보다 효율적이고 이는 데이터가 많아질수록 차이가 두드러집니다.


3.3. Hash 테이블을 사용하지 않는 이유

해시 테이블은 Hash 함수를 사용해서 키 값을 해싱한 후에 테이블에 저장합니다.

해시 테이블은 분명 한 가지 키에 대한 탐색은 효율적입니다.

하지만 데이터가 정렬되어 있지 않기 때문에 부등호 (<, >) 를 사용하지 못한다는 단점이 있습니다.


3.4. B-Tree 인덱스의 쿼리별 특징

  • SELECT
    • 특정 키 값을 찾기 위해 자식 노드를 계속 타고 들어가는 방식
    • 마지막 리프 노드에는 레코드의 주소가 존재하고 이 값으로 테이블 레코드를 찾을 수 있음
  • INSERT
    • B-Tree 에 새로운 키값을 저장할 때는 우선 적절한 위치를 찾아야함
    • 새로운 키값과 레코드 정보는 리프 노드에 저장
    • 만약 리프 노드가 꽉찼다면 트리를 재구성하여 리프 노드를 분리
    • 분리 과정에서 해당 리프 노드의 부모 노드까지 영향이 갈 수 있음
    • 이러한 이유로 INSERT 작업은 상대적으로 비용이 많이 듬
    • 인덱스가 많으면 많을수록 이런 비용이 추가로 들기 때문에 너무 많은 인덱스를 추가하는 건 성능에 영향을 줌
  • DELETE
    • B-Tree 에서 키 값 삭제는 간단
    • 해당 키를 찾아서 삭제 마크만 하면 작업이 완료
    • 삭제 마킹된 인덱스 키 공간은 그대로 두거나 재활용 가능
  • UPDATE
    • 인덱스는 항상 정렬된 상태로 유지됨
    • 단순히 인덱스 키 값을 수정한다면 트리의 전체 구조를 바꿔야 할 수도 있음
    • 그래서 B-Tree 에선 키 변경이 아닌 기존 키 삭제 (DELETE) 후 새로운 키 추가 (INSERT) 방식을 사용
    • 따라서 키 값의 잦은 수정은 성능에 큰 영향을 미침

4. 인덱스 설정 시 고려사항

인덱스는 조회 속도를 향상시키지만 어느정도 오버헤드가 존재하기 때문에 아무렇게나 추가해버리면 안됩니다.


4.1. 인덱스의 갯수

인덱스의 갯수는 3 ~ 4 개가 적당합니다.

인덱스의 갯수가 너무 많으면 다음과 같은 이슈가 존재합니다.

  • 데이터 삽입/수정/삭제 시마다 인덱스도 같이 추가하거나 수정/삭제 해주어야 해서 성능상 이슈가 존재
  • 데이터 삽입시마다 인덱스도 같이 추가하기 때문에 인덱스가 늘어날수록 더 많은 메모리를 차지함
  • 인덱스가 많아지면 옵티마이저가 잘못된 인덱스를 선택할 확률이 높아짐 (인덱스 힌트로 원하는 인덱스를 지정할 순 있음)

4.2. 인덱스를 걸기에 적절한 컬럼

인덱스의 갯수에 한계가 있다면 적절한 인덱스 컬럼을 정하는 것도 중요합니다.

인덱스는 카디널리티 (Cardinality) 가 높은 컬럼에 지정하는 게 좋습니다.

카디널리티가 높다는 말은 데이터의 중복이 적다는 뜻인데 대표적으로 ID, 주민번호 등이 있습니다.

반대로 성별 같은 중복된 데이터가 많은 경우 카디널리티가 낮다고 표현합니다.

성별에 인덱스를 거는 경우 인덱스를 타더라도 남/녀 두가지만 존재하기 때문에 결국 나머지 조건에 맞는 데이터는 직접 풀스캔을 해서 찾아야 합니다.

하지만 ID 같이 중복된 값이 없는 경우 해당하는 데이터를 빠르게 찾을 수 있습니다.


4.3. 읽어야 하는 레코드 갯수

인덱스는 일반적으로 단 하나의 데이터를 구할 때 가장 효율적입니다.

여러 개의 데이터를 구한다면 인덱스를 통해 레코드의 주소를 찾아 데이터의 레코드를 읽는 작업을 반복해야 합니다.

그래서 만약 많은 레코드를 한번에 조회한다면 오히려 인덱스를 사용하지 않고 직접 테이블을 읽는 것이 더 효율적일 수 있습니다.

예를 들어 테이블에 10 개의 데이터가 존재하고, 5건의 데이터를 읽는다고 가정한다면 인덱스를 통하는 것보다 테이블을 직접 읽는 게 효율적일 수도 있습니다.

일반적으로 DBMS 의 옵티마이저는 인덱스를 사용해 레코드 1건을 읽는 것이 테이블에서 직접 읽는 것보다 4 ~ 5배 정도 비용이 더 많이 든다고 예측합니다.

그러므로 인덱스를 통해 읽어야 할 레코드가 전체 테이블의 20 ~ 25% 이상이라면 직접 테이블을 읽는 것이 효율적입니다.

사실 인덱스가 걸려있어도 옵티마이저가 판단하여 더 효율적이라고 생각되는 방법을 선택하기 때문에 개발자가 직접 고려할 일은 없지만 기본적으로 알 고 있으면 좋습니다.


4.4. 복합 인덱스를 구성할 때

인덱스는 여러 개의 컬럼을 동시에 지정할 수도 있는데 어떤 순서로 구성하느냐에 따라 성능이 달라집니다.

위에서 인덱스는 트리 구조로 되어있다고 했는데, 여러 개의 컬럼을 함께 키 값으로 지정하는 경우 먼저 첫 번재 컬럼을 기준으로 정렬된 뒤에 두번째 컬럼이 정렬되어 있습니다.

이 말은 즉 첫 번째 컬럼 없이 두 번째 컬럼만 갖고 인덱스를 조회하면 제대로 된 위치를 찾을 수 없다는 뜻입니다.

그러므로 복합 인덱스를 구성했다면 조회할 때 앞 순서의 조건을 반드시 포함해야 인덱스를 태울 수 있습니다.

예를 들어 A, B, C 컬럼 순으로 인덱스를 구성했다면 A 라는 컬럼을 조회 조건에 포함해야 최소한의 인덱스를 태울 수 있고 B, C 컬럼만 조회 조건에 있다면 인덱스를 타지 못합니다. (정확히 말하면 인덱스 풀 스캔을 하는데 이런 경우 일반적으로 인덱스를 타지 않는다고 표현)

그리고 여러 개의 컬럼이 있다면 카디널리티가 높은 순에서 낮은 순으로 지정하는게 인덱스의 효율을 이끌어낼 수 있습니다.

그리고 과거에는 인덱스의 컬럼 순서와 조회 컬럼 순서를 맞춰야 인덱스를 탔지만 최근에는 옵티마이저가 알아서 인덱스 순서에 맞춰주기 때문에 거의 차이가 없습니다.

그래도 재배열하는 과정을 생략하기 위해 최대한 맞추는게 좋긴 합니다.


5. 인덱스 사용 시 주의사항

지금까지도 조금씩 언급했지만 인덱스는 사용할 대 주의할 점이 몇개 존재합니다.

  • 다중 인덱스를 사용할 때 범위 조건은 인덱스를 타지만 이후 컬럼들은 인덱스를 타지 않음
    • 범위 조건으로 사용할 때 주의
  • 인덱스로 지정한 컬럼은 그대로 사용해야 함
    • WHERE age * 10 > 20 처럼 조회조건에 있는 컬럼을 변경하면 안됨
    • WHERE age > 20 / 10 처럼 컬럼을 그대로 사용해야 함

6. InnoDB Adaptive Hash Index

MySQL 은 기본으로 InnoDB 를 사용하고 InnoDB 는 B-Tree 를 사용합니다.

PK 가 아닌 컬럼으로 인덱스를 지정하면 Secondary Index 가 생성됩니다.

그래서 인덱스로 컬럼을 조회하면 Secondary 인덱스를 기반으로 PK 를 찾은 뒤 다시 Primary Index 로 데이터를 찾아냅니다.

인덱스를 두번 타기 때문에 2 * O(log n) 비용이 듭니다.

그래서 자주 사용되는 컬럼을 해시로 정의해서 B-Tree 를 타지 않고 바로 데이터에 접근할 수 있게 하는 걸 Adaptive Hash Index 라고 합니다.

미리 캐싱한 해시값으로 조회하기 때문애 O(1) 의 속도를 보여주지만 어떤 값을 해싱할지는 옵티마이저가 판단하기 때문에 제어할 수 없다는 약점이 있습니다.


7. Covering Index

일반적으로 인덱스를 설계할 때는 WHERE 절에 대해서만 이야기하지만 사실 쿼리 전체에 대해 인덱스 설계가 필요합니다.

인덱스를 사용하면 특정 컬럼 값을 키로 하여 데이터의 주소값을 구한 뒤 해당 주소값으로 다시 테이블에 접근해서 최종 데이터를 구합니다.

커버링 인덱스란 인덱스에 이미 필요한 데이터가 전부 존재해서 테이블에 접근할 필요가 없는 인덱스를 의미합니다.

예를 들어 (A, B) 컬럼으로 인덱스를 구성한 경우 SELECT * FROM table WHERE A = ? 와 같은 형식으로 쿼리를 사용하면 인덱스를 탄 후에 전체 컬럼값을 모두 구하기 위해 테이블에 접근합니다.

하지만 SELECT A, B FROM table WHERE A = ? 와 같이 구하려는 컬럼값이 모두 인덱스에 이미 존재한다면 테이블에 다시 접근할 필요가 없습니다.

인덱스는 기본적으로 Non Clustered Index 에서 먼저 값을 구하고 Clustered Index 에서 다시 데이터를 구합니다.

여기서 커버링 인덱스가 사용되었다는건 Clustered Index 까지 통하지 않고 Non Clustered Index 만으로도 데이터를 구할 수 있다는 뜻입니다.

커버링 인덱스가 적용되면 EXPLAIN 실행 시 Extra 필드에 Using index 라고 표시됩니다.


Reference

'공부 > Database' 카테고리의 다른 글

MySQL Optimizer 와 USE INDEX vs FORCE INDEX  (0) 2022.06.21
Cache 전략  (0) 2022.05.20
Redis 설치 및 명령어  (0) 2021.08.07

Overview

What is Caching ? 글을 참고하여 캐시 전략에 대해 정리했습니다.


1. Cache 란?

위키 백과에는 이렇게 나와 있습니다.

데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다.
캐시는 캐시의 접근 시간에 비해 원래 데이터를 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용한다.
캐시에 데이터를 미리 복사해 놓으면 계산이나 접근 시간 없이 더 빠른 속도로 데이터에 접근할 수 있다.

설명 그대로 저장소에 접근하는 시간을 절약하기 위해 별도의 저장소에 저장해두는 것을 의미합니다.

서버에서는 MySQL, Oracle 같은 DBMS 에 매번 접근하지 않도록 메모리에 임시로 저장해두는 용도로 많이 사용합니다.

Redis 같은 별도의 캐시 저장소를 사용하는게 일반적이며, DB 내부에 있는 캐시, 브라우저 캐시, CDN 등등도 다 캐시라고 볼 수 있습니다.


2. 캐싱하기에 적절한 데이터

캐시가 효율적으로 동작하기 위해선 데이터가 다음과 같은 특징을 가져야 합니다.

  • 자주 조회됨
  • 데이터의 결과값이 일정함
  • 연산이 무거움

3. 읽기 캐시 전략

캐시를 읽을 때 어떤 전략을 사용할지 결정합니다.

기본적으로는 캐시 먼저 조회 후 DB 를 다시 확인한 뒤, 캐시를 갱신하는 방법입니다.


3.1. Cache Aside

  1. 캐시에 데이터가 있는지 확인
  2. 데이터가 존재하면 (Cache Hit) 해당 캐시 데이터를 반환
  3. 데이터가 존재하지 않으면 (Cache Miss) 애플리케이션에서 DB 에 데이터 요청 후 캐시에 저장하고 데이터를 반환

애플리케이션에서 가장 일반적으로 사용되는 캐시 전략입니다.

주로 읽기 작업이 많은 애플리케이션에 적합합니다.

Cache Hit 의 경우 DB 를 확인하지 않기 때문에 캐시가 최신 데이터를 가지고 있는지 (동기화) 가 중요합니다.

캐시가 분리되어 있기 때문에 원하는 데이터만 별도로 구성하여 캐시에 저장할 수 있고 캐시에 장애가 발생해도 DB 에서 데이터를 가져오는 데 문제가 없습니다.

하지만 캐시에 장애가 발생했다는 뜻은 DB 로 직접 오는 요청이 많아져서 전체적인 장애로 이어질 수 있습니다.


3.2. Read Through

  1. 캐시에 데이터 요청
  2. 캐시는 데이터가 있으면 (Cache Hit) 바로 반환
  3. 데이터가 없다면 (Cache Miss) 캐시가 DB 에서 데이터를 조회한 후에 캐시에 저장 후 반환

Cache Aside 와 비슷하지만 데이터 동기화를 라이브러리 또는 캐시 제공자에게 위임하는 방식이라는 차이점이 있습니다.

마찬가지로 읽기 작업이 많은 경우에 적합하며 두 방법 다 데이터를 처음 읽는 경우에는 Cache Miss 가 발생해서 느리다는 특징이 있습니다.

Cache Aside 와는 다르게 캐시에 의존성이 높기 때문에 캐시에 장애가 발생한 경우 바로 전체 장애로 이어집니다.

이를 방지하기 위해 Cache Cluster 등 가용성 높은 시스템을 구축해두는 것이 중요합니다.


3.3. 읽기 캐시에서 발생 가능한 장애: Thundering Herd

캐시 서버를 구축했다고 해서 아무런 문제가 없는 것은 아닙니다.

캐시 읽기 전략에서는 공통적으로 캐시 확인 -> DB 확인 순서로 이어지는데 이 과정에서 캐시에 데이터가 있으면 DB 확인을 생략하는 것으로 성능을 향상시킵니다.

하지만 서비스를 이제 막 오픈해서 캐시가 비어있는 경우에는 들어오는 요청이 전부 Cache Miss 가 발생하고 DB 조회 후 캐시를 갱신하느라 장애가 발생할 수 있습니다.

이를 회피하기 위해서 캐시에 데이터를 미리 세팅해두는 Cache Warm up 작업을 하거나 첫 요청이 캐시 갱신될 때까지 기다린 후에 이후 요청은 전부 캐시에서 반환하게 할 수 있습니다.

Cache Warm up 작업을 할 때 어떤 데이터를 넣느냐에 따라 마찬가지로 Cache Miss 가 발생할 수 있기 때문에 자주 들어올만한 데이터의 예측이 중요합니다.


4. 쓰기 캐시 전략

쓰기 요청 시 어떤 시점에 캐시 갱신을 하는지에 따라 나뉩니다.

  • Write Around: 캐시를 갱신하지 않음
  • Write Through: 캐시를 바로 갱신
  • Write Back: 캐시를 모아서 나중에 갱신

4.1. Write Around

  1. 데이터 추가/업데이트 요청이 들어오면 DB 에만 데이터를 반영
  2. 쓰기 작업에서 캐시는 건들지 않고 읽기 작업 시 Cache Miss 가 발생하면 업데이트 됨

캐시 쓰기 전략이라고 하기는 좀 애매하게 캐시를 전혀 건들지 않습니다.

수정사항은 DB 에만 반영하고 캐시는 그대로 두기 때문에 Cache Miss 가 발생하기 전까지는 캐시 갱신이 발생하지 않습니다.

Cache 가 갱신된지 얼마 안된 경우에는 캐시 Expire 처리 되기 전까지 계속 DB 와 다른 데이터를 갖고 있다는 단점이 있습니다.

만약 업데이트 이후 바로 조회되지 않을거라는 확신이 있다면 캐시를 초기화하여 Cache Miss 를 유도하는 방법으로 보완할 수 있습니다.


4.2. Write Through

  1. 캐시에 데이터를 추가하거나 업데이트
  2. 캐시가 DB 에 동기식으로 데이터 갱신
  3. 캐시 데이터를 반환

Read Through 와 마찬가지로 DB 동기화 작업을 캐시에게 위임합니다.

동기화까지 완료한 후에 데이터를 반환하기 때문에 캐시를 항상 최신 상태로 유지할 수 있다는 장점이 있습니다.

캐시 및 DB 를 동기식으로 갱신한 후에 최종 데이터 반환이 발생하기 때문에 전반적으로 느려질 수 있습니다.

새로운 데이터를 캐시에 미리 넣어두기 때문에 읽기 성능을 향상시킬 수 있지만 이후에 읽히지 않을 데이터도 넣어두는 리소스 낭비가 발생할 수 있습니다.


4.3. Write Back (Write Behind)

  1. 캐시에 데이터를 추가하거나 업데이트
  2. 캐시 데이터 반환
  3. 캐시에 있던 데이터는 이후에 별도 서비스 (이벤트 큐 등) 를 통해 DB 에 업데이트

캐시와 DB 동기화를 비동기로 하는 방법이며 동기화 과정이 생략되기 때문에 쓰기 작업이 많은 경우에 적합합니다.

캐시에서 일정 시간 또는 일정량의 데이터를 모아놓았다가 한번에 DB 에 업데이트 하기 때문에 쓰기 비용을 절약할 수 있습니다.

다른 캐시 전략에 비해 구현하기 복잡한 편이며 캐시에서 DB 로 데이터를 업데이트 하기 전에 장애가 발생하면 데이터가 유실될 수 있습니다.


4.4. Refresh Ahead

자주 사용되는 데이터를 캐시 만료 전에 미리 TTL (Expire time) 을 갱신합니다.

캐시 미스 발생을 최소화 할 수 있지만 Warm Up 작업과 마찬가지로 자주 사용되는 데이터를 잘 예측해야 합니다.


Reference

'공부 > Database' 카테고리의 다른 글

MySQL Optimizer 와 USE INDEX vs FORCE INDEX  (0) 2022.06.21
MySQL Index 특징 및 유의사항 정리  (2) 2022.05.22
Redis 설치 및 명령어  (0) 2021.08.07

1. Redis 란?

Redis 는 Key-Value 형태로 데이터를 관리하는 오픈 소스입니다.

Redis 는 빠른 속도와 간편한 사용법으로 인해 캐시, 인증 토큰, 세션 관리 등등 여러 용도로 사용됩니다.

  • In-Memory Data Strucutre Store
  • Key - Value 형태로 데이터 저장
  • 여러 가지 Value 타입 저장 가능 (String, Set, Hash, List 등등..)
  • Single Thread
  • 데이터 만료 시간 지정 가능

2. Redis 설치

도커로 설치 후 실행 가능합니다. (https://hub.docker.com/_/redis 참고)

# 이미지 다운 (docker images 로 확인 가능)
$ docker pull redis

# 컨테이너로 레디스 실행 (--name: 컨테이너 이름 설정, -p: 포트 포워딩, -d: 백그라운드에서 실행)
$ docker run --name some-redis -p 6379:6379 -d redis

# redis-cli 접속
$ docker exec -it some-redis redis-cli

3. Redis 명령어

Redis 는 여러 개의 데이터 타입을 저장할 수 있기 때문에 각각의 명령어가 여러개 존재합니다.

모든 명령어는 Redis Commands 를 참고하시고 여기에는 일부 자료구조의 간단한 명령어만 정리합니다.

다만 Redis 는 Single Thread 기반이기 때문에 keys, flushall, flushdb, getall 등 일반적으로 생각했을 때 O(N) 의 시간복잡도를 가질 것 같은 명령어는 운영 환경에서 사용하면 위험합니다.


3.1. String

가장 기본적인 Value 타입입니다.

  • 저장
    • set {key} {value} : key, value 를 저장
    • mset {key} {value} [{key} {value} ...] : 여러 개의 key, value 를 한번에 저장
    • setex {key} {seconds} {value} : key, seconds, value 저장 (설정한 시간 뒤에 소멸)
  • 조회
    • keys * : 현재 저장된 키값들을 모두 확인 (부하가 심한 명령어라 운영중인 서비스에선 절대 사용하면 안됨)
    • get {key} : 지정한 key 에 해당하는 value 를 가져옴
    • mget {key} [{key} ...] : 여러 개의 key 에 해당하는 value 를 한번에 가져옴
    • ttl {key} : key 의 만료 시간을 초 단위로 보여줌 (-1 은 만료시간 없음, -2 는 데이터 없음)
    • pttl {key} : key 의 만료 시간을 밀리초 단위로 보여줌
    • type {key} : 해당 key 의 value 타입 확인
  • 삭제
    • del {key} [{key} ...] : 해당 key 들을 삭제
  • 수정
    • rename {key} {newKey} : key 이름 변경
    • expire {key} {seconds} : 해당 키 값의 만료 시간 설정
  • 기타
    • randomkey : 랜덤한 key 반환
    • ping : 연결 여부 확인 ("ping" 만 입력하면 "PONG" 이라는 응답이 옴)
    • dbsize : 현재 사용중인 DB 의 key 의 갯수 리턴
    • flushall : 레디스 서버의 모든 데이터 삭제
    • flushdb : 현재 사용중인 DB 의 모든 데이터 삭제

3.2. Set

Redis 에서는 Set 에 포함된 값들을 멤버라고 표현합니다.

여러 멤버가 모여 집합 (Set) 을 구성합니다.

진짜 집합처럼 교집합, 차집합 등도 구할 수 있는데 여기선 간단하게 CRUD 만 알아봅니다.

  • sadd {key} {member} [{member} ...]
    • key 에 새로운 멤버들을 추가. key 가 없으면 새로 만듬
  • smembers {key}
    • key 에 설정된 모든 멤버 반환
  • srem {key} {member [{member} ...]}
    • key 에 포함된 멤버들 삭제. 없는 멤버 입력하면 무시됨
  • scard {key}
    • key 에 저장된 멤버 수를 반환
  • sismember {key} {member}
    • member 가 해당 key 에 포함되는지 검사

3.3. Hash

Redis 에서 저장가능한 자료구조 중에 Hash 도 있습니다.

Hash 자체를 나타내는 key 와 해당 key 에 포함된 field 까지 사용해서 값을 조회/저장할 수 있습니다.

  • hset {key} {field} {value} [{field} {value} ...]
    • key 를 이름으로 한 Hash 자료 구조에 field 와 value 값을 저장
  • hget {key} {field}
    • key Hash 값에 포함된 field 의 value 를 가져옴
  • hdel {key} {field} [{field} ...]
    • field 값으로 데이터 삭제
  • hlen {key}
    • Hash 가 갖고 있는 field 갯수 반환
  • hkeys {key}
    • Hash 가 갖고 있는 모든 field 출력
  • hvals {key}
    • Hash 가 갖고 있는 모든 value 출력
  • hgetall {key}
    • Hash 가 갖고 있는 모든 field 와 value 출력

Reference

'공부 > Database' 카테고리의 다른 글

MySQL Optimizer 와 USE INDEX vs FORCE INDEX  (0) 2022.06.21
MySQL Index 특징 및 유의사항 정리  (2) 2022.05.22
Cache 전략  (0) 2022.05.20

+ Recent posts