*이 포스트는 Todd Hoff 님의 허락을 받아 highscalability.com 의 글 “Google Pro Tip: Use Back-Of-The-Envelope-Calculations To Choose The Best Design“을 번역한 것입니다. 저작권에 유의하시기 바랍니다.
어떤 문제가 주어졌을 때, 최적의 설계가 무엇일지는 어떻게 알 수 있을까요? 예를 들어 30개의 섬네일을 표시하는 이미지 검색 결과 페이지를 만들어야 한다고 합시다. 이미지는 순차적으로 로드할까요? 병렬 처리할까요? 캐싱은? 어떻게 결정해야 할까요?
만약 여러분이 마블의 닥터 스트레인지 같은 초능력자라면, 다중 우주에서 가능한 모든 설계를 시험해보고 그중에 가장 적합한 것을 고르면 됩니다. 정말 실용적인 조언이죠?
다른 선택지로 알고리즘의 Big-O 노테이션을 따져볼 수도 있겠죠. 전산적 사고(Computational Thinking) 황금기의 선지자인 구글은 당연히 따져보고 있을 겁니다. 또 다른 선택지는 무엇이 있을까요? 구글이라면 어떻게 설계할까요?
봉투 뒷면 계산법을 사용해 설계해보자
봉투 뒷면 계산법은 구글의 인프라 마법학교 교장 전설이자 위대한 개발자인 제프 딘Jeff Dean이 스탠포드 대학교 초청 강의에서 소개한 계산법입니다. (제프 딘은 구글의 핵심 시스템인 광고 시스템, 검색 엔진, MapReduce, ProtocolBuffers 등을 설계했습니다.)
*역자주: 봉투 뒷면 계산법이라는 명칭은, 주변에 흔히 나뒹구는 봉투 뒷면에 잠깐 대략적인 계산을 해보는 것을 빗댄 영어 표현입니다.
봉투 뒷면 계산법은 기본적인 동작의 대략적인 소요 시간을 숙지해두고, 일종의 사고실험을 통해 어떤 설계가 요구사항을 충족하는지 계산하는 방법입니다. 제프 딘은 시스템을 직접 빌드해볼 필요 없이, 봉투 뒷면 계산법을 통해 성능을 예상해보는 능력이 모든 소프트웨어 엔지니어가 가져야 할 능력이라고 강조했습니다.
제프 딘이 제시해준 훌륭한 예제들이 있지만, 그 전에 먼저 알아야 할 내용이 있습니다.
모두가 알아야 하는 수치들
위에서 언급했던 대로, 설계를 계산해보기 위해서는 기본적인 동작의 대략적인 소요 시간을 숙지하는 것이 중요합니다. 제프 딘이 제시한 리스트는 아래와 같습니다.
*역자주: 2011년 자료이므로 절대 수치로는 현대 장비 성능과 차이가 꽤 클 수 있습니다 ^^;
- L1 캐시 참조: 0.5 ns
- 분기 예측 실패: 5 ns
- L2 캐시 참조: 7 ns
- Mutex 락/언락: 100 ns
- 메인 메모리 참조: 100 ns
- Zippy 라이브러리로 1K bytes 압축: 10,000 ns
- 1 Gbps 네트워크로 2K bytes 전송: 20,000 ns
- 메모리에서 순차적으로 1MB 읽기: 250,000 ns
- 동일한 데이터센터 내 왕복: 500,000 ns
- 디스크 탐색: 10,000,000 ns
- 네트워크에서 순차적으로 1MB 읽기: 10,000,000 ns
- 디스크에서 순차적으로 1MB 읽기: 30,000,000 ns
- 캘리포니아에서 네덜란드로 패킷 왕복: 150,000,000 ns
짚고 넘어갈 내용들:
- 여러 동작의 성능 단위 차이를 주목하자.
- 데이터센터들은 멀리 떨어져 있으므로 뭔가 전송하려면 오래 걸린다.
- 메모리는 빠르고 디스크는 느리다.
- 효율적인 압축 알고리즘을 통해 많은 네트워크 대역폭을 절약할 수 있다.
- 쓰기 연산은 읽기보다 40배 이상 비싸다(느리다).
- 글로벌 공유 데이터는 비싸다. 이건 분산 시스템의 근본적인 한계다. 자주 쓰이는 공유 객체의 락 경합은 트랜잭션을 직렬화하고 느리게 만들어 성능을 크게 저하한다.
- 늘어날 쓰기 연산을 대비해 설계하자.
- 쓰기 경합을 줄이기 위해 최적화하자.
- 넓게 최적화하자. 쓰기 연산은 최대한 병렬 처리하자.
예제: 섬네일 30개 이미지 검색 결과 페이지 설계
제프 딘이 제시한 예제입니다. 두 가지 설계를 사고 실험에 사용했습니다.
설계 1 – 순차 (Serial)
- 이미지를 순차적으로 읽는다. 디스크를 탐색한다. 256K 이미지를 읽고 다음 이미지로 넘어간다.
- 성능: 30 탐색 * 10 ms/탐색 + 30 * 256K / 30MB / s = 560 ms
설계 2 – 병렬 (Parallel)
- 병렬로 읽는다.
- 성능: 10 ms/탐색 + 256K / 30MB / s = 18 ms
- 각 디스크 읽기 시간에 오차가 있으므로, 30~60 ms 정도가 현실적이다.
어떤 설계가 최적일까요? 요구사항에 따라 다르겠지만, 봉투 뒷면 계산법을 이용하면 빌드 없이 설계들을 비교해볼 수 있습니다.
이제 스스로에게 설계 질문을 던지고 여러 설계들을 비교해볼 수 있는 일종의 프레임워크가 생겼습니다.
- 섬네일 이미지를 각각 캐시하는게 좋을까?
- 30개 전부를 한 세트로 묶어 하나의 엔트리에 캐시해야할까?
- 섬네일을 전처리하는게 좋을까?
이런 주제들을 현실적으로 계산하려면 서비스들의 성능을 알고 있어야 합니다. 만약 알 수 없다면, 그 부분을 빠르게 프로토타입해서 확인해볼 수 있습니다. 예를 들어 캐싱을 하는게 나을지 아닐지를 판단하려면, 캐시에 쓰는 동작은 얼마나 오래 걸릴지를 알아야합니다.
마치며
여기까지가 번역한 내용입니다. 후반부는 글쓴이 요약, 감상 위주라서 잘라냈습니다. 거의 10년이 다 되어가는 자료라서 제시된 수치같은 것들은 동작간의 소요 시간 비율 정도만 참고해야겠다는 생각이 듭니다.
그래도 글이 설명하고자 하는 것이 무엇인가를 생각하면 도움이 될 것 같습니다. 무언가 설계를 할 때 당연히 이쪽이 더 빠르겠거니 하는 식으로 결정하지 말자. 실제로 각 동작은 얼마나 시간을 소요하는지, 각 서브시스템은 처리 속도가 얼마나 빠른지를 파악하고 있어야 한다. 같은 내용들 말이죠.
원본 글에서는 제프 딘의 발표 링크가 만료되어 있는데, 구글에 간단히 검색해보니 당시 발표 영상을 찾을 수 있었습니다. 링크 58분 20초쯤부터 설명하는 내용입니다. 영어가 된다면 처음부터 들어보는 것도 좋을 것 같네요.
구글의 전설적인 개발자가 소개한 방법이니 구글 입사를 목표로 하고 있다면 알아두었다가 면접에서 은근슬쩍 뽐내보는 것도 도움이 될 수 있겠네요.😉
서툰 번역 읽어주시느라 고생하셨습니다.
비슷한 내용을 알고리즘과 관련해 설명한 아래 글도 관심있다면 읽어봐주세요!