Skip to main content
Summary

CS는 개별 지식이 아니라 연결하는 힘이다

CS를 공부하다 보면 비트, CPU, 자료구조, 스레드, 네트워크, 데이터베이스가 서로 다른 과목처럼 보인다. 하지만 실무에서 문제는 과목별로 나뉘어 나타나지 않는다.

사용자는 "페이지가 느리다"고 말한다. 운영자는 "서버 CPU는 괜찮은데 응답 시간이 튄다"고 말한다. 로그에는 간헐적인 타임아웃만 남는다. 이때 필요한 것은 특정 개념 하나를 외우는 능력이 아니라, 요청이 지나가는 여러 계층을 따라가며 비용과 실패 지점을 좁히는 능력이다.

개발자가 CS를 실무 설계에 연결한다는 것은 다음 질문을 계속 던지는 일이다.

  • 이 데이터는 어디에 저장되고, 어떤 형태로 표현되는가?

  • 이 코드는 CPU를 쓰는가, 메모리를 기다리는가, 네트워크나 디스크를 기다리는가?

  • 요청이 많아졌을 때 어느 자원이 먼저 부족해지는가?

  • 실패가 발생하면 중복 실행, 데이터 불일치, 장애 전파가 생기지 않는가?

  • 지금 선택한 기술은 성능뿐 아니라 운영 비용과 복잡도까지 감당할 수 있는가?

이 장은 앞에서 다룬 지식을 하나의 실무 사고 흐름으로 묶는다.

CS 지식을 실무 설계로 연결하기AI 생성 이미지

하나의 웹 요청을 끝까지 따라가 보기

상품 상세 페이지를 여는 간단한 요청을 생각해 보자. 사용자는 브라우저 주소창에 URL을 입력하거나 앱에서 상품을 누른다. 겉으로는 버튼 클릭 하나지만, 내부에서는 여러 계층이 차례로 움직인다.

먼저 클라이언트는 도메인을 IP 주소로 바꾸기 위해 DNS를 조회한다. 그다음 서버와 TCP 연결을 맺고, HTTPS라면 TLS 핸드셰이크로 암호화 통신을 준비한다. 여기까지가 네트워크 계층에서 발생하는 비용이다. DNS가 느리거나, TCP 연결이 너무 자주 생성되거나, TLS 설정이 잘못되어도 애플리케이션 코드가 실행되기 전에 요청은 이미 느려질 수 있다.

연결이 준비되면 HTTP 요청이 전송된다. 요청에는 메서드, 경로, 헤더, 쿠키나 토큰, 바디가 포함된다. 로드 밸런서는 여러 서버 중 정상 상태인 인스턴스를 골라 요청을 전달한다. 이때 애플리케이션이 무상태로 설계되어 있으면 어떤 서버가 요청을 받아도 처리할 수 있다. 반대로 세션이 특정 서버 메모리에만 있으면 서버 증설과 장애 대응이 어려워진다.

서버에 도착한 요청은 운영체제의 소켓을 통해 애플리케이션 프로세스로 들어온다. 프로세스 안에서는 스레드, 이벤트 루프, 코루틴 같은 실행 단위가 요청을 처리한다. 요청이 많아지면 CPU만 문제가 되는 것이 아니다. 파일 디스크립터, 스레드 수, 커넥션 풀, 컨텍스트 스위칭, 메모리 할당, GC 정지 시간도 함께 영향을 준다.

애플리케이션은 인증 토큰을 검증하고, 사용자가 해당 상품을 볼 권한이 있는지 인가를 확인한다. 이후 캐시를 조회하거나 데이터베이스에 쿼리를 보낸다. 캐시에 값이 있으면 빠르게 응답할 수 있지만, 값이 오래되었을 가능성을 고려해야 한다. 데이터베이스로 가면 인덱스, 실행 계획, 락, 트랜잭션 격리 수준, 복제 지연이 성능과 정확성을 결정한다.

마지막으로 서버는 결과를 JSON 같은 형식으로 직렬화하고, 압축하거나 캐시 헤더를 붙여 응답한다. 응답은 다시 네트워크를 지나 클라이언트로 돌아가고, 브라우저는 이를 파싱해 화면을 갱신한다.

이 흐름을 알면 장애 분석 방식이 달라진다. "API가 느리다"는 말은 너무 넓다. DNS가 느린지, 연결 수립이 느린지, 서버 큐에서 기다리는지, DB 락을 기다리는지, 외부 API가 느린지, 응답 크기가 큰지 나누어 봐야 한다. CS 지식은 이 구간을 나누고 원인을 좁히게 해 주는 지도 역할을 한다.

증상별로 계층을 좁히는 사고 과정

실무 장애는 보통 명확한 원인 하나로 시작하지 않는다. 먼저 증상을 수치로 정의하고, 어느 계층에서 대기하거나 실패하는지 좁혀야 한다.

대용량 트래픽이 들어올 때

트래픽이 늘면 가장 먼저 확인할 것은 처리량, 지연 시간, 오류율이다. 평균 응답 시간이 괜찮아도 p95나 p99가 나쁘면 일부 사용자는 이미 느린 서비스를 경험하고 있다.

그다음 병목을 나눈다. 애플리케이션 CPU가 가득 찼다면 알고리즘, 직렬화 비용, 암호화 비용, 불필요한 반복 계산을 본다. 메모리가 증가한다면 객체 생성량, 캐시 크기, GC 로그를 본다. DB 커넥션 풀이 가득 찼다면 쿼리 시간, 트랜잭션 길이, 커넥션 반환 누락을 확인한다. 외부 API 호출이 많다면 타임아웃, 재시도, 서킷 브레이커, 비동기 큐를 검토한다.

서버를 늘리는 수평 확장은 강력하지만 만능은 아니다. 애플리케이션 서버를 10대로 늘려도 데이터베이스가 단일 병목이면 전체 처리량은 크게 늘지 않는다. 반대로 캐시, 읽기 복제본, 큐, 배치 처리를 적절히 섞으면 요청 경로의 압력을 분산할 수 있다.

느린 쿼리가 발생할 때

느린 쿼리는 "인덱스를 추가하자"로 바로 끝내면 안 된다. 먼저 실행 계획을 확인해야 한다. 전체 테이블 스캔인지, 인덱스를 타지만 선택도가 낮은지, 정렬이나 조인에서 큰 임시 작업이 생기는지 봐야 한다.

애플리케이션 코드도 함께 봐야 한다. 한 번의 화면을 만들기 위해 반복문 안에서 쿼리를 계속 호출하는 N+1 문제가 있을 수 있다. 트랜잭션이 너무 오래 열려 락을 잡고 있을 수도 있다. 페이지네이션이 OFFSET 기반이라 뒤쪽 페이지로 갈수록 비용이 커질 수도 있다.

즉, 데이터베이스 성능은 SQL만의 문제가 아니다. 자료구조, 알고리즘, 트랜잭션, 네트워크 왕복, 데이터 모델이 함께 만든 결과다.

메모리가 계속 증가할 때

메모리 증가는 원인을 잘못 짚기 쉽다. CPU가 낮아도 메모리 때문에 서비스는 죽을 수 있다. 컨테이너 환경에서는 프로세스가 제한 메모리를 넘으면 운영체제나 오케스트레이터에 의해 종료될 수 있다.

먼저 힙, 스택, 네이티브 메모리, 페이지 캐시, RSS 같은 지표를 구분한다. 객체가 계속 쌓인다면 힙 덤프나 프로파일러로 어떤 타입이 많이 남는지 확인한다. 캐시가 무제한으로 커지는지, 요청별 데이터를 전역 컬렉션에 넣고 제거하지 않는지, 이벤트 리스너나 콜백이 참조를 붙잡고 있는지 살핀다.

GC 언어라고 해서 메모리 문제가 사라지는 것은 아니다. GC는 더 이상 도달할 수 없는 객체만 회수한다. 여전히 참조되는 불필요한 객체는 개발자가 설계를 바꿔야 한다.

간헐적 장애가 발생할 때

간헐적 장애는 가장 어렵다. 재현이 잘 안 되고, 로그도 부족한 경우가 많다. 이때는 동시성, 네트워크, 분산 시스템을 의심해야 한다.

경쟁 상태는 실행 순서에 따라 결과가 달라진다. 평소에는 정상처럼 보이다가 요청이 몰릴 때만 재고가 음수가 되거나 중복 결제가 생길 수 있다. 네트워크는 항상 성공하지 않는다. 패킷 손실, DNS 지연, TLS 실패, 외부 API 지연, 일시적인 연결 끊김이 발생할 수 있다. 분산 환경에서는 복제 지연 때문에 방금 쓴 데이터를 바로 읽지 못할 수도 있다.

이런 문제를 줄이려면 요청마다 correlation ID를 붙이고, 로그·메트릭·트레이싱을 함께 남겨야 한다. 타임아웃과 재시도는 반드시 멱등성과 함께 설계해야 한다. 같은 요청이 두 번 실행되어도 결과가 깨지지 않아야 하기 때문이다.

기술 선택은 장점이 아니라 대가까지 비교하는 일이다

실무 설계에서 기술 선택은 "무엇이 더 빠른가"만으로 결정되지 않는다. 빠른 기술이 더 복잡하고, 안정적인 기술이 더 비싸며, 단순한 기술이 확장에 불리할 수 있다.

좋은 의사결정은 다음 순서로 진행된다.

  1. 문제를 수치로 정의한다.
    초당 몇 요청을 처리해야 하는가? p95 목표는 얼마인가? 데이터는 얼마나 쌓이는가? 장애 시 몇 분 안에 복구해야 하는가?

  2. 접근 패턴을 확인한다.
    읽기가 많은가, 쓰기가 많은가? 단건 조회가 많은가, 범위 검색이 많은가? 강한 일관성이 필요한가, 조금 늦게 반영되어도 되는가?

  3. 실패 방식을 상상한다.
    캐시가 죽으면 어떻게 되는가? 메시지가 중복 전달되면 어떻게 되는가? DB 복제본이 늦으면 사용자는 어떤 값을 보는가?

  4. 운영 비용을 계산한다.
    모니터링, 백업, 마이그레이션, 보안 패치, 장애 대응, 온콜 부담까지 포함해야 한다.

  5. 작게 검증한다.
    추측으로 결정하지 말고 부하 테스트, 프로파일링, 실행 계획, 장애 주입으로 가설을 확인한다.

대표적인 선택의 대가는 다음과 같다.

선택얻는 것함께 생기는 대가
캐시 도입빠른 응답, DB 부하 감소캐시 무효화, 오래된 데이터, 장애 시 우회 경로
비동기 큐사용자 대기 시간 감소, 피크 완화최종 일관성, 중복 처리, 재시도 설계
인덱스 추가조회 성능 개선쓰기 비용 증가, 저장 공간 증가, 잘못된 인덱스 관리
마이크로서비스배포와 조직 분리네트워크 실패, 분산 트랜잭션, 관측성 복잡도
스레드 수 증가동시 처리 가능성 증가컨텍스트 스위칭, 메모리 사용, 락 경쟁
NoSQL 사용유연한 모델, 특정 패턴의 확장성제품별 쿼리 방식 제약, 조인·트랜잭션 지원 차이

기술은 문제를 없애는 것이 아니라 문제의 위치를 바꾼다. 동기 처리를 큐로 바꾸면 사용자의 대기 시간은 줄지만, 메시지 유실·중복·순서 문제가 생긴다. 캐시를 넣으면 DB 부하는 줄지만, 데이터 신선도 문제가 생긴다. 분산 시스템은 확장성을 주지만, 부분 실패와 운영 복잡도를 가져온다.

따라서 설계 문서에는 "이 기술을 쓴다"보다 "이 기술을 선택해 어떤 비용을 줄이고, 어떤 위험을 받아들이며, 그 위험을 어떻게 감시하고 복구할 것인가"가 들어가야 한다.

계속 성장하기 위한 실습 주제

CS 지식은 읽기만 하면 금방 흐려진다. 작은 실습으로 직접 관찰해야 오래 남는다.

  • 요청 흐름 추적하기: 간단한 API 서버를 만들고 DNS 조회, TCP 연결, HTTP 헤더, 서버 로그, DB 쿼리 시간을 순서대로 기록한다.

  • 인덱스 전후 비교하기: 수십만 건의 데이터를 넣고 같은 쿼리를 인덱스 전후로 실행해 실행 계획과 응답 시간을 비교한다.

  • 캐시 구현하기: LRU 캐시를 직접 만들고 캐시 적중률, 메모리 사용량, 만료 정책이 성능에 미치는 영향을 측정한다.

  • 동시성 버그 재현하기: 여러 스레드가 같은 카운터나 재고 값을 수정하게 만든 뒤, 락·트랜잭션·낙관적 락으로 해결한다.

  • 메모리 증가 분석하기: 의도적으로 전역 리스트에 객체를 계속 저장하고, 힙 사용량과 GC 로그가 어떻게 변하는지 관찰한다.

  • 부하 테스트하기: 같은 API에 동시 요청을 늘리며 평균, p95, 오류율, CPU, 메모리, DB 커넥션 사용량을 함께 본다.

  • 장애를 주입하기: DB를 잠시 끊거나 외부 API 응답을 늦게 만들어 타임아웃, 재시도, 서킷 브레이커가 제대로 동작하는지 확인한다.

실습의 목적은 멋진 결과물을 만드는 것이 아니다. 시스템이 느려지고, 깨지고, 회복되는 모습을 직접 보는 것이다. 그 경험이 설계 판단의 기준이 된다.

면접과 실무를 위한 복습 체크리스트

마지막으로, 앞으로 설계하거나 장애를 분석할 때 다음 질문을 습관처럼 확인하자.

  • 데이터 타입의 범위, 부동소수점 오차, 문자 인코딩 문제를 고려했는가?

  • 입력 크기가 커져도 알고리즘과 자료구조의 비용이 감당 가능한가?

  • CPU 병목인지, 메모리 병목인지, I/O 대기인지 구분할 수 있는 지표가 있는가?

  • 프로세스, 스레드, 커넥션 풀, 파일 디스크립터 같은 운영체제 자원 한계를 알고 있는가?

  • 공유 상태에 경쟁 상태가 없고, 락 범위가 과도하게 넓지 않은가?

  • 네트워크 실패를 전제로 타임아웃, 재시도, 멱등성을 설계했는가?

  • HTTP 메서드, 상태 코드, 캐시 헤더, 인증·인가 처리가 의도와 맞는가?

  • 데이터베이스 인덱스, 트랜잭션 범위, 격리 수준, 락 대기를 확인했는가?

  • 분산 환경에서 복제 지연, 부분 실패, 장애 전파를 고려했는가?

  • 비밀 정보가 로그에 남지 않고, 권한 확인이 서버에서 수행되는가?

  • 성능 개선 전 기준선을 측정했고, 개선 후 실제로 좋아졌는가?

  • 새 기술을 도입할 때 운영, 모니터링, 백업, 장애 대응 비용까지 계산했는가?

좋은 개발자는 모든 것을 외우는 사람이 아니다. 문제가 생겼을 때 올바른 계층을 의심하고, 필요한 지표를 찾고, 선택의 대가를 설명할 수 있는 사람이다.

CS 지식은 코드 아래에서 계속 작동한다. 프레임워크가 바뀌고 언어가 바뀌어도 데이터는 표현되어야 하고, CPU는 명령어를 실행하며, 운영체제는 자원을 나누고, 네트워크는 실패할 수 있고, 데이터베이스는 일관성과 성능 사이에서 선택한다.

이 원리를 알고 있으면 새로운 기술을 만나도 덜 흔들린다. 문법을 익히는 속도보다 중요한 것은 비용을 읽는 눈이다. 그 눈이 결국 더 안정적이고 확장 가능한 시스템을 설계하게 만든다.

책 목록

개발자가 알아야 할 컴퓨터 과학 지식 정리

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기