[ TIL ] 네트워크 기초 지식

정승민·2026년 8월 21일

IT개념

목록 보기
1/1

🚀 백엔드 & 네트워크 코어 지식 요약 노트

1. 비동기 프로그래밍 기초

📌 프로세스와 스레드

  • 프로그램 (Program): 하드디스크에 저장된 정적인 '코드'
  • 프로세스 (Process): RAM에 올라가 프로그램이 실제로 동작하는 상태. 프로세스는 OS로부터 자기만의 독립된 메모리 공간을 할당받는다. (서로 다른 프로세스는 독립된 메모리 공간에 존재)
  • 스레드 (Thread): 프로세스 안에서 실제로 일을 진행하는 '일꾼'.
    • 하나의 프로세스에는 반드시 메인 스레드 하나가 존재하며, 추가로 멀티 스레드를 사용할 수 있다.
    • 동일한 프로세스 내부에서 스레드들은 메모리를 공유한다.
    • 스레드는 그 환경 안에서 현재 어느 명령어를 실행하고 있는지 나타내는 실행 단위이다. (즉, CPU는 프로세스를 실행하는 게 아니라, 프로세스에 속한 스레드를 실행한다.)

스레드에 존재하는 독립적인 상태들

  • PC (Program Counter): 다음에 실행할 CPU 명령어의 주소
  • CPU Register 값: 현재 계산 중인 값과 호출 상태
  • 스택 포인터와 전용 스택: 함수 호출 기록, 지역 변수, 반환 주소 등
  • 스레드 ID
  • 스케줄링 정보: 실행 가능, 실행 중, 대기 중 등의 상태와 우선순위에 대한 정보
  • Thread Local Storage: 해당 스레드만 사용하는 데이터

💡 스레드가 필요한 이유
실행 흐름이 단 하나라면 프로세스 하나로도 충분하지만, 실행 흐름이 여러 개이거나 한 작업이 디스크, 네트워크, 입력 등을 기다리게 되면 전체 작업이 멈추게 된다. 여러 프로세스를 사용하면 메모리 공간을 공유하지 못하는 단점이 있으므로, 스레드 단위로 나누어 하나의 프로세스 안에서 병렬로 작업을 실행하기 위해 필요하다.

  • 컨텍스트 스위칭 (Context Switching): OS가 실행 중인 스레드를 멈추고 레지스터와 Instruction Pointer 등을 저장한 뒤, 다른 스레드에 저장된 상태를 CPU에 올려서 수행하는 기법. (수행 중인 스레드를 교체하는 작업)

📌 동시성 제어 (Concurrency Control)

  • 경쟁 상태 (Race Condition): 하나의 프로세스에서 여러 스레드가 메모리를 공유하다 보니, Read-Write 과정에서 원하지 않는 타이밍에 컨텍스트 스위칭이 발생해 동기화가 깨지는 현상. (두 스레드가 동시에 공유 메모리에 접근해 값을 변경하려고 할 때 발생하는 치명적인 문제)
  • 동기화 (Synchronization): Lock이나 Mutex를 사용하여 서로 다른 스레드가 항상 동일한 값을 읽어올 수 있도록 해주는 기법.
    • Lock / Mutex: 공유 메모리를 한 스레드가 사용하는 동안 자물쇠(Lock)를 걸어 다른 스레드가 접근하지 못하게 하여 데이터 오염을 막는다. (다른 스레드는 작업이 끝날 때까지 대기)
    • Event: Lock이 끝나는 경우 .set()을 호출해 다른 스레드에게 사용해도 된다는 알림을 전달한다. (.wait() 중이던 스레드들의 작업 재개)
  • 교착 상태 (Deadlock): A 스레드가 자원 a를 쥐고 b를 대기 중이고, B 스레드가 자원 b를 쥐고 a를 대기하는 경우 둘 다 영원히 대기만 하는 상황.

💀 Deadlock이 발생하는 4가지 필수 조건 (모두 충족 시 발생)
1. 상호 배제 (Mutual Exclusion): 자원을 한 번에 하나의 스레드만 사용 가능해야 한다.
2. 점유와 대기 (Hold and Wait): 자원을 하나 쥔 상태에서 다른 자원을 기다려야 한다.
3. 비선점 (No Preemption): 남이 사용 중인 자원을 강제로 가져올 수 없다.
4. 순환 대기 (Circular Wait): A는 B 자원을, B는 A 자원을 원형으로 물고 기다려야 한다.

🛡️ Deadlock 해결 전략
1. 예방 (Prevention): 4가지 필수 조건 중 한 가지를 원천 차단하는 방법.
2. 회피 (Avoidance): 데드락을 미리 계산해서, 안전한 상태인 경우에만 Lock을 빌려준다.
3. 탐지 및 회복 (Detection & Recovery): 스레드를 자유롭게 풀어두되, 중간중간 데드락 검사를 하여 걸린 스레드 중 하나를 강제 종료시킨다.


📌 스레드 상태 관리

스레드는 생성부터 소멸까지 효율적인 운영을 위해 OS가 상태 꼬리표를 달아 철저하게 관리한다.

  • 생성 (New): 스레드가 막 만들어진 상태
  • 실행 대기 (Ready): 당장이라도 일할 준비가 되어 있지만, 아직 CPU를 배정받지 못해 줄을 서 있는 상태
  • 실행 (Running): 실제로 CPU를 할당받아 열심히 코드를 실행 중인 상태
  • 대기/블로킹 (Blocked/Waiting): I/O 작업을 요청했거나, 다른 스레드가 Lock을 걸어두어 강제로 일손을 멈추고 기다리는 상태
  • 종료 (Terminated): 모든 작업을 마치고 스레드가 소멸한 상태

📌 코루틴과 비동기 제어

  • 코루틴 (Coroutine): 실행을 중간에 일시 중단했다가 나중에 그 지점부터 다시 재개할 수 있는 함수. (async def로 생성하고, 내부에서 await 키워드를 사용해 제어권을 넘긴다.)

    • vs 멀티 스레드: 멀티 스레드는 OS가 제어권을 강제로 뺏어가지만(스케줄링), 코루틴은 단일 스레드 안에서 애플리케이션(개발자)이 주체가 되어 자발적으로 제어권을 양보한다. 따라서 동기화 문제가 심각하지 않다.
  • GIL (Global Interpreter Lock): CPython 인터프리터가 "파이썬 바이트코드는 프로세스당 한 번에 한 스레드만 실행한다"고 강제하는 락(Lock). OS로부터 다중 코어를 할당받아도, 실행권은 단 하나의 스레드에게만 부여된다.

    • 작동 예시: A 스레드가 GIL 통제권을 얻어 실행 중 I/O 작업을 만나면 GIL을 내려놓는다. 이때 B 스레드가 GIL을 가져가 실행된다. A가 I/O를 끝내도 B가 GIL을 반환할 때까지 대기해야 한다. 스레드들은 자신만의 독립적인 스택(메모리)을 가지므로 한 프로세스 내에서도 독립적인 수행이 가능하다.
  • 블로킹 (Blocking) vs 논블로킹 (Non-blocking): 스레드의 '제어권'에 초점을 맞춘 개념.

    • 블로킹: 스레드가 I/O 작업을 요청(System call)하면, 결과를 반환할 때까지 호출된 쪽이 제어권을 쥐고 돌려주지 않아 스레드가 아무 작업도 못 하고 멈춰버리는 상태.
    • 논블로킹: I/O 작업을 요청하고 OS가 계속 진행하라는 결과를 반환하면 곧바로 다음 코드를 진행.
  • 동기 (Sync) vs 비동기 (Async): 작업의 '순서와 결과'에 초점을 맞춘 개념.

    • 동기: 특정 결과를 받아야지만 다음 작업으로 진행되는 경우.
    • 비동기: 특정 결과를 기다리지 않고 다음 작업으로 넘어가는 경우.

🐍 Python에서의 사용
일반 def는 동기(Sync)로 동작하며, async def와 await 키워드를 사용해 비동기를 처리한다. await는 내부적으로 Yield 메커니즘을 사용하여, 결과를 반환할 때까지 현재 진행 상황을 저장해 두고 다른 작업을 수행할 수 있도록 제어권을 넘겨준다.


2. ASGI 서버와 Uvicorn의 정체

  • WSGI (Web Server Gateway Interface): 과거 파이썬 웹 생태계(Flask, Django 등)에서 사용하던 동기(Sync) 방식의 규격. 클라이언트 1명당 1개의 스레드를 할당하여, DB 조회나 딥러닝 연산 등 I/O 대기 시 블로킹(대기)이 발생한다. 접속자가 많아지면 서버가 멈추거나 터지는 한계가 있었다.
  • ASGI (Asynchronous Server Gateway Interface): 위 문제를 해결하기 위해 등장한 비동기 인터페이스 (예: FastAPI). 기다리는 시간 동안 스레드의 제어권을 넘겨 다른 클라이언트의 요청을 동시에 처리할 수 있다.
  • Uvicorn: FastAPI 같은 ASGI 프레임워크로 작성된 코드를 실제로 실행해 주는 엔진(웹 서버).

💡 네트워크 요청 처리 파이프라인 예시
1. 사용자가 스마트폰 브라우저에서 로그인 버튼을 눌러 HTTP 요청서를 보낸다.
(예: POST /login HTTP/1.1 Host: www.mysite.com {"user_id": "hello", "password": "1234"})
2. 특정 포트로 도착한 데이터를 OS가 받아 HTTP 요청서로 재조립한다.
3. 이때 가장 먼저 마중 나오는 것이 웹 서버 (Uvicorn)이다.
4. Uvicorn이 날것의 HTTP 데이터를 파이썬이 다루기 쉬운 Dictionary 형태로 번역해 준다 (ASGI 규격).
5. 포장된 데이터를 FastAPI에게 넘기면, 개발자가 작성한 @app.post('/login') 라우팅을 타고 로직이 실행된다.


3. 네트워크 & 서버 인프라 핵심

📌 IP 주소와 포트

  • 공인 IP (Public IP): 전 세계 어디서든 IP만 알면 정확히 찾아올 수 있는 고유한 대표 주소. (통신사에서 부여)
  • 사내/사설 IP (Private IP): 공유기 내부(가정, 회사)에서만 사용하는 내부망 주소 (예: 192.168.x.x). 외부 인터넷망에서 직접 찾아올 수 없다.
    • 분리 이유: 전 세계 IP 주소 고갈 문제를 해결하기 위해, 건물 대표 주소(공인 IP) 1개를 두고 공유기가 내부망에 내선 번호(사설 IP)를 뿌려주는 방식을 채택.
  • IP와 랜카드의 관계: PC 1대당 IP가 무조건 1개인 것은 아니다. 네트워크 인터페이스(랜카드)마다 1개씩 부여되므로 유선/무선 랜을 동시 사용 시 여러 개의 IP를 가질 수 있다.
  • 포트 (Port): 1개의 IP 안에는 서비스별로 연결할 수 있는 방(포트)이 65,535개 존재한다.

📌 포트 포워딩 (Port Forwarding)

외부에서 공인 IP를 치고 들어왔을 때, 공유기가 수많은 사설 IP 중 목적지를 찾을 수 있도록 미리 설정해 두는 '안내 장부'.

  • 동작 원리: 외부에서 공인IP:8000 접속 요청 ➡ 공유기가 수신 ➡ 설정된 규칙에 따라 내부IP(192.168.0.10):8000으로 요청 전달.
  • 설정 3요소: 외부 포트(접속용 창구 번호), 내부 IP(수신할 PC 주소), 내부 포트(PC 안에서 대기 중인 방 번호).

📌 서버 오픈을 위한 3단계 (Inbound)

  1. 포트 바인딩 (Listening): 파이썬 코드 등을 실행해 OS에게 "이 포트 쓸 테니 열어줘!"라고 요청하고 대기.
  2. 방화벽 (Firewall) 허용: OS 자체의 보안 경호원인 방화벽(ufw 등)에서 해당 포트의 외부 접근을 허용하는 명령어 실행.
  3. 포트 포워딩: 공유기를 거치는 환경일 경우 관리자 페이지에 포워딩 규칙 작성.

📌 데이터 이동 흐름 (Outbound)

내 PC에서 외부 인터넷(구글 등)으로 데이터를 보낼 때의 흐름:
1. 출발: 192.168.0.10 (내 PC에서 패킷 생성)
2. 안내데스크: 내부망을 타고 공유기(192.168.0.1) 도착
3. NAT (주소 변환): 공유기가 사설 IP를 숨기고 '공인 IP'로 발신자 스티커를 교체
4. 도착: 인터넷 세상을 거쳐 목적지에 도달

네트워크 흐름도


4. 웹소켓과 상태 유지

컴퓨터들이 소통하는 방식은 크게 무상태(Stateless)와 상태 유지(Stateful)로 나뉜다.

  • HTTP (Stateless): 매 요청마다 인증서나 신분증을 같이 제공해야 하며, 서버가 클라이언트의 상태를 기억하지 않는다. 단방향 통신. (80번/443번 포트 사용)
  • WebSocket (Stateful): 서버가 클라이언트가 누구인지 지속적으로 기억하며, 실시간 양방향 통신이 가능하다. (HTTP와 동일하게 80번/443번 포트를 공유)

🔄 웹소켓 연결 과정 (4단계)
1. Handshake: 클라이언트가 서버에게 연결 요청 (처음엔 HTTP 방식 사용).
2. 연결 수락 및 업그레이드: 서버가 수락하면 프로토콜을 업그레이드하여 둘 사이에 전용 통로가 뚫린다.
3. 상태 유지 및 실시간 소통: 통로 끝에 누가 있는지 기억(상태 유지). 클라이언트가 새로고침(요청)을 하지 않더라도 서버가 통로를 통해 즉각적으로 데이터를 밀어 넣어줄 수 있다.
4. 연결 종료: 작업이 끝나면 연결된 통로를 끊는다.

📌 기저의 통신 프로토콜 (TCP / UDP)

  • TCP (Transmission Control Protocol): 데이터를 가장 안전하고 확실하게 배달하는 통신 시스템. 웹 브라우징, 웹소켓 등 신뢰성이 중요한 곳에 쓰인다.
    • 3-Way Handshake를 통해 상대방의 준비 상태를 미리 확인.
    • 데이터 유실 시 재전송 및 순서 조립 보장.
  • UDP (User Datagram Protocol): TCP와 다르게 연결 확인이나 재배송을 하지 않고 데이터를 빠르게 냅다 던지는 방식. 속도가 생명인 유튜브 라이브 스트리밍이나 온라인 FPS 게임 등에서 주로 사용한다.

5. 파이썬 동시성 & 병렬성 심화 (Concurrency vs Parallelism)

📌 3가지 동시성 처리 방식의 본질적인 차이

많은 개발자가 헷갈리는 '비동기', '멀티 스레드', '멀티 프로세스'의 개념은 작업자(스레드)의 수와 메모리 공유 여부, 그리고 주체에 따라 명확히 구분됩니다.

  1. 비동기 (Asyncio) : "1인 주방의 달인"
    • 스레드 1개 안에서 일어나는 기술입니다.
    • 요리사(스레드)가 스스로 I/O 대기(물 끓이기 등) 시간을 파악하고, 기다리는 동안 멈춰있지 않고 다른 작업(야채 썰기)으로 스스로 제어권을 양보하며 일을 계속합니다.
    • OS가 개입하는 강제 교체(Context Switching)가 없어서 매우 가볍고 빠르며, 메모리 공유 문제(Race Condition)도 적습니다.
  2. 멀티 스레딩 (Multi-threading) : "한 주방 안의 여러 요리사"
    • 하나의 프로세스(주방) 안에서 메모리(도마)를 공유하며 여러 스레드가 작업합니다.
    • OS가 강제로 스레드를 번갈아 가며 실행시킵니다.
    • 파이썬의 한계 (GIL): 파이썬은 GIL(Global Interpreter Lock) 때문에 코어가 아무리 많아도 파이썬 바이트코드는 한 번에 하나의 스레드만 실행할 수 있습니다. 즉, 진정한 병렬 처리가 아니라 아주 빠르게 번갈아 실행되는 '가짜 동시성(Concurrency)'만 지원합니다.
  3. 멀티 프로세싱 (Multi-processing) : "주방 건물을 통째로 복제"
    • 스레드를 늘리는 것이 아니라, 독립된 메모리를 가진 프로세스 자체를 N개로 복제합니다.
    • 프로세스마다 자신만의 GIL을 따로 가지므로, GIL의 통제를 완전히 우회하여 물리적인 '진짜 병렬 처리(Parallelism)'가 가능해집니다.

📌 멀티 프로세싱의 황금률과 치명적 오해

🚨 1. 싱글 코어(CPU 1개)에서 멀티 프로세싱을 쓰면 최악인 이유

CPU 코어가 1개일 때 멀티 프로세싱을 시도하면 병렬성의 장점은 사라지고 최악의 비효율이 발생합니다.

  • 막대한 컨텍스트 스위칭 오버헤드: 코어(진짜 요리사)는 1명인데 주방 건물(프로세스)만 4개인 상황입니다. 요리사가 이 건물 저 건물을 뛰어다니느라(프로세스 교체 작업) 정작 요리할 시간을 다 버리게 됩니다. 스레드 교체보다 훨씬 무거운 비용이 발생합니다.
  • 극심한 메모리 낭비: 같은 프로그램을 복제하므로 RAM 용량만 헛되이 낭비하게 됩니다. 오히려 프로세스 1개로 순서대로 처리하는 것보다 속도가 느려집니다.

🎯 2. 올바른 프로세스 개수 설정 : "코어 수에 맞춰라!"

멀티 프로세싱의 성능을 100% 끌어내려면 생성하는 프로세스의 수를 내 컴퓨터의 논리적 CPU 코어 수와 일치시키는 것이 가장 이상적입니다.

  • 프로세스 < 코어 수: 놀고 있는 잉여 코어가 생겨 자원이 낭비됩니다.
  • 프로세스 > 코어 수: 코어가 프로세스를 번갈아 맡아야 하므로 다시 컨텍스트 스위칭 오버헤드가 발생해 속도가 떨어집니다.

💡 실전 개발 팁 (권장 프로세스 수 = 코어 수 - 1)
내 PC의 코어가 8개라고 프로세스를 딱 8개 띄우면, 파이썬이 CPU를 100% 점유해 버려 OS(윈도우 등)나 마우스 커서마저 멈추는 현상이 발생할 수 있습니다.
따라서 multiprocessing.cpu_count() - 1 공식을 사용하여, 코어 1개 정도는 OS와 백그라운드 프로그램을 위해 숨통을 틔워주는 것이 실무적인 표준입니다.


📌 동시성 제어 기술 한눈에 보기

비교 항목멀티 스레딩 (Multi-threading)비동기 (Asyncio)멀티 프로세싱 (Multi-processing)
작업자 수1개 주방, 여러 명의 요리사 (스레드 N개)1개 주방, 단 1명의 요리사 (스레드 1개)주방 건물 N개, 요리사 N명 (프로세스 N개)
메모리 상태공유함 (Race Condition 주의)공유함 (단일 스레드라 통제 쉬움)독립됨 (데이터 공유 어렵고 RAM 소모 큼)
적합한 작업파일 읽기, 가벼운 대기 작업네트워크 API, DB 통신 (I/O 대기 최적화)영상 처리, 모델 훈련 등 무거운 수학적 연산 (CPU 최적화)
핵심 원리OS가 강제로 스레드를 번갈아 교체스레드 스스로 대기 시간에 다른 작업 수행물리적으로 여러 코어에서 진짜 동시 실행 (GIL 우회)
profile
매일 꾸준히

0개의 댓글