Ordinary
About

대규모 서비스를 지탱하는 기술 (3 ~ 4)

profileordilov / 2022. 3. 15
OS 캐시와 분산

OS는 캐시를 통해 대규모 데이터를 효율적으로 처리하며, 제대로 처리 못할 경우 분산을 고려해보게 됩니다.

OS의 캐시 구조

디스크와 메모리 간 속도차는 10^5 ~ 10^6배 정도 차이가 나는데 OS 내에는 디스크 데이터를 빠르게 접근할 구조가 있습니다. 바로 OS 캐시로 페이지 캐시, 파일 캐시, 버퍼 캐시등이 해당합니다. 그 중 페이지 캐시를 예시로 설명합니다.

페이지는 가상 메모리 구조에서 논리적인 선형 어드레스를 물리 어드레스로 변환합니다. 가상 메모리 구조는 물리적인 하드웨어를 OS에서 추상화하는 메모리 구조입니다. 프로세스에서 메모리를 필요로 하면 메모리에서 어드레스를 직접 가져오는 것이 아니라 OS에 요청을 합니다. OS는 메모리에 해당 어드레스에 대한 정보를 가상 메모리 주소로 가지고 있고 프로세스에게 가상 메모리 주소를 넘겨줍니다. 프로세스가 실제로 가상 메모리 주소를 읽고 쓸 때 OS에 요청하면 가상 메모리 주소를 실제 메모리 주소로 변환하여 처리합니다.

이때 메모리를 확보할 때도 4KB정도로 블록으로 확보해서 프로세스에 넘기고 이런 블록을 페이지 라고 합니다.

Linux의 페이지 캐시 원리

OS는 확보한 페이지를 메모리상에 계속 확보해 둡니다. 디스크 읽기가 끝나서 불필요해지더라도 페이지를 남겨두는데 이후에 다시 사용하면 디스크를 읽으러 갈 필요가 없게 됩니다. 이렇게 한 번 할당한 메모리를 남겨두는 것이 페이지 캐시의 기본입니다. 즉 첫 번째는 디스크까지 읽기 때문에 느리지만 두 번째부터 메모리까지만 접근하면 되기에 빨라집니다.

VFS

디스크의 캐시는 페이지 캐시에 의해 제공되지만 실제로 디스크를 조작하는 디바이스 드라이버와 OS 사이에는 파일시스템이 있습니다. 파일 시스템에는 ext3, xfs등이 있고 파일 시스템 위에는 VFS(Virtual File System)으로 인터페이스를 통일합니다. 따라서 어떤 파일시스템을 읽어내더라도 VFS를 거쳐 동일한 구조로 캐싱이 이뤄집니다.

Linux는 페이지 단위로 디스크를 캐싱한다.

메모리에 2GB가 남았을 때 4GB 파일을 캐싱할 수 있을까요? 정답은 일부분을 캐싱 처리 가능합니다. 먼저 파일은 하나의 추상적인 단위로 작은 데이터 단위로 나누어져있습니다. 따라서 4GB 전체가 아니라 일부분만 불러와 캐싱이 가능하며 메모리가 남아있지 않으면 오래된 캐시를 버리고 메모리를 확보합니다. LRU(Least Recently Used) 방식으로 가장 최근에 사용한 메모리들만 남습니다.

메모리를 늘려서 I/O 부하 줄이기

이렇게 설명을 보면 메모리를 늘리면 캐시 용량도 늘어나고 실제 I/O 부하를 줄일 수 있습니다.

I/O 부하를 줄이는 방법

캐시에 의한 I/O 부하를 줄이려면 먼저 데이터 규모가 작을 수록 효과적입니다. 따라서 데이터 크기를 줄이기 위해 압축할 경우 캐시할 수 있는 양이 늘어납니다. 다만 비용적으로 고려해서 경제적으로 메모리를 추가로 구매하는 게 합리적인지 먼저 판단해야합니다.

복수 서버로 확장시키기

메모리를 늘려서 전부 캐싱할 수 없는 규모가 되면 먼저 복수 서버로 확장시키는 방법을 생각해볼 수 있습니다. CPU 부하분산은 단순하게 늘리면 되지만, I/O 분산의 경우 단순히 늘려서는 확장성을 확보할 수 없습니다.

단순히 대수만 늘려서는 확장성을 확보할 수 없다.

캐시 용량을 늘리기위해 데이터를 복사하고 대수를 늘리면 여전히 각 서버마다 캐시 용량이 부족한 채가 됩니다.

국소성을 살리는 분산

캐시 용량을 늘리기 위해서는 국소성(locality)을 고려해서 분산해야 합니다. 어떤 액세스일 때는 어떤 서버로 처럼 어떤 처리이냐에 따라 다른 서버로 분리하는 방법이 있습니다. 이런 액세스 패턴을 고려하지 않고 대수만 늘리면 병목 현상은 여전하게 됩니다.

파티셔닝

파티셔닝은 한 대였던 DB서버를 여러 대로 분할하는 방법입니다. 간단한 것은 테이블 단위 분할로 같이 사용하는 테이블끼리 묶어서 서버를 분할합니다. 다른 방법으로는 테이블 데이터 분할로 하나의 테이블을 여러 개의 작은 테이블로 분할합니다. 예를 들면 알파벳 순으로 나누어 해당 알파벳은 어떤 서버에 위치하는 식입니다. 이 분할의 문제점이라면 분할 기준을 조절하는 경우 데이터를 병합해야하는 번거로움이 있습니다.

요청 패턴을 섬으로 분할

요청에 따라 이미지 요청, 봇 요청, 사용자 요청등 캐싱하기 어려운 요청과 쉬운 요청으로 구분할 수 있습니다. 이 때 요청들을 구분해놓으면 캐시의 적중률이 올라가게 됩니다.

페이지 캐시를 고려한 운용의 기본 규칙

큰 서버의 경우 OS 기동 직후에 서버를 투입시키지 않습니다. 캐시가 쌓여있지 않아서 디스크 액세스가 오랫동안 발생해 서버가 내려가게 됩니다. 따라서 자주 사용하는 DB의 파일을 한번 읽는다던가 하는 방식으로 메모리에 올린 후 사용합니다.

분산을 고려한 MySQL 운용

분산을 고려한 MySQL 운용, 세 가지 포인트

OS 캐시 활용, 인덱스, 확장을 한다는 전제로 시스템을 설계가 그 포인트들입니다.

OS 캐시 활용

규모가 큰 테이블이라면 스키마 변경과 칼럼 변경에 주의가 필요합니다. 레코드는 가능한 작아지도록하고, 기본적인 타입의 크기는 숙지하고 오버헤드를 계산하는 것이 좋습니다. 정규화를 통해 칼럼들을 분리하는 것도 쿼리 계산속도를 감당 가능하다고 계산되면 분리하면 좋습니다.

인덱스의 중요성

인덱스에서 B트리가 사용되며 B트리와 이진트리의 차이점은 자식의 수입니다. B트리는 자식의 수를 조정할 수 있고 개수에 따라 노드의 크기를 정할 수 있습니다. 이렇게 크기가 정해지면 디스크는 노드를 찾아갈 때 자식 노드를 블록으로 모아서 이동해 최소화할 수 있습니다. 이분 트리의 경우 특정 노드를 모아서 1블록에 저장하기 어렵습니다.

인덱스의 효과

B트리 인덱스로 찾게 되면 선형으로 찾을 시 O(n)이던 시간을 O(log n)으로 줄일 수 있씁니다.

인덱스의 작용

쿼리에서 인덱스 작용이 될 수 있는 복수의 칼럼이 있을 시 하나의 인덱스만 사용됩니다. 복수의 칼럼을 모두 이용하려면 복수 칼럼을 이용한 복합 인덱스로 설정해야 합니다.

인덱스가 작용하는지 확인하는법

explain 명령으로 쿼리를 던지면 possible_keys로 인덱스를 사용하는지 확인할 수 있습니다.

MySQL의 분산

MySQL에는 기본 기능으로 레플리케이션이 있어 마스터 슬레이브 구조로 동일한 내용을 여러 대 준비할 수 있습니다. 데이터를 읽을 때는 로드 밸런서로 어느 DB에서 읽을지 분산시킬 수 있습니다. 커맨드의 경우 마스터로 직접 던져 처리하며, 슬레이브로 던지는 경우 동기화되지 않아 레플리케이션이 중지됩니다.

마스터 슬레이브의 특징

위를 생각해봤을 때 커맨드는 분산할 수 없다는 문제점이 있습니다. 대부분의 경우 쿼리 작업이 커맨드 작업보다 많기 때문에 커맨드 작업이 병목으로 발생하는 일은 많지 않습니다. 하지만 그럼에도 많은 경우 테이블을 분할하거나 테이블의 크기를 줄이는 방법으로 분산할 수 있습니다.

다른 방법으로는 RDBMS를 사용하지 않는 방법으로 RDB의 처리보다 간단하게 값을 저장하고 꺼낸다면 key-value를 고려해볼 수 있습니다.

MySQL의 스케일 아웃과 파티셔닝

파티셔닝으로 스케일 아웃 구조를 만드려면 이를 전제로 한 설계가 필요합니다. 예를 들면 서로 다른 서버에 있는 테이블끼리 JOIN하는 기능이 기본적으로는 없습니다. 따라서 JOIN 쿼리를 사용하는 테이블은 앞으로도 테이블끼리 서버 분할하지 않을 것이라는 것이 보장되야 합니다.

파티셔닝의 상반관계

파티셔닝으로 캐시 효과가 올라가지만 단점들이 있습니다.

  • 운용이 복잡해진다.
  • 고장률이 높아진다.

이렇게 고장률이 올라가면 내장애성을 위해서 여러 대로 분할해야 합니다. 이 때 안정적으로 하기 위해서는 1개의 마스터 + 3개의 슬레이브가 필요합니다. 슬레이브가 고장나면 다른 서버로 교체하면 문제가 없습니다. 마스터가 고장나는 경우 슬레이브 중 하나를 마스터로 변경하고 새로운 서버를 추가합니다. 슬레이브가 3대나 필요한 이유가 있습니다.

새로운 서버를 슬레이브로 복제할 시 기존 서버 중 하나는 쓰기 작업을 멈춰야 합니다. 전체 구성이 3대인 경우 하나는 고장나고, 하나는 복제를 위해 멈추면 하나의 서버에서 읽기와 쓰기를 모두하게 됩니다. 따라서 안정적인 구성을 위해선 4대를 1세트로 생각할 필요가 있습니다. 서버를 더 늘리더라도 4대를 1세트 기준으로 8대, 12대.. 이런 식으로 늘어나게 됩니다.