Software Engineering at Google
<구글 엔지니어는 이렇게 일한다(Software Engineering at Google)>는 구글에서 10년 이상 근무한 시니어 개발자들이 직접 저술한 서저이다. 책의 큐레이터 타이터스 윈터스는 2억 5천만 줄의 C++ 코드로 이루어진 코드베이스의 라이브러리 리더를 담당하고 있으며, 큐레이터 톰 맨쉬렉은 구글의 테크니컬 라이터로 근무하고 있다. 큐레이터 하이럼 라이트는 구글의 코드 베이스를 가장 많이 수정한 사람으로 손꼽힌다. 그 외에도 수많은 구글의 엔지니어들이 공동으로 책을 검수하고 자신들만의 노하우를 녹여내며 저서를 집필했다. 이들이 전하는 구글 엔지니어들의 방식을 알아보자.
이 책의 가장 큰 장점은, 무료이다. 이 링크에서 저자들이 무료로 공개한 책의 원본을 읽을 수 있다. 영어로 작성되어 있긴 한데, 만약 영어를 읽지 못한다고 하더라도 요새는 deepL 등 번역기 성능이 좋아서 번역해가면서 읽어볼 수 있다.
책의 흐름
책은 크게 세 대단원으로 나뉜다. 첫 장은 전제로, 이 책이 쓰여진 목적과 개요, 그리고 전제 조건과 배경 지식들을 약속한다. 제2장은 문화로, 소프트웨어 팀에서의 생산성을 높이기 위한 문화, 조직의 유지와 성장을 위한 문화 등을 기술한다. 제3장은 프로세스로, 실제 구글에서 작업하는 엔지니어링의 방식을 기술한다. 이곳에서 어떻게 코드를 리뷰하는 지, 코드의 규칙과 가이드라인을 어떻게 제시하는지, 테스트는 어떻게 관리하는지 등을 알 수 있다. 마지막 장은 도구로, 버전 관리 도구, 정적 분석 툴, 의존성 관리 도구, CI/CD 도구 등 구글에서 개발에 필요한 유용한 도구들을 어떻게 사용하고 관리하는 지를 알 수 있다.
이번 글에서는 첫 장 개요에 대해서 이야기해볼 것이다. 소프트웨어 엔지니어링이 무엇인지 시간, 규모, 트레이드오프의 관점에서 알아보고, 구글의 철학을 살펴볼 것이다.
주의
언제나 그렇듯이, 이러한 책을 읽을 때 주의해야 할 것들이 있다. 우리는 구글이 아니다. 대부분의 회사는 사실 구글만큼 돈이 많지도 않고, 일하는 사람들 역시 그 능력과 성격, 문화적 배경이 모두 다르다. 규모도 다르고, 목적도 다를 수 있다. 구글은 거대 SW 회사로, 10년 이상 지속되고, 그 소프트웨어가 전 지구의 사람들에게 영향을 미치는 규모의 프로젝트를 다루고 있다. 이 문화와 철학을 자칫 2-3년 미만의 스타트업이 무조건적으로 받아들이게 되는 것을 주의해야 한다. 책에서 이야기하는 기반과 문화를 다지는데 투자한 비용이 회수되기 전에 문을 닫을 수도 있기 때문이다. 그리고 책에서 이야기한 방법론은 구글만의 방법이지, 모든 회사에 적용되는 법칙도 아니며, 구글 역시 자체적으로 이 방법을 시간이 지남에 따라 문제점을 찾고 개선하고, 변경할 수 있다. 즉 언제나 옳은 방법은 없으며, 구글이라는 권위에 기대어 무조건적인 신뢰를 보내는 것에 주의해야 한다.
또한 더 주의해야 할 것은, 책에서 이야기하는 것을 정확히 알기 위해서는 책을 직접 읽어보아야 한다. 나는 이 책을 읽고서 전할 때 나의 생각과 의견, 가치관과 경험이 녹아있을 수밖에 없다. 즉 책에서 이야기하는 내용 자체가 변질된다. 그러니 진짜로 구글에서 어떻게 일하는 지를 알기 위해서는, 책을 직접 읽어보자.
소프트웨어 엔지니어링이란 무엇인가?
프로그래밍과 소프트웨어 엔지니어링의 가장 큰 차이는 무엇일까? 시간, 확장, 실전에서의 트레이드 오프(time, scale, trade-offs at play)이다.
소프트웨어는 시간에 따라 변화한다. 이러한 변화는 필연적이다. 따라서 소프트웨어 엔지니어링은 시간에 따라 변하는 외부적, 내부적 요구에 대해 대응할 수 있어야 한다.
한편 규모의 관점에서도, 소프트웨어는 참여 인원, 시간의 흐름에 따른 엔지니어들의 유지 보수 관리 역량 등을 평가할 수 있다. 팀의 조직, 프로젝트 구성, 정책 및 관례 등은 모두 SW 엔지니어의 복잡성을 좌우하고, 이 문제는 대부분 규모의 문제로 이어진다. ‘조직이 성장하고 프로젝트가 확장될수록 소프트웨어의 생산 효율도 높아지는가?’, ‘개발 워크플로우의 효율도 성장에 발맞춰 개선되는가?’ 등의 규모에 따른 소통 문제와 인력 관리 문제는 SW 엔지니어링 초창기부터 논의되어 왔다.
또한 SW 엔지니어링에서는 여러 불확실한 지표에 기대어 선택을 평가하고 트레이드오프를 평가해야 한다. SW 엔지니어는 지속 가능성을 잃지 않으면서 트레이드오프를 평가하여, 결과에 지대한 영향을 주는 여러 결정들을 만들어야 한다.
시간, 규모, 트레이드 오프에 대해서 더 자세히 이야기해 보자.
시간과 변경
한 가지 생각해야 할 것은, 모든 소프트웨어의 수명은 동일하지 않다. 정확히 말하면, 어떤 소프트웨어는 다른 소프트웨어에 비해 더 긴 수명을 갖게 되고, 이 긴 수명을 갖는 소프트웨어는 그만큼 코드베이스 역시 긴 시간 동안 누적되고 관리된다.
모바일은 슬프지만 수명이 길지가 않다. 보통 2-3년 정도 간다면 성공한 편이다. 5-6년 간다면, 매우 오래 간 편이라고 할 수 있다.
스타트업의 소프트웨어 역시 수명이 길지가 않다. SW의 기반을 다지는데 투자한 비용이 회수되기 전에, 보통 스타트업이 문을 닫을 확률이 높다. 그래서 스타트업의 SW들은 일단 살아남기 위해서, 당장 눈 앞에 보이는 이득을 회수하여야 한다.
이렇게 수명이 짧은 소프트웨어는, 장기적인 변화에 대응할 필요가 없다.
그러나 구글에서 관리하는 수많은 기술들, 예를 들어 구글 독스, 검색 엔진, 데이터베이스 시스템, http 웹서버 등을 생각해 보자. 이러한 소프트웨어들은 그 수명이 기본 10년 이상 간다. 이러한 소프트웨어들은 언제 그 수명이 다할지, 언제쯤 이제 더 이상 유지 보수와 업그레이드가 필요 없어질지 한계를 정하기 어렵다. 사실 이런 소프트웨어들은 반평생 간다고 봐야 한다.
이러한 코드 베이스를 관리하는 것은 단순한 프로그래밍의 관점과 문제 접근 방식으로는 풀어낼 수 없는, 다른 차원의 문제이다. 단명하는 시스템과는 다르게, 수명이 길어질수록 변경이라는 요소가 점점 중요해지고, 프로그램이 10년 이상 살아남는다면 처음과는 거의 모든 의존성(외부 라이브러리, 운영 체제, 기반 프레임워크 등)이 달라지게 될 것이다. 흔히 생각하는 프로그래밍과 SW 엔지니어링을 가르는 가장 큰 핵심은 바로 이 사실을 인식하는데서 출발한다. 이 차이는 바로 SW의 지속 가능성의 핵심이다. 소프트웨어는 기술적이든 사회적이든 기대 생애 동안 요구되는 내부, 외부적 변화에 대응할 수 있는 역량을 의미한다.
책에서는 소프트웨어의 수명과 업그레이드의 중요성의 상관 관계를 이야기한다. 당장 한 번 쓰고 버릴 파이썬 스크립트에 OS의 신버전에 대응하거나, python 버전 업그레이드에 신경 쓸 필요는 없다.
그러나 만약 구글 검색 엔진이 아직도 1990년 대의 시스템, 코드 베이스에서 머물고 있다고 생각해보자. 문제가 있지 않겠는가.
하이럼의 법칙
모든 변화는 누군가의 워크 플로우와 충돌한다.
하이럼의 법칙은 소프트웨어의 수명과 업그레이드의 중요성을 설명하는 법칙이다. 하이럼의 법칙은 다음과 같다.
API에 충분한 수의 유저가 있다면, 명세에서 지정된 것은 아무런 상관이 없다:
시스템에서 관측될 수 있는 모든 행동 양식은 다른 이들에게 달려있을 것이다.
— 하이럼 라이트
SW 엔지니어링에서 ‘동작하는 것’과 ‘유지보수 가능한 것’은 큰 차이를 갖고 있다. 그리고 이는 API 사용과도 관련되어 있는데, 하이럼의 법칙에 따르면 API의 명세화는 크게 중요하지 않다. 시스템에서 가능해 보이는 모든 동작들을 누군가는 이용하게 될 것이다.
하이럼의 법칙은 소프트웨어 엔트로피의 개념으로도 볼 수 있다. 충분한 사용자의 수가 확보되고, 소프트웨어가 사용된 시간이 길어질수록, 소프트웨어의 엔트로피는 증가하게 된다. 이렇게 되면, 소프트웨어의 명세는 중요하지 않다. 아무리 API 명세서를 꼼꼼히 작성한다 하더라도, 누군가는 명세에 없는, 예상치 못한 방법으로 소프트웨어를 사용하고 있을 것이다.
그리고 이 행동이 유용하게, 널리 쓰이게 된다면, 이 역시 소프트웨어의 변경을 어렵게 만드는 요인으로 작용한다.
변하지 않는 프로그램이 위험한 이유
우리는 소프트웨어의 변화에 대해서 이야기하고 있다. 그런데 왜 반드시 소프트웨어는 변해야 하는가? 아예 처음부터 잘 설계해서 변화가 없도록 하면 되는 문제 아닐까?
만약에 어느 한 장기 프로젝트가, 오랜 시간이 지나지 않도록 설계하고, 순수한 C 언어로 작성되었다고 가정하자. 이런 프로젝트는 10년이 지나도 불변성과 영속성을 유지할 수 있을까?
답은 당연히 아니오이다. 여기 몇 가지 근거가 있다.
일단, 사실 C 언어 자체도 변화한다. 3년마다 표준이 바뀌는 C++이나, 수개월 주기로 새 버전이 배포되는 go나 dart 언어 등과 비교하면 당연히 변화의 속도는 느리지만, C 언어 역시 계속해서 변화한다. 가장 최근의 C 표준은 C17이다.
그런데 이를 차치하더라도, 여기 책에서 제시한 몇 가지 근거가 있다.
보안 문제를 생각해 보자. 20년 전 소프트웨어가, 2000년 대 중반 이후부터 발견된 보안 취약점에 대비하지 않고 설계됐다면, 문제가 발생할 것이다. Heart Bleed 어택이나, 2010년대 이후에 나온 Spectre, Meltdown 등의 CPU 자체의 구조적 문제점을 공략해서 나온 공격들이 있다. 이 공격들에 대비하지 않는 소프트웨어는 영속하기 어렵다.
기술의 변화 역시 소프트웨어의 변화를 요구하는 중요한 외부적 변화이다. 30년 전이나 지금이나 여전히 연결 리스트, BST 등의 자료 구조는 잘 작동한다. 그러나 점차 하드웨어가 발달하고, CPU 클럭 속도와 메모리 레이턴시 간의 격차가 벌어지면서, 효율적인 알고리즘의 형태가 변하고 있다.
이제 연결 리스트는 캐시 미스 문제 때문에 비효율적인 알고리즘이 되었고, BST 역시 이를 대체할 더 좋은 알고리즘들이 많이 등장하였다. 대신 vector, hashmap, B tree, tries 등 대체제가 등장하면서 효율적인 알고리즘의 형태 역시 진화하고, 이에 따라 소프트웨어의 변화 요구 역시 촉진된다.
참고로 연결 리스트의 캐시 미스 문제의 경우 이 글에서 다룬 적이 있으니, 혹시 모른다면 참고하길 바란다.
규모의 확장
프로젝트의 모든 것은 확장 가능해야 한다. 심지어 코드 베이스 그 자체, 빌드 시스템, 버전 관리 시스템 등의 모든 것을 포함해서 확장 가능해야 한다.
사실, 가만히만 있어도 악화되는 지표들이 존재한다. '전체 빌드에 걸리는 시간', '레포지토리에서 전체 코드를 새로 내려받는 시간', '프로그래밍 언어 버전을 업그레이드하는 데 걸리는 시간' 등은 가만히만 있어도 점차 코드 베이스의 크기가 커짐에 따라 악화되는 지표이다.
만약에 이러한 변화들을 무시하고 가만히 있는다면, 끓는 물속의 개구리처럼, 어느 순간 위기가 찾아왔을 때 이에 대응할 능력을 잃게 될 것이다.
확장 불가능한 정책들
조직이 10배로 늘어날 때 작업도 10배 많아지는가?, 엔지니어가 할 일의 양이 조직이 커질수록 늘어나는가?, 코드베이스가 커질수록 작업량도 늘어나는가?
이 중 하나라도 해당할 때, 작업을 자동화하거나 최적화할 수단이 있는가? 이 답에 아니오라면 확장성에 문제가 있다.
확장 가능한 정책들
책에서 소개하는 몇 가지 확장 가능한 정책들의 예시를 알아보자.
비욘세 규칙
“인프라 변경으로 서비스가 중단되는 문제가 발생했음에도, 만약 같은 문제가 CI 시스템의 자동 테스트에서 발견되지 않는다면 이는 인프라 팀의 책임이 아니다.”
비욘세 규칙의 의미는, 만약에 어느 팀에서 새로운 기술이나 변화를 수용했다면 이에 대한 테스트를 직접 해보라는 것이다. 즉 매번 문제가 생길 때마다 CI 팀이 모든 팀들을 찾아다니며 테스트를 확인하고 변화에 따른 영향을 파악할 필요가 없다.
전문성과 공유 포럼
엔지니어가 질문하였을 때 이에 답해줄 전문가가 있는 것은 매우 중요하다. 그리고 공유 포럼에서 엔지니어가 질문하고, 전문가가 이에 답해준다면, 이 영향은 단순히 두 명에 의해서만 그치지 않고 지식이 전파된다. 이 과정에서 더 나은 엔지니어들이 탄생한다.
100여 명의 Java 엔지니어가 있는 공유 포럼을 생각해 보자. 여기에 질문에 답해줄 전문가가 단 한 명만 있어도, 100여 명의 더 나은 Java 엔지니어가 탄생하는 것이다.
원점 회귀
개발 과정에서 문제는 빨리 발견할수록 고치는 비용이 낮아진다. 위 그림은 각 개발 단계와, 취약점이 발견되었을 때 드는 비용의 지수적 상관관계를 보여준다. 당연하겠지만, 프로덕션 레벨에서, 실제 사용자들에게 배포가 되었을 때 문제가 발생한다면, 이에 대한 비용은 급상승한다.
반면에 개발 초기 단계에서 문제를 발견할수록, 비용은 감소한다. 그렇기 때문에 엔지니어들의 최대 과제는 문제의 발견 시점을 최대한 왼편(초기 시점)으로 앞당기는 것이다.
구글에서는 이를 달성하기 위해 정적 분석, 코드 리뷰, 테스트 등의 다층 방어 전략을 구사한다고 설명한다.
트레이드오프와 비용
모든 결정은 트레이드오프와 비용을 수반한다. 구글에서는 어떤 결정을 내릴 때, '그냥 내가 시켰으니깐' 하는 식의 결정을 매우 싫어한다. 항상 결정을 내릴 때 그 트레이드오프와 비용을 고려한다고 한다.
이때 주의해야 할 것은, 이 비용은 돈과 같은 금융적 비용만 포함하는 것이 아니라, 다양한 지표들로 그 비용을 산출한다는 것이다. 구글에서는 다음 비용들을 주로 고려한다고 한다.
- 금융 비용 (예: 돈)
- 리소스 비용 (예: CPU 시간)
- 인적 비용 (예: 엔지니어링 노력)
- 거래 비용 (예: 조치를 취하는 비용)
- 기회 비용 (예: 조치를 취하지 않는 비용)
- 사회적 비용 (예: 선택이 사회 전체에 미치는 비용)
더불어서 위 비용을 추산할 때 현상 유지 편향, 손실 회피 등의 bias가 들어가는 것을 경계하면서, 열거한 모든 비용을 염두한다고 설명한다.
사실 이러한 트레이드오프와 비용의 평가는 데이터를 기반으로 한 평가이기도 하다. Data Driven Design이라고 하기도 하고, 더 나아가서는 데이터를 기반으로 하는 MLOps에 관련된 내용이기도 하다.
그러나 본인이 MLOps에 관련돼서는 잘 모르기 때문에 그렇게 심도 있게 알아보지는 않았고, 이 부분에 대해서는 책을 직접 읽어보거나 혹은 다른 분들의 글을 참고하는 것이 좋을 것 같다.
사례: 화이트보드 마커
많은 조직에서 화이트보드와 마커는 귀중하게 대접받는다. 이 때문에 마커는 항상 공급 부족에 시달린다. 어디에 가든, 마커의 절반은 말라 있거나 사용할 수 없다. 혹은 다른 팀에서 가져가는 바람에, 사용할 수 있는 마커가 없을지도 모른다.
회의 때 제대로 된 마커가 부족해서 방해된 적이 몇 번인가. 마커가 안 나와서 사고의 흐름이 끊겨본 적은 없는가. 마커가 아예 없는 경우는? 사실, 마커의 비용은 고작 천 원이다. 그러나 보통 엔지니어들이 산출하는 값어치는 이보다 훨씬 크다. 이 비용을 아끼려고 손해 보는 인적 비용이 더 큰 것이다.
구글에서는 이 엔지니어들의 가치를 얻기 위해서, 대부분의 사무실에 마커를 포함해 사무용품을 구비해 둔 사물함이 별도로 존재한다. 이곳에서 원하는 색의 마커를 원하는 만큼 잔뜩 가져와 쓸 수 있다.
이뿐만 아니라, 집중력에 방해받지 않도록 무선 헤드폰, 무선 키보드와 마우스 등을 가장 가까운 곳에서 찾을 수 있도록 배치한다. 이들 비용을 절약하는 비용보다 엔지니어링의 집중력에서 비롯되는 트레이드오프가 더 가치 있다고 판단하는 것이다.
개인적인 경험
위 화이트보드 마커와 관련되어 내 개인적인 경험이 있다. 바로 대학교의 일체형 책상에 관련된 이야기이다.
우리 학교에서는 일체형 책상을 사용하고 있고, 수년간 총학에서 이를 교체해 달라고 끊임없이 요구하고 있지만, 학교에서는 비용이 너무 많이 든다는 이유로, 그리고 일체형 책상의 수명이 아직 많이 남았다는 이유로 이를 거부하고 있다.
사실, 학교에서는 이 일체형 책상을 교체하는 비용을 절감하기 위해 학생들의 집중력이라는 가치를 희생하고 있는 것이다. 다시 말해 우리 학교는 학생들에게 거는 기대가 크지 않다는 말이고, 장차 이 학생들이 사회에 나가서 갖게 될 영향력에 대한 기댓값보다, 책상을 교체하는 데 드는 비용을 더 높게 평가하고 있다는 뜻이다.
사실, 대학교의 가치는 그 학생들이 시간이 지남에 따라 사회에서 갖게 되는 영향력과 지위에 크게 영향받는다. 서울대학교라는 이름이 가치 있는 이유 중 하나는 바로 서울대학생들이 사회에 나갔을 때 갖게 되는 영향력과 지위가 클 것이라 기대되기 때문이다.
이러한 점을 고려했을 때, 바로 이 학생들의 가치를 희생하고서 책상을 교체하는 일시적인 비용을 선택한 학교의 결정이 잘 이해가 안 되긴 한다. 어쩌면 우리 학교는 트레이드오프와 비용의 계산을 잘못하여, 단순히 금전적인 비용만을 고려하고 있는 건 아닌지 짚어봐야 한다.
사례: 분산 빌드 시스템
화이트마커의 예시뿐 아니라, 책에서는 분산 빌드 시스템에 관한 이야기도 소개하고 있다.
초기 구글에서는 분산 빌드 시스템이 없었다. 그렇기 때문에 모든 컴파일과 빌드 과정이 로컬 머신에서 진행되었다.
문제는 코드 베이스의 양이 커짐에 따라 이 빌드 시간 역시 늘어나면서, 생산성에 크게 차질을 미쳤다. (당시 구글 어스를 로컬 컴퓨터에서 빌드했다고 하니, 얼마나 오랜 시간이 걸렸을지 상상하기 힘들다)
엔지니어들은 이 빌드 시간을 줄이기 위해 점차 더 고사양의 로컬 머신을 요구하게 되었다. 이는 그러나 임시 방책에 불가했다. 빌드 시간은 조금 줄어들었지만, 대부분의 시간에서 이 고성능 로컬 머신은 놀고 있었고, 이마저도 코드베이스의 양이 늘어나게 되면 또다시 더 높은 사양의 로컬 머신이 요구되었기 때문이다. 이로 인한 비용 역시 만만치 않았다.
시간적, 금전적 비용 외에 엔지니어들이 이 시간 동안 일을 할 수가 없으므로, 이는 다시 말해 인건비의 손실까지 더해진다고 볼 수 있다.
구글에서는 이 까닭에 분산 빌드 시스템을 만들기로 결정했다고 한다. 분산 빌드 시스템을 만드는데 드는 엔지니어의 비용과 시간, 시스템을 구성하는 자원이 있었지만, 이 비용을 감수하고서라도 로컬 빌드 시스템에서 발생하는 비용이 더 크다고 판단했기 때문이다.
결과적으로 분산 빌드 시스템을 만들면서, 엔지니어들의 시간은 단축되었고, 빌드 시스템의 컴퓨팅 자원은 효율적으로 사용됐다. 로컬 빌드 시스템의 비용을 아끼기 위한 트레이드오프로써 분산 빌드 시스템을 구축하는 비용을 감수했지만, 이 결정은 성공적...이라고 하면 좋겠지만, 사실 예상치 못한 결과를 불러왔다.
로컬 빌드 시절에는 엔지니어들이 스스로, 빌드 시간을 최대한 줄이기 위한 노력을 다했다. 그러나 이제는 그 노력이 분산 빌드 시스템이라는 혜택 속에 가려지게 되고, 자원이 골고루 분배되기 시작하자, 엔지니어들은 이 자원을 남용하기 시작했다.
결과적으로 엔지니어들의 자원 남용으로 인해 분산 빌드 시스템은 점차 오버로드되기 시작하였고, 구글에서는 그제야 빌드 시스템의 최적화를 위한 몇 가지 지침들을 엔지니어들에게 강제하고 나서야 이 문제를 진압할 수 있었다.
결론적으로 엔지니어들의 자원 남용을 합쳐도, 분산 빌드 시스템이 주는 혜택이 더 많은 것은 사실이나, 이 이야기의 교훈은 어떤 결정을 내릴 때 예기치 못한 비용이 발생할 수 있음 역시 고려해야 한다는 것이다.
1장 후기
1장은 본격적인 내용이 시작되기 전, 전제를 하고 가는 대단원이다. 구글에서 왜 이러한 결정을 내렸는지, 구글의 철학과 생각은 무엇인지를 이 단원에서 미리 보여주고 있다. 더불어 프로그래밍과 SW 공학의 차이점에 대해서 깊게 생각하지 못했는데, 대규모 소프트웨어를 만드는 관점에서 장기적인 비전과 변화를 고려할 때 구글의 생각과 사고방식을 알 수 있었다.
개인적으로는 화이트마커의 이야기가 상당히 재밌고 공감이 된다.