[웹 개발자를 위한 대규모 서비스를 지탱하는 기술]Chapter 4

zzarbttoo·2021년 8월 4일
0

이 글은 절판도서 "웹 개발자를 위한 대규모 서비스를 지탱하는 기술" 을 개인적인 용도로 정리한 글입니다. 모든 내용을 정리한 것이 아니라 필요한 부분만 정리했다는 점 양해 부탁드립니다
문제/오류가 있을 시 댓글로 알려주면 감사하겠습니다

DB 스케일아웃 전략

분산을 고려한 MySQL운용


| 인덱스를 올바르게 운용하기

분산을 고려한 MySQL 운용의 대전제

  • OS 캐시 활용
  • 인덱스를 적절하게 설정
  • 확장을 전제로 한 설계

| OS 캐시 활용

  • 전체 데이터 크기에 주의해서 데이터량이 물리 메모리보다 가능한 적어지도록 유지하고, 메모리가 부족하면 증설한다
  • create table로 스키마를 결정할 때 대충 결정하는 경우도 많겠지만, 테이블 규모가 커지면 중요해진다(스키마를 조금 변경하는 것만으로 기가바이트 단위로 데이터가 증감한다)
  • 댜량의 데이터를 저장하려는 테이블은 레코드가 가능한 작아지도록 컴팩트하게 설계한다
  • 정수 32비트 4바이트, 문자열 8비트 1바이트 인 것을 외워두면 대략 몇바이트 정도의 오버헤드가 발생하는지 알 수 있다
+ 정규화에 관해서 

- 한 테이블에 4가지 필수항목이 있고 필요할 때만 사용하는 항목 두가지가 있다고 생각해본다 
- 정규화를 하면 이 두개를 분리하도록 하면, 많은 바이트를 줄일 수 있다 
- 있다/없다를 표현하는 경우 1바이트로 설정하면 된다(플래그)
- 만일 가지고 있는 데이터만으로 물리 메모리 크기랑 비슷하다면, 새로운 컬럼의 추가는 무리라고 생각하면 된다 

| 인덱스의 중요성

  • 인덱스는 검색을 빠르게 하기 위한 용도로 트리구조로 되어있으며 MySQL 인덱스는 B+트리 데이터 구조이다(다분트리, 평형트리인 B트리에서 파생되었으며, 하드디스크 상에 구축하기에 알맞은 데이터 구조이므로 DB에서 많이 사용된다)
  • 삽입/탐색할 때 루트부터 시작해서 각 노드에 찾고있는 값이 저장되어있느지 확인하고, 없으면 자식을 찾아간다
  • 이 때 찾는 값의 대소 관계로 어떤 자식을 찾아가면 될지가 한 번에 결정될 수 있는 규칙이 사용된다
  • 최대 트리 높이만큼의 횟수만 자식을 찾아가면 되므로 탐색이 빨라지게 된다(O(logn))
  • 계산량 측면 (O(n) -> O(logn)) 에서 개선이 될 뿐만 아니라 디스크 구조에 최적화 된 인덱스를 사용해서 탐색함으로써 디스크 Seek 횟수 면에서도 개선된다

| 이분 트리와 B 트리 비교

  • 이분 트리는 노드가 반드시 하나로 정해져있고, 자식이 반드시 두개이지만 B트리는 갯수를 조정할 수 있기 때문에 노드의 크기를 적당한 사이즈로 정할 수 있다
  • B 트리에서 노드 1개를 디스크 1블록만큼 할당하면, 그 아래에 있는 각 노드들을 딱 1블록만큼으로 해서 저장할 수 있다
  • 디스크 Seek 발생 횟수를 노드를 찾아갈 때만으로 최소화할 수 있다(데이터 분산을 막을 수 있다)

| 인덱스 효과의 예, 작용

  • 데이터가 1000건 정도라면 트리를 먼저 순회하는 오버헤드가 더 커서 그냥 처음부터 찾아내려가는 편이 더 빠른 경우일 수 있다
  • 하지만 크기가 커지면 인덱스 없이는 시작부터 엑세스할 수 없는 상황이 되므로 인덱스는 중요하다
  • MySQL은 레코드 총 건수를 보고 인덱스를 사용하지 않는 편이 더 빠르다라고 판단되면 사용하지 않는 최적화 작업을 내부에서 수행하게 된다
  • MySQL에서는 인덱스를 걸어놓고 있는 칼럼을 대상으로 한 쿼라라도 던지는 SQL에 따라서는 그것이 사용되거나 사용되지 않기도 한다
  • MySQL에서 alter table 명령 등으로 명시적으로 인덱스를 추가한 경우 이외에도 Primary KEy, UNIQUE 제약을 건 칼럼에도 인덱스를 가지고 있다(show index 명령으로 인덱스 내용 확인)
  • 복수의 칼럼이 인덱스 작용의 대상이 되는 경우 하나의 칼럼만 인덱스로 사용될 것이다

select * from entry where url like '~~~' order by timestamp
  • url, timestamp가 모두 index로 걸려있을 때 하나만 인덱스를 사용하고 나머지는 사용하지 않게 된다
  • 위의 경우 두개 모두 index로 태우려고 할 경우 (url, timestamp)를 쌍으로 한 복합 인덱스를 사용해야 한다

| 인덱스가 작용하는지 확인하는 법(explain 명령)

  • explain 명령을 실행하면 MySQL 이 인덱스가 작용하는지 여부를 전부 조사해준다
  • 다음과 같이 사용
explain select url from entry where eid = ~~~;
-> index가 작용하는 row의 갯수가 나온다  
explain select url from entry use index(cname) where eid = ~~;
  • SQL에 수반하는 프로그램 개발 시 속도에 신경쓰고자 하면 index 작용 여부를 explain으로 확인하는 것이 좋다
  • 인덱스 작용법이라는 의미에서 Extra 열도 중요(where 이외에 Using filesord, Using temporary가 나올 경우가 있지만, 이 두 경우는 좋지는 않음)
  • explain 명령의 결과 속도와 실제 결과 속도는 차이가 있음(explain 명령어가 빠르다고 빠른 쿼리는 아님)

| MySQL 의 분산

확장을 전제로 한 시스템 설계

| MySQL의 레플리케이션 기능

  • 마스터(master)을 정하고 마스터를 따르는 슬레이브(slave)를 정해두면 마스터에 쓴 내용을 슬레이브가 폴링(polling)해서 동일한 내용으로 자신을 갱신하는 기능
  • 슬레이브는 마스터의 레플리카(replica)가 되며, 이렇게 동일한 서버를 여러 대 마련할 수 있다
  • (여러 대의 AP 서버) -> 로드 밸런서 -> (여러 대의 MySQL 슬레이브) - 마스터
  • AP 서버에서는 로드밸런서를 경유해서 슬레이브로 질의하고, 이렇게 쿼리를 여러 서버로 분산할 수 있다
  • 어플리케이션 구현에서 select와 같은 참조 쿼리만 로드밸런서로 흘러가게 하고, 갱신 쿼리는 마스터로 직접 던진다(갱신 쿼리를 슬래이브로 던지면 마스터와의 동기화가 어렵다)
  • MySQL에서 마스터와 슬레이브의 불일치를 감지하면 레플리케이션을 중지하게 된다 (이에 대한 제어를 O/R 매퍼에서 한다)
  • 로드 밸런서 대신 MySQL Proxy 와 같은 것을 사용할 수 있다

| 마스터/슬레이브의 특징

  • 위 같은 구성으로는 마스터는 분산할 수 없다는 문제가 생긴다
  • 참조계열 쿼리는 확장을 위해 서버를 늘리면 되는데, 서버를 늘린다고 해도 대수를 늘리기 보다는 메모리에 맞추는 것이 중요하다
  • 마스터는 확장할 수 없다(할 수 없는건 아니지만 험난)
  • 어플리케이션 중 대략 90%가 참조계열 쿼리이다 때문에 참조계열에 비하면 마스터가 병목이 되는 경우는 거의 없다
  • 하지만 발자국(히스토리)와 같은 기능의 경우 쓰기 요청이 많이 생기기 때문에 부하가 걸리는 경우가 있다

| 갱신/쓰기 계열의 확장

  • 테이블을 분할해서 테이블 크기를 작게 해준다 -> 분할로 인해 쓰기 작업이 분산되게 된다
  • 테이블 파일이 분산되면 동일 호스트 내에서 여러 디스크를 가지고 분산시킬 수 있으며, 서로 다른 서버로 분산할 수도 있다
  • 처음부터 RDMS를 사용하지 않는 것도 방법이다
  • key-value 스토어는 오버헤드도 적고 압도적으로 빠르며 확장하기 쉽다(단순 값 저장/호출, 통계 처리/범용 정렬 필요 없을 경우)

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

  • 데이터가 메모리에 올라가는 크기면 메모리에 올린다
  • 아니면 메모리의 증설
  • 메모리 증설이 불가능하면 파티셔닝

| 파티셔닝에 대한 보충

  • 파티셔닝 : 테이블 A와 테이블 B를 다른 서버에 두어 분산하는 방법
  • 국소성을 활용해 분산할 수 있으므로 캐시가 유효하여 효과적인 방법

| 파티셔닝을 전제로 한 설계

  • entry와 tag 테이블이 있고, entry와 tag가 1:n 관계라고 하면 RDBMS에서는 이런 데이터를 뽑을 때에는 entry와 tag 두개를 JOIN하는 쿼리를 던진다
  • JOIN쿼리를 던지기 위해서는 entry와 tag 테이블을 분할 할 수 없다(즉 다른 머신으로 나눌 수 없다)
  • MySQL에서는 서로 다른 서버에 있는 테이블을 JOIN 하는 기능이 없다(대상이 되는 테이블을 앞으로도 서버 분할하지 않을 것이라고 보장할 수 있을 때에만 사용한다)
  • JOIN을 사용한다면 두 테이블을 다른 서버에 위치시킬 수 없지만, JOIN을 사용하지 않는다면 쿼리를 두개 사용하는 방식을 사용할 수 있다(tag 테이블에 entry 에 대해 질의해 entry id 를 뽑아내고, 그 값을 이용해 where in 쿼리를 이용해 entry에 질의)
  • 밀접하게 연관이 있고, JOIN을 해야한다면 같은 서버에 두고, 테이블 크기를 볼 때 반드시 분할해야 하는 경우는 JOIN을 하지 않는다

| 파티셔닝의 상반관계 (근데 이건 클라우드 이전의 내용이라 좀 오래된 것 같기도)

  • 파티셔닝을 하면 나쁜점도 있다
  • 용도가 다른 서버가 여러개가 생기기 때문에 운용이 어렵고 테이블 정보가 숙지되어있지 않으면 장애에 대응하기 어렵다
  • 대수가 늘어나는 만큼 고장확률이 높아진다
  • 참고로 DB 서버는 다중화 해야하기 때문에 분할과 동반해서 머신을 늘릴 때 1대만 늘려서는 안된다
  • 기본적으로는 4개가 1세트이다(마스터 하나, 슬레이브 3개) 그래서 분할을 할 때 4대가 늘어난다고 생각하면 된다
  • 만일 슬레이브 하나가 고장났는데, 슬레이브가 두대밖에 없었다면, 데이터 복사 시 서비스 중지를 시켜야 복구를 할 수 있는 불상사가 일어나기 때문이다
  • 물론 무중단 배포를 안해도 되는 상황이라면 저렇게까지 할 필요는 없다
profile
나는야 누워있는 개발머신

0개의 댓글