데이터베이스를 지탱하는 기술 - 1

전성운·2023년 5월 28일
0

DB

목록 보기
1/2
post-thumbnail

다음 내용은 책 '데이터베이스를 지탱하는 기술'을 읽고 정리한 글입니다.

데이터베이스가 없으면 무엇이 곤란한가?

소프트웨어 개발 현장에서는 처음에 기획된 것이 바뀌지 않는 경우가 오히려 더 드물다. 따라서 그러한 사양 변경에 신속하게 대응할 수 있는 설계 기술이 중요하다.

데이터베이스를 사용하면 인증, SQL 작성, 결과 셋의 검색 처리 등의 특수 처리가 필요하다.

이러한 번거로운 일을 함녀서까지 DB를 사용하는 이유는 뭘까?

데이터베이스가 없을 때 일어날 수 있는 일들.

대량의 데이터 중에서 필요한 것을 빨리 반환할 수 없다.

데이터가 증가함에 따라서 시간은 그에 비례하여 엄청나게 증가할 것이다. 따라서 보다 효율적인 데이터 구조로 이를 관리해야 한다.

대량의 데이터를 메모리 내에서만으로는 취급할 수 없다.

인덱스 구조의 라이브러리를 사용할 수 있지 않나? 라고 생각했다면 그 구조는 메모리에 의존하게 된다. 이러한 구조는 메모리 내에서밖에 사용할 수 밖에 없다. 또한, 메모리는 휘발성이다.

날아간 데이터를 어떻게 복구할 수 있을까?

장애가 발생했을 때 빠른 복구가 어렵다.

기업의 입장에서 1분의 서비스 정지는 천문학적인 손해로 돌아온다. 따라서 이에 대한 대책을 철저히 할 필요가 있다.

단순한 백업만으로 생길 수 있는 문제.

  • 최종 백업 이후에 업데이트 된 결과를 되돌릴 수 없다.
  • 백없 데이터를 다시 되돌리는 처리에도 시간이 걸리고, 그 기간 동안은 다운 타임이 된다.
  • 백업 중에 섣불리 업데이트를 하면 백업이 손상될 위험이 있다.

병렬성 제어가 어렵다. (배타 제어를 확실하게 해야한다.)

웹 서비스를 운영하는 경우에는 여러 사람이 동시에 액세스하는 환경이 당연하다.

배타 제어를 하지 않으면 일관성이 무너지고, 배타 제어의 범위가 넓으면 병렬성이 떨어진다.

데이터 무결성을 보장하는 것은 어렵다.

인덱스로 고속 액세스 실현하기

대규모 웹사이트에서 접속 빈도가 초당 수천을 넘어가는 상황에는 정보를 얼마나 빨리 반환하는가가 중요한 요소일 것이다.

텍스트 파일로 관리했을 때 문제점.

전체 검색은 대량의 데이터에 적합하지 않다.

어떠한 데이터를 찾으려면 파일을 처음부터 탐색하여 찾을 때 까지 검색해야 한다. (선형 검색을 사용한다.)

원하는 위치까지 순식간에 도달할 수 있는 방법은 어떤게 있을까?

  1. 고정 길이로 관리하기
    이 경우 데이터까지 빠르게 도달할 수는 있지만, 고정된 길이 안에 모든 데이터가 들어온다는 보장이 없다.
    또한 새로운 데이터를 추가하는 경우에도 어려움이 있다.
  2. 인덱스 구조 도입하기
    사용자 정보를 취급하는 가변 길이 포맷의 문서와는 별도로 각각의 사용자 ID마다 파일 상의 시작 위치를 기록한 파일을 만들어서 고속으로 사용자 정보를 접근할 수 있게 한다.
    인덱스 항목은 [키 값, 바이트 위치] 뿐이므로 고정 길이 포맷으로 대응할 수 있을 것이다.
    접근 단계가 2단계 필요하다는 단점이 있긴 하지만, 어느 쪽도 데이터 양에 의존하지 않는 비용으로 값을 취할 수 있다.
  3. 해시 인덱스
    키 값의 데이터 항목이 숫자, 문자열, 날짜/시간 등 여러가지를 생각할 수 있으므로 더욱 더 범용성을 추구하면 고정 길이로 귀착하는 것이 어렵다.
    키 값을 해시 함수에 대입하여 해시 값과 값의 쌍을 갖는 구조가 즐겨 사용되고 있다. ( 이 방식을 해시 인덱스 라고 한다. ) 해시 값은 문자열 길이에 상고나없이 동일한 크기이므로 고정 길이 포맷으로 대응할 수 있다.
  • 키의 값이 몇 개로 반환될지 사전에 알 수 없는 상황에서는 사용할 수 없다.
  • 순서에 따라(소트) 결과를 반환해 주고 싶은 경우 해시 인덱스는 그다지 도움이 되지 않는다.

B+Tree 인덱스

위에서 언급한 단점들을 대응하고자 나온 것이 B+Tree 인덱스 이다.
정상이 Root 블록, 최하층이 leaf블록 이라고 불리며 그 사이는 branch블록 이라고 불린다.

루트 블록과 브랜치 블록은 사용자 ID에 대해 해당 블록이 어디 있느닞에 대한 정보를 가지고 있다. (인덱스 검색 시에 루트-브랜치-리프 순으로 도달하여 원하는 데이터를 얻을 수 있다.)

브랜치 및 리프의 분기 개수가 두 개밖에 없는 것을 이진트리라고 한다.

B-Tree

모든 값을 리프 블록에서만 갖도록 제한하지 않으며,브랜치에서도 값을 가질 수 있는 데이터 구조이다.

B-Tree에 비해 B+Tree는 루트에서 리프까지 거쳐야 검색을 완료할 수 있지만, 인덱스 자체의 계층 구조를 작게할 수 있다는 장점이 있다.
B-Tree는 리프뿐만 아니라 브랜치에서도 키 정보를 가지고 있어 주변의 리프가 아닌 브랜치로 되돌아가 값의 존재 확인을 실시하고 그 위의 브랜치로 돌아가거나 다른 리프로 가는 등의 처리를 거쳐야 하지만, B+Tree는 리프 블록에서 인접한 리프 블록으로 건너가는 것만으로 값의 탐색이 가능하기에 보다 효율적이다.

즉, 좀 더 유연하다고 할 수 있다.

RDBMS에서는 어떻게 최적화를 실현하고 있는가?

실제 시장에서는 B+Tree 인덱스의 구성에 더해 실제 용도를 상정하여 다양한 최적화를 진행하고 있다.

고유성의 보장.

인덱스는 고유성을 보증하는 목적으로도 사용할 수 있다.
데이터베이스 서버에서는 고유성을 보장하려는 열에 인덱스를 지정하거나 내부적으로 인덱스를 작성하는 것이 대부분이다.

멀티 칼럼 인덱스

여러 조건의 인덱스.
대부분의 RDBMS는 (사용자ID, 최종 갱신일)과 같이 두 가지 요소를 결합한 인ㄷ게스를 만들 수 있다. (AND 조건에서 검색을 가속화 할 수 있다.)

인덱스만을 읽는 검색

인덱스 부분과 데이터 부분은 서로 독립적 영역에 존재하고 있기 때문에 양쪽을 한 번의 액세스로 단번에 읽을 수는 없다. 즉, 데이터를 2번 불러와야 한다.

인덱스 부분만을 불러와도 되는 경우(어떤 조건의 상품 개수를 알고 싶을 때는 단순히 인덱스의 개수를 세면 된다.)에는 인덱스 영역만을 읽어서 검색을 고속화 한다.

인덱스 병합

한 번의 검색에서 두 개 이상의 인덱스를 동시에 사용하여 각각의 결과에서 원하는 레코드를 꺼내려고 하는 동작.

  1. 먼저 각 인덱스에서 각각 검색을 실시하여 대상의 행 번호를 추출한다
  2. 각각의 결과에 대해 AND 조건과 OR 조건 등으로 집합 연산을 수행하고, 마지막으로 남은 행 번호에 대해 실제 데이터를 읽어 가는 효율적인 동작을 시행한다.

랜덤 엑세스는 느리다.

데이터베이스에서는 무정지성(시스템 내에서 어떠한 장애가 발생하더라도 아무런 데이터의 손실 및 파괴 없이 시스템이 동작해야 한다.)을 확보하기 위해 갱신한 데이터를 디스크에 써야 한다.

메모리에 맞지 않는 규모의 데이터를 취급하는 것이 대부분이며, 메모리에 올라와 있지 않은 데이터를 읽기 위하여 디스크로부터의 로드가 발생한다.

대부분들의 경우 이것들은 랜덤 액세스로, 랜덤 액세스 시간은 HDD 라면 디스크가 반 정도 회전하는 시간이므로 한 초당 수천 회의 수준의 랜덤 액세스 요청이 들어와 버리면 처리를 수행할 수 없게 된다.

업데이트 비용 절감을 위한 노력

인덱스는 본체 데이터와 별도로 유지할 필요가 있기에 검색 성능이 높아지는 대가로 업데이트 성능이 떨어진다.
이 업데이트 비용을 최대한 떨어뜨리기 위해 다양한 최적화를 구현하고 있다.

디스크에 모아서 기록하기

레코드를 추가 등록하는 겨웅에는 인덱스에도 값을 등록해야 한다.

오름차순으로 등록된다는 보장이 없기에 인덱스의 리프 블록의 이곳저곳이 무작위에 가까운 형태로 업데이트 되어 간다. 랜덤억세스는 느리기에 이 비용을 얼마나 감소시키는가가 성능에 있어서 중요하다.

DB는 무정지성을 높이기 위해 업데이트도니 부분을 최대한 빨리 디스크에 저장해야 한다. 하지만, 업데이트된 정보를 메모리나 전용 파일 등에 일시적으로 기록하여 두고 나중에 모아서 단번에 리프 블록을 갱신하는 구조를 사용한다면 이 성능을 개선할 수 있을 것이다. 그리고 이 구조를 사용하는 DB가 MySQL이다.

병렬 갱신 성능 높이기

B+Tree 인덱스에 값의 추가/갱신/삭제를 할 경우, 인덱스의 리프 블록의 내용을 이동시킬 필요가 있게 된다. (리프 분할).

여러 클라이언트에 일제이 업데이트가 발생하여 리프 분할이 여기저기에서 일어난 경우, 그것들의 일관성을 갖는 것은 상당히 어려운 작업. 따라서 MySQL에서는 인덱스의 재편성 처리가 완료될 때까지 일체의 참조/갱신 처리를 차단하는 동작을 시행한다. 재편성 조작 자체는 금방 끝나지만 동시에 대량의 클라이언트로부터 동일 테이블에 대해 일제히 요청을 시행하여도 동시성이 그다지 높아지지 않는 문제가 있다.

멀티코어 CPU 환경의 대두로 이러한 병렬 갱신 성능의 중요성이 높아졌다.

현재 MySQL에서 이것을 해결하는 가장 빠른 방법은 파티션 테이블을사용하는 것이다. (사용자에게는 테이블이 한 개로 보이자만 내부적으로 복수로 분할 관리되는 것. 인덱스도 복수로 구분하고 있기에 병렬 갱신이 가능하다.)

profile
대학생

0개의 댓글