글을 시작하기에 앞서 해당 글은 이토 나오야, 다나카 신지의 저서인 『웹 개발자를 위한 대규모 서비스를 지탱하는 기술』 를 바탕으로 요약 및 정리한 글임을 알려드립니다.
왜 대규모 데이터 처리는 어려울까?
대규모
라는 단어는 참 애매하다. 숫자로 정확한 기준을 정의할 수 없기 때문이다. 보통 모든 IT 인프라는 서비스 성장에 따라 규모도 변하는데 아래의 조건을 신경써야 한다면 규모 단위를 변경해야 한다는 시그널이다.
- 부하분산: 엑세스 빈도가 더이상 현재 서버로는 처리할 수 없다. 부하를 분산하기 위해서 서버의 스케일 아웃이나 스케일 업이 필요하다.
- 다중성: 다루는 서버가 많아질수록 에러률도 올라간다. 특정 서버가 고장난다고 서비스 운영에 지장이 있는 구조를 벗어나야 한다.
- 효율적인 운용: 더이상 관리하는 서버 IP를 외울 수 없다. 클러스터로 묶어서 한번에 관리해주는 툴이 필요하다.
- 개발자 수와 개발방법: 이 많은 일을 나 혼자 다해야한다니 지옥이다. 개발자를 더 뽑고 코딩 규약을 정해서 표준화 하자.
위 조건들은 그나마 비교적 처리가 수월하다. 사람을 더 뽑고 플랫폼을 도입하고 웹서버 분산 셋팅은 비교적 어렵지 않기 때문이다. 사실 규모가 커질수록 가장 큰 어려움은 바로 대용량의 데이터 처리이다. 데이터를 분산시켜 처리하는 것은 여러모로 복잡하기 때문이다.
왜 많은 양의 데이터를 처리하면 시간이 오래걸릴까? 또 분산처리는 왜 어려울까? 이 부분에 대해서 정리해보자.
메모리와 디스크
메모리와 디스크 중에 데이터 처리에 있어 메모리가 더 빠르다는 사실은 대부분 알고 있다. 하지만 왜
더 빠른지에 대해 설명할 수 있는가?
먼저 디스크는 원반 모양의 데이터를 직접 저장하는 부분인 플래터와 데이터를 읽어드리는 부분인 헤드가 존재한다. 만약 유저가 어떤 데이터 엑세스를 요청한다면 플래터는 회전을 하고 헤드는 해당 데이터 위치의 자성을 파악하여 데이터를 읽는다. 즉 플래터가 회전을 하고 헤드가 해당 부분까지 이동을 하는 물리적인 이동 시간이 소요되는 것이다. 플래터의 회전속도(RPM, Revolution Per Minute)가 아무리 빨라도 1회 탐색 당 밀리초 정도가 소요된다.
반면 메모리는 물리적인 이동 없이 전기 신호로 모든 것들을 처리하며, 그렇기에 데이터 저장 위치에 따른 속도 차이가 없다. 메모리는 보통 1회 탐색하는데 마이크로초 정도가 소요된다. 비교해보면 1초에 디스크는 1000번, 메모리는 1000000번 데이터 탐색이 가능한 것이다.
이 외에도 CPU와 연결된 버스(Bus)의 데이터 전송속도가 꽤 다르다. 해당 부분은 아래 실습에서 더 자세히 살펴보자.
요즘 보조기억장치로 HDD 대신 SSD를 많이 사용한다. SSD는 물리적인 회전이 아니기에 탐색은 비교적 빠르지만 버스의 전송속도나 구조 등의 문제로 메모리보다는 빠르지 않다.
규모조정과 확장성
규모 조정은 크게 스케일 업과 스케일 아웃으로 나뉜다. 하지만 두 전략 중에 보편적으로 선택하는 것은 스케일 아웃 전략이다. 보통 하드웨어는 가격 상승과 성능이 비례로 올라가지 않기 때문이다. 새로나온 좋은 하드웨어가 성능은 2배가 되었지만 가격은 10배가 되는 현상은 주변에서 쉽게 찾아볼 수 있다. 또한 스케일 업과 스케일 아웃은 확장성에서도 큰 차이를 가진다. 스케일 업은 현재까지 상품화된 성능이 가장 좋은 하드웨어를 사용하면 그 이상 확장할 수 없지만 스케일 아웃은 프로그램이 매니징만 가능하다면 계속 하드웨어를 추가할 수 있기 때문이다.
보통 우리가 규모를 조정해야 한다고 느끼는 이유는 크게 2가지이다. 바로 CPU 부하와 I/O 부하이다. 두가지의 부하를 웹서비스 아키텍처를 기반으로 살펴보자.
웹 애플리케이션 구조에는 프록시, AP 서버, DB가 있고 보통 프록시를 통해 요청이 들어오고 이를 위해 AP서버가 DB에 필요한 데이터를 요청한다. 즉 프록시 -> AP -> DB 순으로 요청이 이루어지는 것이다. DB는 AP서버가 요청한 데이터를 찾아서 이를 반환하는데 이 과정에서 I/O가 발생하고 AP서버는 받은 데이터를 바탕으로 필요한 형태로 변환하여 클라이언트로 응답을 보낸다. 이 구조에서는 보통 DB가 I/O부하 발생률이 높으며, AP서버는 CPU 부하 발생률이 높다.
서비스가 성장하면서 두 부하가 발생한다면 보통 스케일 아웃 전략에 따라 서버를 추가한다. CPU 부하의 경우 비교적 스케일 아웃이 쉽다. 앞에 로드밸런서를 붙여 들어오는 일감을 여러 AP서버에 나눠주면 되기 때문이다. 하지만 I/O부하는 조금 다르다. 하나의 서비스는 동일한 시간에 동일한 데이터를 유지해야 한다. 만약 사용자 A는 코드가 123로 보이는데 사용자 B는 동일한 코드가 456으로 보인다면 이는 데이터 정합성 문제로 이어진다. 따라서 여러 DB서버는 실시간으로 동기화가 이루어져야 한다. 이 부분이 확장성에 많은 어려움을 보여준다.
“대규모”를 위한 준비
대규모 데이터를 다루기 위한 준비로는 아래와 같다.
- 최대한 메모리에서 처리를 끝내기: 디스크 탐색 횟수를 최소환하기, 국소성을 활용한 분산실현
- 데이터량 증가에 강한 알고리즘 데이터 구조: 선형검색 -> 이분검색
- 데이터 압축, 정보검색기술
마지막으로 위와 같은 프로그램을 개발하기 위해서는 아래의 3가지를 알고 있어야 한다.
- OS 캐시
- 분산을 고려한 RDBMS 운용
- 알고리즘과 데이터 구조
실제 사용툴
Docker에서 직접 Centos7 서버를 셋팅해서 실습해보자. 먼저 실습할 환경에 Docker가 설치되어 있어야 한다. 만약 설치되어 있지 않다면 Docker 홈페이지에서 설치파일을 다운받아 설치해야 한다.
도커 환경이 구성되었다면 아래의 명령어로 간단한 Centos7 컨테이너 하나를 생성하자.
1 | ❯ docker pull images centos:7 |
그후 컨테이너를 조회해보면 생성된 것을 확인할 수 있다. 그후에 옵션 -it
룰 사용하여 해당 컨테이너에 접속하면 된다.
1 | # 생성 확인 및 컨테이너 ID 복사 |
이렇게 컨테이너에 접속했다면 실습 준비가 완료된 것이다.
top
top 명령어는 내장되어 있기 때문에 터미널에서 바로 명령어를 입력하면 된다.
1 | > top |
여기서 주요하게 볼 부분은 load average
이다. load average
는 시스템 전체 부하상황을 나타내며, 위에서 봤던 CPU 부하와 I/O부하를 합쳐서 나타낸다. 따라서 해당 인자값이 높은 경우는 현재 서버에 부하가 있다는 의미이다. 하지만 load average
는 둘 중 어떤 부하가 발생한건지 자세하게 보여주지는 않는다. 좀 더 정확하게 판단하기 위해서 sar
명령어를 사용해보자.
sar
먼저 sar 패키지를 아래의 명령어로 설치한다.
1 | yum install sysstat |
그다음 아래의 명령어로 프로세스를 시작한다.
1 | systemctl start sysstat |
그 다음 아래의 명령어를 입력해서 CPU 정보들을 출력한다.
1 | sar -u 2 5 |
옵션 -u
는 정보를 프린트하라는 의미이며, 뒤의 2는 2초 간격으로 5는 행을 나타낸다. 위 명령어로 출력된 결과는 아래와 같다.
1 | Linux 5.10.25-linuxkit (d2efd0bffa79) 10/19/22 _x86_64_ (4 CPU) |
위 인자값 중에서 %iowait
이 CPU가 디스크 I/O 대기를 기다린 시간 비율이다. 여기서 I/O 이슈를 확인할 수 있다.
hdparm
hdparm 패키지 또한 아래의 명령어로 설치한다.
1 | yum install hdparm |
설치가 끝났다면 hdparm
명령어를 통해 메모리와 디스크 버스 throughput을 측정할 수 있다.
먼저 측정할 디스크를 확인해보자.
1 | > df |
현재 환경에서는 디스크가 /dev/vda1
에 마운트되어 있다. 해당 Path를 가지고 아래의 명령어를 입력해보자.
1 | hdparm -tT /dev/vda1 |
위 명령어에서 -t
는 디스크 드라이브의 읽는 속도를 체크하고 -T
는 디스크 드라이브에 대한 캐시데이터 읽는 속도를 체크한다. 디스크 캐시는 메모리에 올라가기 때문에 이를 디스크와 메모리의 속도 차이로 추산할 수 있다.