개발자에게 컴퓨터 과학 기초가 중요한 이유
F-Lab : 상위 1% 개발자들의 멘토링
📌 글 작성
성장에 관심이 많은 F-Lab 백앤드 멘토 Elkein
스타트업 여럿과 NHN, 넷마블, 크래프톤 등을 거쳤으며
게임 산업, 클라우드 플랫폼 개발, 웹 개발 등을 두루 경험한 19년 차 엔지니어
개요
우리가 사용하는 프레임워크는 사용하기 쉬운 방향으로 발전해 왔다. 애초에 프레임워크를 쓰는 목적이 쉽게 원하는 기능을 구현할 수 있게 함에 있다.
하지만 결국 프레임워크의 원리를 모른다면 프레임워크가 가진 가정, 제약, 오류의 원인을 발생한다면? 그리고 그 오류가 구글링을 해도 잘 나오지 않는다면 어찌할 것인가?
현상만으로 원인을 찾아야 할 때 컴퓨터 과학 기초가 빛을 발한다.
몇가지 사례
간단한 사례부터 시작하자.
돈 다루기
정수를 다룰 땐 기본적으로 int를 많이 선택하게 된다.
돈도 그렇게 선택하면 어떻게 될까?
int의 범위: -2,147,483,648 ~ 2,147,483,647
int 값의 양수 최대치인 2,147,483,647를 넘기게 되면 오버플로우가 된다.
정수가 오버플로우가 되면 어떻게 될까?
어째서 값이 범위를 초과했는데 이런 값이 담길까?
변수형, 변수의 값의 범위가 로우 레벨에서 어떻게 동작하는지 아는 것이 중요하다. 변수의 언더플로우, 오버플로우는 일종의 안전장치다. 메모리 무결성을 지키기 위해서 앞이나 뒤의 메모리 영역을 덮어쓰는 동작 대신 언더 플로우, 오버 플로우라는 결과를 선택한 것이다.
메모리 무결성을 포기했다면 어땠을까?
변수의 값이 오버플로우나 언더플로우될 때마다 인접 메모리에 어떤 값이 있었냐에 따라서 다른 버그가 생겼을 것이다.
int64를 쓰면 괜찮은가?
아니 그렇지 않다. 값의 범위가 커지는 것일 뿐 오버플로우 이슈는 존재한다. 우선 오버플로우가 일어났는지 체크하는 것은 상대적으로 쉽다. 이 상황에서 어떻게 해야 될까?
- 값을 더 크게 담기 위해서 더더 큰 타입을 사용?
- 문자열을 이용해 범위가 더 큰 연산?
- unsigned 타입 이용으로 2배의 범위 확장?
- 오버플로우시에는 예외를 발생시키기?
이 모든 판단을 하기 위해선 오버플로우라는 상황이 발생할 수 있음을 인지해야 한다. 그리고 오버플로우로 인해 값이 어떻게 변하고 망가지는가를 알아야 가능하다.
미국의 돈 다루기. 센트 표기를 위해 float, double를 쓰겠다고?
한국의 원은 최소 단위가 1이다. 정수형을 쓰기 딱 좋다. 반면 미국은 센트가 존재한다. 50센트는 0.5로 표기하고 싶어진다. 정수 대신 실수를 써볼까? 실수를 쓰는 순간 부동 소수점 실수가 어떤 것인지 잘 몰랐다면 이로 인한 값 유실로 인해 오류를 만들게 될 것이다.
NaN
부동 소수점 실수가 담을 수 없는 값의 경우 Not a Number라는 뜻의 NaN이 담겨 있게 된다. 이러한 값을 왜 생길까?
난수는 값의 분포를 항상 보장하진 않는다.
임의의 값을 원할 경우 우리는 각 언어의 기본 Random 함수를 호출한다.
하지만 이 Random 함수는 우리가 원하는 임의의 값을 적절한 분포로 발생시키지 않는다.
0~9999의 값을 원할 경우, 결과 값에 나머지 연산 (%)으로 10000을 수행하는 데 여기서 나오는 결과 값이 0~9999까지의 값이 1번씩 발생함을 보장하지 않는다. 그렇기에 연속으로 9999, 9998이 나올 여지도 분명히 존재한다.
이러한 분포를 개선하기 위해서는 MersenneTwister (Apache Commons Math 3.6.1 API) 를 이용한 랜덤 기능을 써야 한다. (논문은 Mersenne twister: a 623-dimensionally equidistributed uniform pseudo-random number generator: ACM Transactions on Modeling and Computer Simulation: Vol 8, No 1 다음과 같다) 자세히 살펴보면 단점이 없는 것은 아닌데 당연하게도 더 정밀해지면 더 느려진다.
컴퓨터 과학의 기본은 트레이드 오프다. 모든 것이 좋아진 발전이 없는 것은 아닌데, 대부분은 트레이드 오프이기 때문에 장단점을 파악하고 단점은 수용 가능한 범주인지, 감내하고라도 이득을 얻어야 되는지를 이해하고 있는지가 중요하다.
Garbage Collector
GC를 이해하려면 JVM이란 레이어가 왜 나왔고 그 레이어가 메모리 이슈를 해소하기 위해 어떠한 방식으로 동작하는지 이해해야 한다.
ZGC 이전의 GC는 Stop The World가 아주 길었다. Stop The World가 왜 발생해야 되는지, GC가 편한 방법 일 뿐 비용 소모가 크다는 점도 알아야 한다. ZGC마저 이전보다 Stop The World가 줄어든 것 뿐 여전히 고성능 어플리케이션을 위해선 GC는 최소화하는 것이 좋다.
GC를 최소화하기 위해선 어떤 접근이 필요할까?
객체의 생명 주기 관리를 신경 쓰거나 객체 풀을 이용해 객체를 재사용하는 등의 접근이 있을 수있는데, 메모리 할당 해제를 줄이는 것이 곧 GC 발생을 최적화하는 것라고 볼 수 있다. 이는 GC 알고리즘 발전과 무관하게 성능과 지연 시간에 영향을 준다.
컨테이너 선정의 중요성 (feat. 시간 복잡도)
컨테이너를 고를 때 실제로 중요한 것은 실제 수행 시간이다. 실제 수행 시간을 유츄하기 위한 보조 정보로써, 시간 복잡도가 있는 것이지 시간 복잡도만 보고 컨테이너를 골라선 안된다. 컨테이너는 한 가지 시간 복잡도로 표현될 수 없다. 평균, 최악, 최선으로 시간 복잡도는 나뉘어서 표기되는데, 그 이유는 데이터 분포나 정렬 상태, 혹은 데이터가 저장된 위치나 저장 방식에 따라 달라질 수 있기 때문이다. 데이터가 100여건 이내를 담을 컨테이너라면 컨테이너는 어떤 것을 써도 무방하다.
하지만 100만 건 이상의 데이터를 관리하게 된다면 반드시 성능을 고려해야 하고 이 고려 과정에서 참고해야 될 정보가 시간 복잡도다. 시간 복잡도는 빅오 표기법을 통해서 원소 수 대비 어떠한 시간 소요가 되는지만 설명할 뿐 숨어있는 코스트나 비용에 대해서까지 표현되지 않는다. 하나의 원소에 대한 처리 시간이 크다면 빅오 표기법적으로 훌륭한 알고리즘이라고 해도 실제로는 더 느릴 수 있는 것이다.
그래서 빅오 표기법은 참고하되 실제 소요되는 자원이나 동작 방식이나 제약 혹은 최악으로 가는 조건 등을 파악해야 되는데 이것을 파악하는 데에 있어 자료구조나 알고리즘에 대한 높은 이해가 반드시 필요하다.
요약하자면 누가 봐도 좋지 않은 시간 복잡도를 가진 컨테이너를 배제할 수 있는 것 상황에 따라 컨테이너를 조합해 쓰거나 메모리를 더 써서 성능을 높이거나 적용할 수 있는 것, 평균, 최악, 원소수에 따른 컨테이너를 검토, 대체, 적용할 수 있는 것 모두 컴퓨터 과학 이해가 높아야 가능하다.
리눅스 (=OS) 이해도
리눅스 이해도가 높으면 뭐가 좋은가?
리눅스는 어플리케이션이 간결하게 작성되는 것을 지향한다. 그리고 그렇게 만들어진 도구는 내가 작성한 어플리케이션의 오류를 빨리 찾을 수 있게 도와준다. 이제 Docker를 쓰니까 쿠버네티스 쓰니까 리눅스 몰라도 되는 거 아니냐고?
아쉽게도 그렇지는 않다. Docker는 리눅스 커널 기능을 이용해 가상화를 한 것이다. Docker 인스턴스에 bash로 접속해서 다양한 명령어를 날리다 보면 내부적인 구성이 어떻게 되어있는지 알 수 있을 것이다. 또한 Docker의 환경적인 불편함으로 급할 때는 VM이나 온 프라미스 머신에 배포해서 이슈를 확인해야 될 수도 있다. 이러한 경우에도 당연히 리눅스 이해도는 중요해진다.
어떠한 관점에서 운영체제 이해도가 도움이 될까?
- 시스템 장애 분석
- 시스템 장애가 발생했을 때 리눅스나 다른 운영체제에 대한 이해는 장애의 원인을 신속하게 파악하는 데 도움이 된다.
- 예를 들어 시스템 로그 분석을 통해 어떤 프로세스나 서비스가 문제를 일으키고 있는지 파악할 수 있다.
- 또한 시스템 리소스 사용량이나 네트워크 트래픽 등의 지표를 모니터링하여 장애의 원인을 찾을 수 있다.
- 성능 최적화
- 운영체제 이해도는 시스템 성능 최적화에 도움을 준다.
- 예를 들어 프로세스 스케줄링 알고리즘을 이해하고 적절한 설정을 통해 CPU 사용량을 최적화할 수 있다.
- 또한 메모리 관리, 파일 시스템 설정, 네트워크 스택 등의 요소를 최적화하여 전반적인 시스템 성능을 향상시킬 수 있다.
- 보안 강화
- 운영체제 이해도는 시스템 보안 강화에 중요합니다. 운영체제는 다양한 보안 기능을 제공하며 악용되거나 취약점이 있는 경우 보안 위험에 노출될 수 있다.
- 운영체제 이해도를 바탕으로 시스템을 분석하고 보안 패치를 적용하고 보안 정책을 설정하여 시스템을 안전하게 운영할 수 있다.
- 자원 사용 분석
- 운영체제 이해도를 통해 시스템의 자원 사용량을 분석할 수 있습니다. CPU, 메모리, 디스크, 네트워크 등의 자원 사용량을 모니터링하고 분석하여 병목 현상이나 자원 과다 사용 등을 파악할 수 있다.
- 이를 통해 시스템 자원의 효율적인 사용을 도모하고 성능 저하나 시스템 장애를 예방할 수 있다.
이외에도 이해도가 필요한 것들
힙/스택 메모리의 차이
힙이 왜 느리고 왜 스택이 빠른지 이해하는 것이 좋다. 힙이 GC와 어떤 영향을 주고받는지 이해해야 어떤 식으로 메모리 관리 전략을 써야 하는지 알 수 있고 왜 느려졌는지 알 수 있다.
프로세스와 스레드
프로세스와 스레드가 어떻게 다른지 이해하는 것이 중요하다. 스레드가 마냥 좋은 것이 아니라는 것이 중요한데 결국 처리량이 핵심인데 처리량을 위해선 어떠한 개념이 좋은지 이해해야 한다.
각 함수가 소모할 자원
커널 레벨 명령인지, 유저 레벨 명령인지, 단순히 작성된 로직을 수행하는 것인지, CPU를 더 많이 쓰는지, 메모리를 얼마나 쓰는지 알아야 한다.
컴퓨터 구조의 이해
최근의 트렌드는 Docker와 같은 컨테이너나 VM 기반의 환경이지만 그럼에도 컴퓨터 구조는 중요하다. 리눅스가 여전히 중요하듯 컴퓨터 구조도 중요한데 실제 리눅스 머신 위에서 동작하면서 할당받은 자원을 어떻게 사용하고 있는지를 파악해야 하는데, 자원 소모 지표를 보고도 어떤 상황인지 판단이 불가능하다면?
지나치게 느린 지연시간이 발생하는 API가 있을 때 이 원인에 대한 판단과 개선이 불가능하다면?
캐시가 왜 빠른지, CPU에는 왜 캐시가 따로 있는지, 이 캐시가 어떻게 하는지, 그리고 어플리케이션이 가동될 때 디스크 → 램을 거쳐서 CPU가 어떤 역할을 담당하는지 알아야 가동 속도를 단축시킬 수 있다.
- 성능 분석
- 컴퓨터 구조의 이해를 통해 어플리케이션의 성능 이슈를 파악할 수 있다.
- 예를 들어 CPU, 메모리, 디스크 등 각 구성 요소의 성능 한계를 이해하면 병목 현상이 발생하는 지점을 파악하고 개선 방안을 모색할 수 있다.
- 최적화
- 컴퓨터 구조를 이해하면 어플리케이션을 최적화하는 데 도움이 된다.
- 예를 들어 캐시 메모리 구조와 액세스 패턴을 고려하여 데이터 로드/저장을 최적화할 수 있고 파이프라인 구성과 병렬 처리를 이용하여 작업 흐름을 최적화할 수 있다.
- 디버깅
- 어플리케이션에서 발생하는 문제의 원인을 찾기 위해 컴퓨터 구조의 이해가 필요할 수 있다.
- 예를 들어 어떤 상황에서 데이터 손실이 발생하는지, 메모리 오류가 있는지, 스레드 간 동기화 문제가 있는지 등을 파악할 때 컴퓨터 구조에 대한 이해가 도움이 된다.
- 플랫폼 종속성 이해
- 컴퓨터 구조에 대한 이해는 어플리케이션이 동작하는 플랫폼(예: x86, ARM)의 특징과 제약 사항을 이해하는 데 도움이 된다.
- 이는 특정 플랫폼에서 발생하는 성능 차이나 호환성 문제를 파악하고 대응하는 데에 도움이 된다.
컴퓨터 과학 기초의 중요성
아직까지 모든 기능은 운영체제 레이어 (혹은 운영체제를 에뮬레이션 한 환경)에서 동작하기에 운영체제에 대한 이해는 곧 어플리케이션이 어떻게 동작하는지에 대한 이해로 귀결된다.
메모리 할당 해제가 왜 느린지, 블러킹이 왜 문제인지, 블러킹 우회로 스레드를 무작정 많이 늘리면 왜 더 느려질 수도 있는지 등에 대해서 이해하기 위해선 동작 원리가 중요하다. 그 원리를 이해할 때 컴퓨터 과학 기초에 대한 이해가 동반되어야 한다.
하지만 기초라는 게 하면 좋다지만 막연하기에 공부하기 어려운데 그렇기에 리눅스 위에서 다양한 작업을 하다 보면 그 필요성과 이해도가 올라오는 데에 도움을 준다. 요즘에는 Docker, Kubernetis 등을 이용해서 배포와 가동이 쉬워져 리눅스를 가지고 놀 일이 줄어든 측면도 있다. 그렇다 보니 직접적인 OS 이슈를 마주하며 겪는 이슈, 그 이슈를 해결하면서 겪는 과정에서의 인사이트나 이해도가 쌓이지 않는 측면이 있는 것도 사실이다.
모든 이론은 실습과 함께 할 때 그 이해도가 높아진다. 그래서 각 이론들을 최대한 많이 동작 시켜보고 변경해 보고 가지고 놀 때 더 큰 성장을 한다고 생각한다. 그러한 실습 과정에서 원리 해져서 컴퓨터 과학 기초에 관심을 갖게 되는 것도 좋은 접근이라고 생각한다. 결국 핵심은 원리를 파악하는 것이고 원리를 파악하기 위해 컴퓨터 과학 이해도가 필요한 것이라는 점을 잊지 않았으면 좋겠다.
사수가 없어 성장하기 힘드신가요?
F-Lab에서 빅테크 기업 타이틀과 실력을 겸비한 멘토님들께 실력 향상을 위한 멘토링을 받을 수 있습니다.
개발 경험이 있는 취준생이거나 7년 이하 경력 개발자라면 충분히 멘토링을 받아 뛰어난 개발자로 성장하실 수 있습니다.
이 컨텐츠는 F-Lab의 고유 자산으로 상업적인 목적의 복사 및 배포를 금합니다.