Kim-Baek 개발자 이야기

프로세스와 스레드 - 경력 5년차 개발자의 실전 경험 본문

컴퓨터 공학/운영체제

프로세스와 스레드 - 경력 5년차 개발자의 실전 경험

김백개발자 2026. 1. 5. 21:57
반응형

프로세스와 스레드 - 경력 5년차 개발자의 실전 경험

새벽 3시에 울린 장애 알림

새벽 3시, 장애 알림이 울렸다.

"API 서버 응답 없음. 모든 요청 타임아웃."

급하게 서버에 접속해서 확인해보니 CPU 사용률은 20%인데, 스레드 200개가 전부 BLOCKED 상태였다. 5년 차가 되어서야 깨달았다. 프로세스와 스레드의 차이를 '아는 것'과 '제대로 사용하는 것'은 완전히 다른 문제라는 것을.

이 글에서는 경력 개발자 면접에서 단골로 나오는 "프로세스와 스레드"를 기본 개념부터 실무 적용까지 상세하게 정리해보려고 한다.

프로세스란 무엇인가

프로세스의 정의

프로세스(Process)는 실행 중인 프로그램을 의미한다. 좀 더 정확히 말하면, 디스크에 저장된 실행 파일(예: .exe, .jar)이 메모리에 올라가서 실행되고 있는 상태를 프로세스라고 한다.

예를 들어보자. 크롬 브라우저를 실행하면, 하드디스크에 있던 chrome.exe 파일이 메모리에 로드되고, 운영체제는 이것을 하나의 프로세스로 관리한다. 작업 관리자(Windows) 또는 Activity Monitor(Mac)를 열어보면 실행 중인 프로세스 목록을 볼 수 있다.

프로세스의 메모리 구조

각 프로세스는 운영체제로부터 독립적인 메모리 공간을 할당받는다. 이 메모리 공간은 크게 4가지 영역으로 나뉜다.

1. Code 영역 (Text 영역)

  • 실행할 프로그램의 코드가 저장되는 곳
  • 컴파일된 기계어 명령어들이 위치
  • Read-Only 영역으로 수정 불가능

2. Data 영역

  • 전역 변수(Global Variable), 정적 변수(Static Variable)가 저장
  • 프로그램 시작 시 할당되고 종료 시 해제
  • 초기화된 데이터와 초기화되지 않은 데이터(BSS) 영역으로 세분화

3. Heap 영역

  • 프로그래머가 동적으로 할당하는 메모리 공간
  • Java/Kotlin의 new 키워드, C의 malloc() 사용 시 여기에 할당
  • 런타임에 크기가 결정됨
  • 메모리 주소가 낮은 곳에서 높은 곳으로 증가

4. Stack 영역

  • 함수 호출 시 지역 변수, 매개변수, 리턴 주소 등이 저장
  • 함수 호출 시 생성, 함수 종료 시 자동 해제
  • 메모리 주소가 높은 곳에서 낮은 곳으로 감소
  • 컴파일 타임에 크기가 결정됨
높은 메모리 주소
┌─────────────┐
│   Stack     │ ← 함수 호출, 지역 변수 (아래로 증가)
│     ↓       │
├─────────────┤
│    ...      │
├─────────────┤
│     ↑       │
│   Heap      │ ← 동적 할당 (위로 증가)
├─────────────┤
│   Data      │ ← 전역/정적 변수
├─────────────┤
│   Code      │ ← 프로그램 코드
└─────────────┘
낮은 메모리 주소

프로세스의 특징

1. 독립성

각 프로세스는 완전히 독립적인 메모리 공간을 가진다. 프로세스 A는 프로세스 B의 메모리에 절대 접근할 수 없다. 이것은 안정성 측면에서 큰 장점이다. 크롬 브라우저의 한 탭이 크래시되어도 다른 탭은 멀쩡한 이유가 바로 여기에 있다. 크롬은 각 탭을 별도 프로세스로 실행하기 때문이다.

2. 프로세스 간 통신 (IPC)

프로세스끼리 데이터를 주고받으려면 특별한 방법이 필요하다. 이를 IPC(Inter-Process Communication)라고 한다.

주요 IPC 방법:

  • 파이프 (Pipe): 단방향 통신, 부모-자식 프로세스 간
  • 메시지 큐 (Message Queue): 비동기 메시지 전달
  • 공유 메모리 (Shared Memory): 가장 빠르지만 동기화 필요
  • 소켓 (Socket): 네트워크를 통한 통신

3. Context Switching

CPU는 한 번에 하나의 프로세스만 실행할 수 있다(싱글 코어 기준). 여러 프로세스를 동시에 실행하는 것처럼 보이는 건, CPU가 아주 빠른 속도로 프로세스를 전환하기 때문이다. 이 전환 과정을 Context Switching이라고 한다.

Context Switching 과정:

  1. 현재 실행 중인 프로세스 A의 상태를 PCB(Process Control Block)에 저장
  2. 다음 실행할 프로세스 B의 상태를 PCB에서 복원
  3. 프로세스 B 실행

문제는 이 과정이 비용이 많이 든다는 것이다. 프로세스마다 독립적인 메모리 공간을 가지므로, 전환 시 캐시 메모리를 모두 비워야 한다. 이것이 나중에 설명할 스레드와의 가장 큰 차이점이다.

스레드란 무엇인가

스레드의 정의

스레드(Thread)는 프로세스 내에서 실행되는 흐름의 단위다. 프로세스가 하나의 프로그램이라면, 스레드는 그 프로그램 내에서 실행되는 작업 흐름이다.

예를 들어, 워드 프로세서 프로그램을 생각해보자. 사용자가 문서를 작성하는 동안(스레드 1), 자동 저장 기능이 백그라운드에서 동작하고(스레드 2), 맞춤법 검사도 동시에 실행된다(스레드 3). 이 세 가지 작업이 하나의 프로세스 안에서 동시에 진행되는 것이다.

스레드의 메모리 구조

스레드는 프로세스의 자원을 공유한다. 하지만 모든 것을 공유하는 건 아니다.

공유하는 영역:

  • Code 영역
  • Data 영역
  • Heap 영역

독립적인 영역:

  • Stack 영역 (각 스레드마다 별도 Stack)
  • PC Register (Program Counter)
  • 레지스터
프로세스
┌─────────────────────────┐
│  Code (공유)            │
│  Data (공유)            │
│  Heap (공유)            │
├─────────────────────────┤
│  Thread 1  │  Thread 2  │
│  Stack 1   │  Stack 2   │
│  PC Reg 1  │  PC Reg 2  │
└─────────────────────────┘

왜 Stack만 독립적일까? 각 스레드는 독립적인 함수 호출 흐름을 가지기 때문이다. 스레드 1이 함수 A를 호출하고, 스레드 2가 함수 B를 호출할 때, 각각의 지역 변수와 리턴 주소를 따로 저장해야 한다.

스레드의 특징

1. 자원 공유

같은 프로세스 내의 스레드들은 Heap 영역을 공유한다. 이것은 큰 장점이자 단점이다.

장점: 데이터 공유가 쉽다. IPC 같은 복잡한 방법이 필요 없다.

단점: 동시성 문제(Race Condition)가 발생할 수 있다. 여러 스레드가 동시에 같은 데이터를 수정하면 예상치 못한 결과가 나올 수 있다.

2. 가벼운 Context Switching

스레드 간 전환은 프로세스 간 전환보다 훨씬 가볍다. 이유는 간단하다.

프로세스 전환:

  • 전체 메모리 맵 교체
  • 캐시 메모리 전부 무효화
  • 페이지 테이블 교체

스레드 전환:

  • Stack, PC Register만 교체
  • 나머지 메모리는 그대로 유지
  • 캐시 메모리 대부분 유지

3. 생성/소멸 비용

스레드는 프로세스보다 생성과 소멸 비용이 훨씬 적다.

프로세스 생성 시:

  • 새로운 메모리 공간 전체 할당
  • Code, Data, Heap, Stack 영역 모두 초기화

스레드 생성 시:

  • Stack 영역만 새로 할당
  • 나머지는 기존 프로세스 자원 공유

프로세스 vs 스레드: 언제 무엇을 쓸까

기본 원칙

면접에서 "멀티 프로세스와 멀티 스레드 중 뭘 쓸 건가요?"라고 물어보면, 정답은 "상황에 따라 다릅니다"다. 하지만 면접관이 원하는 건 이 답이 아니다. 어떤 상황에서 어떤 선택을 했고, 그 이유가 무엇인지를 듣고 싶어 한다.

멀티 프로세스를 선택하는 경우

1. 안정성이 최우선일 때

크롬 브라우저가 대표적인 예다. 각 탭을 별도 프로세스로 실행한다. 한 탭에서 무한 루프나 메모리 누수가 발생해도, 다른 탭은 영향을 받지 않는다. 최악의 경우 해당 탭만 종료하면 된다.

2. CPU-bound 작업일 때

이미지 처리, 동영상 인코딩, 데이터 분석 같은 CPU 연산이 많은 작업은 멀티 프로세스가 유리하다. Python의 경우 GIL(Global Interpreter Lock) 때문에 멀티 스레드로는 CPU를 100% 활용할 수 없다. 이럴 땐 multiprocessing 모듈로 멀티 프로세스를 사용한다.

3. 보안이 중요할 때

프로세스 간에는 메모리가 완전히 격리되므로, 한 프로세스가 다른 프로세스의 민감한 데이터에 접근할 수 없다.

멀티 스레드를 선택하는 경우

1. I/O-bound 작업일 때

웹 서버가 대표적이다. 대부분의 시간을 DB 조회, 외부 API 호출, 파일 읽기 등 I/O 대기에 쓴다. CPU는 거의 안 쓴다. 이럴 땐 멀티 스레드가 효율적이다.

2. 메모리가 제한적일 때

서버 메모리가 8GB인데 프로세스 하나가 200MB를 쓴다면, 멀티 프로세스로 40개밖에 못 만든다. 반면 스레드는 Stack만 추가로 할당하므로(보통 1MB), 수백 개를 만들 수 있다.

3. 데이터 공유가 빈번할 때

여러 작업이 같은 데이터를 자주 읽고 써야 한다면, 스레드가 편하다. 프로세스는 IPC로 데이터를 주고받아야 해서 복잡하고 느리다.

실무 사례: Court Alarm 백엔드 설계

이제 내가 개발한 Court Alarm(테니스 코트 예약 알림 서비스)를 통해 실제 의사결정 과정을 보여주겠다.

초기 버전: 단일 프로세스의 한계

처음엔 Flask로 간단하게 시작했다.

# app.py
from flask import Flask
app = Flask(__name__)

@app.route('/api/reservations')
def get_reservations():
    # DB 조회 로직
    return jsonify(reservations)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

문제: 동시에 1개 요청만 처리 가능. 사용자 A가 느린 쿼리를 실행하는 동안, 사용자 B는 무한정 기다려야 했다.

시도 1: 멀티 프로세스 (Gunicorn)

# Gunicorn으로 4개 워커 프로세스 실행
gunicorn --workers 4 --bind 0.0.0.0:5000 app:app

장점:

  • 동시에 4개 요청 처리 가능
  • 한 워커가 크래시해도 다른 워커는 정상 동작
  • 자동으로 크래시된 워커 재시작

단점:

  • 메모리 사용량: 워커당 200MB × 4 = 800MB
  • DB 커넥션: 워커당 10개 × 4 = 40개 (DB 부하 증가)
  • 캐시 중복: 각 워커가 같은 데이터를 캐시에 저장 (비효율)

실제 결과:

  • 동시 접속자 200명까지는 OK
  • 그 이상은 응답 시간 급증 (2초 → 10초)

시도 2: 멀티 스레드 (Spring Boot)

사용자가 늘면서 Kotlin + Spring Boot로 전환했다.

// application.yml
server:
  tomcat:
    threads:
      max: 200          # 최대 스레드 수
      min-spare: 10     # 항상 유지할 스레드 수
    accept-count: 100   # 대기 큐 크기
    max-connections: 8192

spring:
  datasource:
    hikari:
      maximum-pool-size: 20  # DB 커넥션 풀 크기

장점:

  • 메모리 사용량: 1.2GB로 200개 요청 동시 처리 (멀티 프로세스의 1/5)
  • DB 커넥션: 20개만 사용 (효율적)
  • 캐시 공유: 모든 스레드가 같은 Heap 공유

단점:

  • 동시성 제어 필요 (나중에 자세히 설명)
  • 한 스레드의 메모리 누수가 전체 영향

실제 결과:

  • 동시 접속자 1,000명 처리 가능
  • 평균 응답시간 200ms 유지
  • 메모리 효율 5배 향상

왜 멀티 스레드를 선택했나

Court Alarm의 특성을 분석해보니:

작업 유형:

  • DB 조회: 전체 시간의 60%
  • 외부 API 호출: 30%
  • CPU 연산: 10%

→ I/O-bound 작업이 90%

동시 접속자:

  • 평균: 300명
  • 최대: 1,500명 (오전 9시 예약 오픈 시간)

메모리:

  • 서버 RAM: 4GB
  • 멀티 프로세스: 200MB × 20 = 4GB (한계)
  • 멀티 스레드: 1.2GB (여유 공간 2.8GB)

결론: I/O-bound 작업 + 높은 동시 접속 + 제한된 메모리 → 멀티 스레드가 최적

동시성 제어: 멀티 스레드의 가장 큰 도전

멀티 스레드를 쓰면서 가장 고생한 부분이 동시성 제어였다.

문제 상황: Race Condition

테니스 코트 마지막 1개 좌석을 두 명이 동시에 예약하는 상황을 생각해보자.

// ❌ 버그가 있는 코드
class ReservationService {
    private var availableSlots = 1  // 남은 좌석 1개
    
    fun reserve(userId: String): Boolean {
        // Thread A, B가 동시에 이 줄 실행
        if (availableSlots > 0) {
            println("$userId: 좌석 확인 OK")
            
            // DB 저장하는 동안 100ms 소요
            Thread.sleep(100)
            
            // Thread A, B가 거의 동시에 이 줄 실행
            availableSlots--
            println("$userId: 예약 완료! 남은 좌석 = $availableSlots")
            return true
        }
        return false
    }
}

// 테스트 코드
fun main() {
    val service = ReservationService()
    
    thread { service.reserve("사용자A") }
    thread { service.reserve("사용자B") }
    
    Thread.sleep(200)
}

실행 결과:

사용자A: 좌석 확인 OK
사용자B: 좌석 확인 OK
사용자A: 예약 완료! 남은 좌석 = 0
사용자B: 예약 완료! 남은 좌석 = -1

좌석은 1개인데 2명이 예약 성공! 이것이 Race Condition(경쟁 조건)이다.

해결 방법 1: synchronized

// ✅ synchronized로 해결
class ReservationService {
    private var availableSlots = 1
    
    @Synchronized  // 한 번에 한 스레드만 실행
    fun reserve(userId: String): Boolean {
        if (availableSlots > 0) {
            Thread.sleep(100)
            availableSlots--
            println("$userId: 예약 완료!")
            return true
        }
        println("$userId: 예약 실패 (만석)")
        return false
    }
}

장점: 구현 간단, 안전

단점: 성능 저하 심각

  • Thread A가 실행 중이면 Thread B는 무조건 대기
  • 동시 접속 1,000명이면 평균 대기 시간 = 100ms × 500 = 50초

해결 방법 2: AtomicInteger

// ✅ Atomic 연산으로 해결
import java.util.concurrent.atomic.AtomicInteger

class ReservationService {
    private val availableSlots = AtomicInteger(1)
    
    fun reserve(userId: String): Boolean {
        // CAS (Compare-And-Swap) 연산: Lock-free
        val newValue = availableSlots.updateAndGet { current ->
            if (current > 0) current - 1 else current
        }
        
        if (newValue >= 0) {
            println("$userId: 예약 완료!")
            return true
        }
        println("$userId: 예약 실패 (만석)")
        return false
    }
}

장점: Lock 없이 동작, 성능 우수

단점: 단일 서버에서만 동작

해결 방법 3: Redis 분산 락 (최종 선택)

Court Alarm은 서버 3대로 운영 중이었다. synchronized와 AtomicInteger는 단일 서버 내에서만 동작한다. 분산 환경에서는 Redis 같은 외부 저장소를 활용해야 한다.

// ✅ Redis 분산 락으로 해결
@Service
class ReservationService(
    private val redissonClient: RedissonClient,
    private val courtRepository: CourtRepository
) {
    fun reserve(courtId: Long, userId: String): Boolean {
        // Redis 락 획득 시도 (최대 3초 대기)
        val lock = redissonClient.getLock("court:reservation:$courtId")
        
        return try {
            // 락 획득 시도: 3초 대기, 획득 후 5초 자동 해제
            val acquired = lock.tryLock(3, 5, TimeUnit.SECONDS)
            
            if (!acquired) {
                throw IllegalStateException("락 획득 실패")
            }
            
            // 임계 영역 (Critical Section)
            val court = courtRepository.findById(courtId)
            if (court.availableSlots > 0) {
                court.availableSlots--
                courtRepository.save(court)
                true
            } else {
                false
            }
        } finally {
            // 락 해제
            if (lock.isHeldByCurrentThread) {
                lock.unlock()
            }
        }
    }
}

왜 Redis 락을 선택했나:

  1. 분산 환경 지원: 서버 3대 모두 같은 Redis 사용
  2. 성능: 락 획득 시간 평균 5ms
  3. 안정성: 락을 획득한 서버가 죽어도 5초 후 자동 해제

성능 측정 결과:

  • synchronized: 평균 응답시간 2,000ms
  • Redis 락: 평균 응답시간 250ms

장애 경험: 스레드 풀 고갈

멀티 스레드를 운영하면서 가장 큰 장애를 겪었던 사례를 공유한다.

사건 발생

어느 날 오전 10시, 모니터링 대시보드에 빨간불이 들어왔다.

증상:

  • API 응답시간: 200ms → 30초
  • 에러율: 0% → 95%
  • 스레드 풀 사용률: 100% (200/200)

원인 분석

급하게 Thread Dump를 떴다.

# Thread Dump 생성
jstack <pid> > thread_dump.txt

# BLOCKED 상태 스레드 검색
grep "WAITING" thread_dump.txt | wc -l
# 결과: 195개

Thread Dump를 분석해보니, 거의 모든 스레드가 외부 날씨 API 응답을 기다리고 있었다.

"http-nio-8080-exec-1" #123 waiting on condition
   java.lang.Thread.State: WAITING
   at WeatherApiClient.getWeather()
   
"http-nio-8080-exec-2" #124 waiting on condition
   java.lang.Thread.State: WAITING
   at WeatherApiClient.getWeather()
   
... (195개 동일)

원인:

  1. 외부 날씨 API 서버 장애
  2. 우리 코드에 타임아웃 설정 안 함
  3. 스레드들이 응답을 무한정 대기
  4. 새로운 요청이 들어와도 스레드가 없어서 전부 실패

코드 문제점

// ❌ 타임아웃 없는 코드
@Service
class WeatherService {
    private val restTemplate = RestTemplate()
    
    fun getWeather(location: String): WeatherInfo {
        val url = "https://api.weather.com/forecast?location=$location"
        
        // 타임아웃 설정 없음!
        // API 서버가 응답 안 하면 영원히 대기
        return restTemplate.getForObject(url, WeatherInfo::class.java)!!
    }
}

해결 방법

1단계: 타임아웃 설정

// ✅ 타임아웃 추가
@Configuration
class RestTemplateConfig {
    @Bean
    fun restTemplate(): RestTemplate {
        return RestTemplateBuilder()
            .setConnectTimeout(Duration.ofSeconds(3))  // 연결 타임아웃
            .setReadTimeout(Duration.ofSeconds(5))     // 읽기 타임아웃
            .build()
    }
}

2단계: Circuit Breaker 도입

// ✅ Circuit Breaker 추가 (Resilience4j)
@Service
class WeatherService {
    
    @CircuitBreaker(
        name = "weatherApi",
        fallbackMethod = "getDefaultWeather"
    )
    fun getWeather(location: String): WeatherInfo {
        return restTemplate.getForObject(url, WeatherInfo::class.java)!!
    }
    
    // Fallback 메서드
    private fun getDefaultWeather(location: String, ex: Exception): WeatherInfo {
        logger.warn("날씨 API 호출 실패, 기본값 반환", ex)
        return WeatherInfo(
            temperature = 20,
            condition = "맑음",
            message = "날씨 정보를 가져올 수 없습니다"
        )
    }
}

Circuit Breaker 설정:

# application.yml
resilience4j:
  circuitbreaker:
    instances:
      weatherApi:
        failure-rate-threshold: 50        # 실패율 50% 이상이면
        wait-duration-in-open-state: 60s  # 60초 동안 Circuit Open
        permitted-number-of-calls-in-half-open-state: 3  # 3번 시도 후 판단

3단계: 스레드 풀 분리

외부 API 호출 전용 스레드 풀을 별도로 만들었다.

@Configuration
class ExecutorConfig {
    
    // 일반 요청 처리용 (기본 Tomcat 스레드 풀)
    // max: 200
    
    // 외부 API 호출 전용
    @Bean("externalApiExecutor")
    fun externalApiExecutor(): Executor {
        val executor = ThreadPoolTaskExecutor()
        executor.corePoolSize = 10
        executor.maxPoolSize = 50
        executor.queueCapacity = 100
        executor.setThreadNamePrefix("external-api-")
        executor.initialize()
        return executor
    }
}

// 사용
@Service
class WeatherService(
    @Qualifier("externalApiExecutor")
    private val executor: Executor
) {
    fun getWeatherAsync(location: String): CompletableFuture<WeatherInfo> {
        return CompletableFuture.supplyAsync({
            getWeather(location)
        }, executor)
    }
}

결과

Before:

  • 외부 API 장애 → 전체 서비스 다운
  • 복구 시간: 수동 재시작 필요 (10분)

After:

  • 외부 API 장애 → 날씨 기능만 Fallback, 나머지 정상
  • 자동 복구: Circuit Breaker가 60초마다 재시도
  • 서비스 가용성: 99.9% → 99.99%

면접에서 이렇게 답하자

경력 5년차 면접에서 "프로세스와 스레드"를 물어본다면:

1단계: 기본 개념 (30초)

"프로세스는 실행 중인 프로그램으로, 독립적인 메모리 공간(Code, Data, Heap, Stack)을 가집니다. 스레드는 프로세스 내부의 실행 단위로, Stack만 독립적이고 나머지는 공유합니다."

2단계: 핵심 차이 (30초)

"가장 큰 차이는 격리성과 Context Switching 비용입니다. 프로세스는 완전 격리되어 안정적이지만 전환 비용이 크고, 스레드는 자원을 공유해 효율적이지만 동시성 제어가 필요합니다."

3단계: 실무 의사결정 (1분)

"제가 개발한 Court Alarm 서비스는 I/O-bound 작업이 90%였습니다. DB 조회와 외부 API 호출이 대부분이라 멀티 스레드를 선택했습니다. Spring Boot Tomcat으로 스레드 200개를 운영하며, 메모리 1.2GB로 동시 접속 1,000명을 처리했습니다."

4단계: 기술적 도전 (1분)

"가장 어려웠던 건 동시성 제어였습니다. 예약 좌석 Race Condition이 발생해서, 처음엔 synchronized를 썼는데 성능이 떨어졌습니다. 최종적으로 Redis 분산 락을 도입해 평균 응답시간을 250ms로 유지하면서도 데이터 정합성을 보장했습니다."

5단계: 장애 경험 (1분)

"외부 API 타임아웃 미설정으로 스레드 풀이 고갈된 장애를 겪었습니다. 이후 모든 외부 호출에 타임아웃을 설정하고, Circuit Breaker와 전용 스레드 풀을 도입해 장애 격리를 구현했습니다. 결과적으로 서비스 가용성이 99.9%에서 99.99%로 개선됐습니다."

핵심 팁

숫자로 말하기:

  • ❌ "성능이 좋아졌어요"
  • ✅ "응답시간이 2,000ms에서 250ms로 88% 개선됐습니다"

트레이드오프 설명:

  • ❌ "멀티 스레드가 더 좋아요"
  • ✅ "I/O-bound 작업에서는 멀티 스레드가 메모리 효율적이지만, 동시성 제어가 필요합니다"

장애 경험 포함:

  • ❌ "이론적으로 이렇게 하면 됩니다"
  • ✅ "실제로 이런 장애를 겪었고, 이렇게 해결했습니다"

정리

프로세스와 스레드는 단순 암기 과목이 아니다. 실무에서는 항상 트레이드오프를 고려해야 한다.

언제 멀티 프로세스를 쓸까:

  • 안정성이 최우선 (브라우저 탭)
  • CPU-bound 작업 (이미지 처리)
  • 보안이 중요한 경우

언제 멀티 스레드를 쓸까:

  • I/O-bound 작업 (웹 서버)
  • 메모리가 제한적일 때
  • 데이터 공유가 빈번할 때

꼭 기억해야 할 것:

  1. 외부 호출에는 반드시 타임아웃 설정
  2. 분산 환경에서는 Redis 같은 분산 락 사용
  3. 스레드 풀은 작업 유형별로 분리
  4. Circuit Breaker로 장애 전파 차단
  5. 모니터링으로 스레드 사용률 추적

다음 글에서는 "메모리 관리"를 다루면서, Heap과 Stack의 동작 원리, 그리고 실제 메모리 누수를 어떻게 찾아내고 해결했는지 이야기해보겠다.

 

반응형
Comments