
본인은 첫 소프트웨어 엔지니어 업무를 DevOps로 시작하였다. 좀 더 구체적으로 말하자면.. Platform Engineering 팀에서 내부 개발자 플랫폼 개발, 배포 프로세스 체계화, SRE 등 다양한 업무를 했는데, 그 과정에서 모니터링, observability 확보 그리고 시스템 상태를 참고하여 트러블슈팅들을 도와주는 업무를 많이 하였다.
시스템 상태를 본다고하면 CPU, Memory 같은 메트릭들이 대표적으로 떠오르는데, 단순히 이게 높다 낮다 뿐 아니라 왜 이게 애플리케이션 성능에 영향을 주는지 이해를 해야한다. 업무하면서 만났던 많은 개발자 (서비스 개발에 집중하는 엔지니어)들은 생각보다 여러 기능 출시와 제품 개선에 바빠 이런 수치들의 의미에 대해서 깊게 공부할 여력이 없었던 것 같다.
따라서 이번 기회에 이를 정리해보고자 한다. LLM 발전에 따라 개발자가 코드에 사용하는 시간이 줄어듦에 따라 이런 수치정보를 이해하는 능력이 중요해지고있다. 따라서 이 글이 모든 소프트웨어 엔지니어들의 발전에 도움이 되면 좋겠으면 하는 .. 바람이 있다. (바이브코딩 포함!)
시스템 상태를 위해 수집해야하는 메트릭은 아주 크게 분류하면 2가지이다.
첫번째는 인프라 수준의 메트릭이고 두번째는 애플리케이션 수준 메트릭이다. 두 메트릭의 목적 자체는 애플리케이션이 원하는 기능을 수행하는 정상적인 상태인지를 파악하는데 도움을 준다.
이 때 메트릭들은 측정이 가능해야한다! 수치로서 측정이가능해야 엔지니어들끼리 소통하는데 논리적인 판단을 할 수 있기 때문이다. 이번 시간에는 먼저 Infra metrics에 대해 살펴보자
프로세스는 os로부터 하드웨어 자원을 할당받아 연산을 처리하고, 보통은 연산 처리량에 따라 사용률이 결정된다. 인프라 메트릭은 이 하드웨어 자원에 대한 정보이고 커널이 측정하고 관리한다. 정말 많은 종류들이 있지만 그래도 서비스가 건강한지 판단하는데는 주로 4가지 지표를 보면 된다.
Request 처리에는 CPU가 사용되고 CPU 사용률이 100%가 되면 timeout이 발생한다.
CPU는 모든 연산을 중앙 처리하는 부품이다. 보통 컴퓨터의 두뇌라고 퉁쳐서 표현하고 넘어가는데 좀 더 본질적인 이해를 하기위해서 트랜지스터를 추가로 설명해보고자한다.

소스코드를 작성하면 컴파일되서 0과 1의 기계어 명령이 되고, 이 명령들은 실행 파일로 저장되며, 실행 시에는 메모리에 0과 1의 데이터와 명령 형태로 저장된다. CPU는 이 메모리에 있는 0과 1을 가져와서 연산을 처리하는데, 이 연산 자체가 트랜지스터의 스위칭을 통해 이루어진다.
컴퓨터는 디지털 신호만 인식할 수 있기 때문에 인간은 데이터를 이진수로 변환하여 전송해야한다. 그래서 트랜지스터라는 전자부품을 통해 0과 1의 이진수 데이터를 만든다. 0은 전류가 흐르지 않는 상태, 그리고 1은 전류가 흐르는 상태로 아주 짧은 시간 동안 전류가 흘렀다가 안흘렀다가를 반복하면서 이진수 데이터를 만들고 있다.
HTTP 요청 같은 작업도 근본적으로는 수많은 단순한 이진 연산의 집합으로 분해된다. 예를 들어, 웹 서버 주소를 처리하거나, 네트워크 프로토콜(TCP/IP)에 따라 데이터를 패킷화하는 과정같은 모든 과정은 결국 덧셈, 비교, 데이터 이동과 같은 기본 연산으로 구성된다. 이러한 모든 기본 연산 하나하나가 CPU 내의 트랜지스터로 인해 동작한다.
이러한 CPU 사용량 통계는 커널이 하드웨어 인터럽트와 타이머를 통해 CPU의 상태를 주기적으로 샘플링하고, 이를 가상 파일 시스템에 기록하여 수집한다. OS에 미리 프로그래밍된 로직에 의해 이루어지며, 하드웨어의 트랜지스터 스위칭 데이터를 기반으로 통계를 만들고 있다.
그러니까 요약하면! CPU Usage는 CPU 내의 수십억 개 트랜지스터가 1초에 몇 번, 그리고 얼마나 활발하게 0과 1을 스위칭하고 연산했는지를 백분율(%)로 나타낸 수치이다.
CPU 사용률이 100%가 되면 HTTP 요청이 타임아웃이 발생하는 이유는 정해진 시간 내에 트랜지스터가 처리할 수 있는 스위칭 연산량이 넘었기 때문이다.
이로 인해 요청이 대기열에 쌓이거나 처리 지연이 발생하고, 설정된 응답 시간 내에 처리가 완료되지 않으면 클라이언트는 타임아웃 오류를 받는다. 물론 고객은 타임아웃이 떨어질 때 까지 기다려주지않을 가능성이 높다... ㅎㅎ
Out Of Memory가 보고되면 Kernel은 즉시 프로세스를 종료시킨다.

메모리가 커널에게 Out Of Memory를 보고하면 커널은 즉시 프로세스를 종료시키게 된다. 여러 서비스를 운영한다면 하나의 프로세스가 종료되면 일시적으로 다른 프로세스에 더 많은 부하가 가게 되며, 그로 인해 전체 시스템이 셧다운 될 위험이 있다.
CPU에 대해 알아보면서 트랜지스터에 대해 살펴봤듯이.. 메모리에는 커패시터를 이해해야한다. CPU가 연산을 처리하기위해서 데이터를 메모리에서 가져오는데 메모리는 **전기적으로 데이터를 임시 저장하는 수많은 커패시터(Capacitor)로 구성되어있다.
커패시터는 전하를 저장할 수 있는 작은 기계 장치로, 전하가 충전되어 있으면 1(ON), 방전되어 있으면 0(OFF)을 나타낸다. 트랜지스터는 이 커패시터에 접근하여 데이터를 읽거나 쓸 수 있도록 제어하는 스위치 역할을 한다.
소스코드를 실행하면 그 프로그램의 변수, 함수, 객체, 실행 중인 명령어들이 모두 0과 1의 형태로 메모리에 저장된다. 예를 들어, int count = 100;이라는 코드를 실행하면, 숫자 100은 이진수 01100100으로 변환되어 메모리의 특정 주소에 있는 8개의 셀에 각각 0 또는 1로 저장된다.
웹 애플리케이션을 예로 들어보자. 사용자가 로그인하면 세션 정보, 사용자 권한, 최근 조회한 데이터 등이 메모리에 저장된다. API 요청이 들어오면 데이터베이스에서 가져온 JSON 데이터가 메모리에 로드되고, 이를 가공하는 과정에서 생성되는 중간 객체들도 모두 메모리를 차지한다. 심지어 프로그램 코드 자체도 실행을 위해 메모리에 적재되어 있어야 한다.
Memory Usage는 현재 실행 중인 프로세스들이 얼마나 많은 데이터를 메모리 셀(커패시터)에 0과 1로 저장하고 있는지를 나타낸 수치이다. 일반적으로 바이트(Byte) 단위 또는 전체 메모리 대비 백분율(%)로 표현된다.
메모리가 커널에게 Out Of Memory를 보고하면 커널은 즉시 프로세스를 종료시킨다. 물리적 메모리와 스왑 공간마저 바닥나 더 이상 데이터를 저장할 커패시터 공간을 확보할 수 없게 되면, 커널은 시스템 전체의 마비를 막기 위해 OOM(Out Of Memory) 킬러를 가동한다. 이는 가용 자원을 확보하기 위해 특정 프로세스를 강제로 희생시키는 최후의 수단이며, 시스템 전체가 응답 불능 상태에 빠지는 것을 방지하기 위한 커널의 핵심적인 자기방어 기제이다.
애플리케이션은 종종 자동으로 종료되는데 그 이유는 이유는 운영 체제가 더 이상 프로세스에 필요한 메모리를 할당할 수 없거나, 프로세스가 유효하지 않은 메모리 주소에 접근하려고 시도하기 때문이다.
Out Of Memory(OOM) 현상의 경우, 메모리가 모두 소진되면 커널의 OOM 킬러가 실행되어 메모리를 많이 사용하는 프로세스를 강제로 종료시켜 시스템 안정성을 유지한다. 세그먼테이션 폴트는 프로세스가 허용되지 않은 메모리 영역(예: 다른 프로세스의 메모리나 존재하지 않는 주소)에 접근하려 할 때 발생하며, 커널은 이를 감지해 해당 프로세스를 즉시 종료시킨다. 이 두 경우 모두 프로세스가 정상적으로 실행을 계속할 수 없는 상태에 도달했기 때문에, 운영 체제는 애플리케이션을 크래시시켜 추가적인 시스템 손상을 방지한다.
이렇게 보면 커널 개발자는 정말 예술의 영역이라고 볼 수 있다..

** 어느 개발자가 캡처한 OOM 로그
호스트의 네트워크 대역폭을 초과하면 패킷 손실이나 지연 시간 급증이 발생한다.
CPU나 메모리와 달리, 네트워크 대역폭(Network Bandwidth)은 DevOps나 인프라 직군이 아닌 개발자에게 다소 생소할 수 있다. 컴퓨터는 네트워크 통신을 위해 NIC(Network Interface Card)를 사용하며, 이는 전기 신호, 광 신호, 또는 전파를 통해 0과 1의 비트 스트림을 전송하는 물리적 매체를 관리한다.

아래 사진은 NIC의 실제 사진
애플리케이션에서 데이터를 전송할 때, 데이터는 HTTP, TCP, IP, Ethernet 같은 프로토콜 계층을 거쳐 패킷 단위로 분할된다.
각 패킷은 헤더(목적지 주소, 순서 번호 등)와 페이로드(실제 데이터)로 구성되며, 모두 0과 1의 비트로 표현된다. 예를 들어, 웹 브라우저가 "GET /index.html HTTP/1.1" 요청을 보내면, 이 텍스트는 ASCII 또는 UTF-8로 인코딩되어 비트 스트림으로 변환된다.
예를 들어 서버가 응답으로 1MB 이미지를 보낸다고 하면 약 8,388,608비트(1MB = 1,048,576 Bytes × 8 bits)로 나뉘어 수천 개의 패킷으로 전송된다. NIC는 이 비트들을 초당 수백만에서 수십억 번 송수신하는 역할을 하는 하드웨어이다.
Network Bandwidth는 NIC가 1초 동안 송수신할 수 있는 비트(0과 1)의 양을 나타내는 수치로, bps(bits per second), Mbps(Megabits per second), 또는 Gbps(Gigabits per second) 단위로 표현된다.
따라서 네트워크 대역폭 메트릭은 인프라의 데이터 전송 능력이 애플리케이션의 네트워크 요구량을 충족하는지 판단하는 데 필수적이며, 대역폭 부족은 CPU나 메모리가 충분하더라도 병목 현상을 유발해 전체 시스템 성능을 저하시킨다.
디스크 사용량(Disk Usage)은 저장 장치에 데이터가 얼마나 저장되어 있는지를 나타내고, 디스크 I/O는 디스크에서 데이터를 읽거나 쓰는 작업의 빈도와 속도를 나타낸다.
디스크는 데이터를 영구적으로 저장하는 하드웨어이다. 여기서 말하는 데이터는 로그 등 애플리케이션이 생성하거나 사용하는 모든 데이터를 포함한다.
예를 들어서 웹 서버가 사용자 업로드 파일이나 로그를 저장하면 디스크 사용량이 증가한다. 디스크 사용량이 100%에 가까워지면 새로운 데이터를 저장할 공간이 부족해지고, 이는 쓰기 작업 실패나 애플리케이션 오류로 이어질 수 있다.

디스크 I/O(Input/Output)는 디스크와 CPU 또는 메모리 간의 데이터 전송 활동을 의미한다. 디스크 I/O는 읽기와 쓰기 작업으로 나뉘며, 초당 전송 바이트 또는 대기 시간으로 측정된다.
예를 들어, 데이터베이스 쿼리가 실행될 때 디스크에서 데이터를 읽어 메모리로 로드하거나, 처리된 결과를 디스크에 저장하는 과정이 디스크 I/O에 해당한다.
디스크 사용량과 I/O 메트릭은 시스템의 저장 성능과 안정성을 평가하는 데 핵심적이다.
운영 체제는 디스크의 상태를 모니터링하여 사용량과 I/O 통계를 수집하며, 이는 /proc/diskstats 같은 파일을 통해 확인할 수 있다. 높은 디스크 사용량은 저장 공간 부족으로 인한 오류를, 높은 I/O 부하는 느린 응답 시간을 유발할 수 있으므로, 이를 모니터링하여 애플리케이션의 요구사항을 충족할 수 있는 디스크 용량과 성능을 확보하는 것이 중요하다. 만약 디스크 사용량이 100%에 도달하거나 I/O가 과부하되면, 애플리케이션은 데이터 읽기/쓰기 실패로 인해 예기치 않게 종료되거나 응답하지 않을 수 있다.
결론적으로 애플리케이션이 정상 동작하는지 살펴보려면 애플리케이션이 실행되고있는 호스트에서 4가지 지표를 기본으로 살펴봐야한다.
그리고 어떤 수치가 정상이냐에 대한 절대적인 수치는 없고, 운영을 하면서 정상 수치를 합의하고 그것을 지켜나가는 연습이 필요하다!
다음 글에서는 인프라 메트릭이 아닌, Application 메트릭을 수집하고 해석하는 방법에 대해서 공유해보고자한다
잘 보고 갑니다~!