성능 튜닝과 OS 면접 주제
파일·TCP·메모리·I/O 커널 파라미터를 측정 근거로 조정하고 운영체제 핵심 질문을 병목 진단 관점에서 정리합니다.
이 절에서는 운영체제 면접에서 자주 다루는 주제와 서버 운영에서 확인하는 커널 파라미터를 함께 정리합니다.
튜닝 항목은 병목 유형, 측정 지표, 변경 전후 검증 기준을 중심으로 봅니다.
커널 파라미터 튜닝
파일 디스크립터 제한
# 프로세스당 최대 열 수 있는 파일 수
ulimit -n # 현재 셸의 값 확인 (환경에 따라 다름)
ulimit -n 65535 # 허용된 hard limit 안에서 현재 셸과 이후 자식에 적용
# PAM의 pam_limits를 거치는 새 세션 설정: /etc/security/limits.conf
# * soft nofile 65535
# * hard nofile 65535
# 시스템 전체 제한
cat /proc/sys/fs/file-max
sysctl -w fs.file-max=2097152웹 서버가 동시에 수천 개의 연결을 처리하려면 파일 디스크립터 제한을 높여야 합니다.
소켓도 파일 디스크립터를 사용하기 때문입니다.
현재 열린 파일 수와 한도 초과 오류를 확인한 뒤 필요한 범위로 정합니다. limits.conf는 PAM 세션에, systemd 서비스는 LimitNOFILE 등에 의해 적용 범위가 달라집니다.
TCP 튜닝
# 현재 값 확인
sysctl -a | grep somaxconn
# accept를 기다리는 연결의 listen backlog 상한
sysctl -w net.core.somaxconn=65535
# 리스너별 미완료 연결 요청 한도 (SYN_RECV)
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
# 프로토콜상 안전한 TIME_WAIT 재사용 허용 (일괄 권장값 아님)
sysctl -w net.ipv4.tcp_tw_reuse=1
# SO_KEEPALIVE가 활성화된 소켓의 기본 keepalive 설정
sysctl -w net.ipv4.tcp_keepalive_time=300 # 300초 후 첫 probe
sysctl -w net.ipv4.tcp_keepalive_intvl=30 # 30초 간격
sysctl -w net.ipv4.tcp_keepalive_probes=5 # 5번 실패 시 연결 종료
# 사용 가능한 로컬 포트 범위 확장
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
# SYN 큐 초과 시 SYN cookies 사용 허용
sysctl -w net.ipv4.tcp_syncookies=1두 종류의 연결 큐, 재사용 정책, 종료 상태와 로컬 포트 범위를 나눕니다.
| 대상 | 설정 이름 | 적용 범위 |
|---|---|---|
| accept 대기열 | somaxconn | 애플리케이션 listen backlog의 상한. 처리량이나 연결 성공을 보장하지 않음 |
| 미완료 연결 | tcp_max_syn_backlog | 리스너별 SYN_RECV 요청 한도. 정상 부하 대책과 SYN cookies는 구분 |
| 종료된 연결 재사용 | tcp_tw_reuse | 프로토콜상 안전한 재사용만 허용. 0: 끔, 1: 전체, 2: 루프백만 |
| 고아 연결 정리 | tcp_fin_timeout | 앱이 더 이상 참조하지 않는 FIN_WAIT_2 연결의 대기 한도. TIME_WAIT 시간이 아님 |
| 임시 로컬 포트 | ip_local_port_range | 자동 할당할 로컬 포트 범위. 예약 포트·목적지와 연결 상태도 고려 |
- accept 대기열
- 설정 이름: somaxconn적용 범위: 애플리케이션 listen backlog의 상한. 처리량이나 연결 성공을 보장하지 않음
- 미완료 연결
- 설정 이름: tcp_max_syn_backlog적용 범위: 리스너별 SYN_RECV 요청 한도. 정상 부하 대책과 SYN cookies는 구분
- 종료된 연결 재사용
- 설정 이름: tcp_tw_reuse적용 범위: 프로토콜상 안전한 재사용만 허용. 0: 끔, 1: 전체, 2: 루프백만
- 고아 연결 정리
- 설정 이름: tcp_fin_timeout적용 범위: 앱이 더 이상 참조하지 않는 FIN_WAIT_2 연결의 대기 한도. TIME_WAIT 시간이 아님
- 임시 로컬 포트
- 설정 이름: ip_local_port_range적용 범위: 자동 할당할 로컬 포트 범위. 예약 포트·목적지와 연결 상태도 고려
기본값은 커널·배포판에 따라 확인합니다. 예를 들어 somaxconn의 upstream 기본값은 Linux 5.4부터 4096이며, 위 65535는 이 예제의 값입니다.
메모리 튜닝
# 익명 메모리 스왑과 파일 페이지 회수의 상대 비용 (0~200)
sysctl -w vm.swappiness=10
# 10은 설정 형식 예시. 저장장치와 부하를 측정해 선택
# overcommit 정책
sysctl -w vm.overcommit_memory=0 # 0: 휴리스틱으로 과도한 요청 거부
# 1: overcommit 회계 제한을 적용하지 않음
# 2: 계산된 commit 한도 적용
# dirty page 회수·쓰기 임계값과 오래된 페이지 기준
sysctl -w vm.dirty_ratio=20 # free·회수 가능 메모리 기준 dirty 임계 비율
sysctl -w vm.dirty_background_ratio=5 # 5% 초과 시 백그라운드 플러시 시작
sysctl -w vm.dirty_expire_centisecs=3000 # 30초 이상 된 dirty page를 writeback 대상으로 간주
# 영구 설정
echo "vm.swappiness=10" >> /etc/sysctl.conf
sysctl -p # 적용swappiness는 익명 페이지 스왑과 파일 페이지 회수의 상대 비용을 조정합니다. 0도 스왑 완전 금지는 아니며, 100을 넘는 값도 사용할 수 있습니다.
overcommit의 모드 2는 스왑과 설정된 RAM 비율 또는 overcommit_kbytes 등을 바탕으로 commit 한도를 계산합니다. 모드 1도 주소 공간·자원 한도나 실제 메모리 부족에 의한 실패를 없애지 않습니다.
설정 전에 현재 값과 되돌릴 값을 남기고, 병목 가설에 맞는 설정 하나를 바꿔 처리량과 p95·p99 지연을 비교합니다. sysctl -w는 즉시 적용되므로 아래 값을 일괄 추천으로 읽지 않습니다.
I/O 스케줄러
# 현재 I/O 스케줄러 확인
cat /sys/block/sda/queue/scheduler
# [mq-deadline] kyber bfq none
# 변경 (즉시 적용)
echo "none" > /sys/block/nvme0n1/queue/scheduler # NVMe SSD
echo "mq-deadline" > /sys/block/sda/queue/scheduler # HDD/SATA SSD- none: 별도 블록 I/O 스케줄러를 두지 않습니다. 예전 단일 큐의 noop과 같은 이름은 아닙니다.
- mq-deadline: 요청 만료 기준으로 오래 기다린 요청을 우선하되 완료 시각을 보장하지는 않습니다.
- bfq: 대역폭 배분과 공정성을 다룹니다.
- kyber: 지연 시간 목표에 맞춰 요청을 제어합니다.
장치의 scheduler 파일에 나온 지원 항목과 실제 부하를 기준으로 선택합니다. NVMe·HDD라는 이름만으로 최선의 설정을 확정하지 않습니다.
자주 출제되는 OS 면접 질문
프로세스와 스레드
프로세스와 스레드의 차이는?프로세스는 독립된 메모리 공간(코드, 데이터, 힙, 스택)을 가집니다.
스레드는 같은 프로세스의 코드·데이터·힙을 공유하지만 스택, 레지스터 상태, 스레드 로컬 저장소 등은 따로 가집니다.
프로세스 간 통신은 IPC가 필요하지만, 같은 프로세스의 스레드는 공유 메모리로 바로 통신합니다.
프로세스 생성에는 주소 공간 관리 등이, 스레드 생성에는 새 실행 문맥·스택 등의 준비가 필요합니다. 아래의 COW처럼 실제 복사를 줄이는 구현도 있으므로 비용은 방식과 부하에 따라 달라집니다.
컨텍스트 스위칭이란?CPU가 현재 프로세스/스레드의 실행을 중단하고 다른 것을 실행하는 과정입니다.
현재의 레지스터 상태, PC, 스택 포인터 등을 PCB에 저장하고, 새 프로세스의 상태를 복원합니다.
상태 저장·복원과 스케줄링에 비용이 들고, 작업 집합이 바뀌면 캐시·TLB 적중률도 떨어질 수 있습니다. 전환마다 모든 캐시와 TLB가 무효화되는 것은 아니며 PCID·ASID 같은 태그로 주소 변환을 유지하기도 합니다. 같은 주소 공간을 쓰는 전환은 일부 비용을 줄일 수 있지만 항상 더 빠르다는 보장은 없습니다.
fork()와 exec()는?fork()는 부모의 메모리를 복사한 자식 프로세스를 생성합니다.
COW(Copy-on-Write)로 실제 물리 메모리는 수정 시에만 복사됩니다.
exec()는 현재 프로세스의 주소 공간을 새 프로그램으로 교체합니다.
셸이 명령을 실행할 때 fork() + exec() 조합을 사용합니다.
메모리
가상 메모리란?프로세스의 주소 공간을 물리 메모리 배치와 분리하는 기법입니다. 가상 주소 범위에는 매핑되지 않은 구간도 있을 수 있습니다.
필요한 페이지를 RAM에 두고 파일·스왑 등에서 가져오거나 처음 접근할 때 0으로 채울 수 있습니다. 모든 비상주 페이지가 디스크에 저장된 것은 아닙니다.
페이지 테이블로 가상 주소를 물리 주소로 변환하며, TLB가 이 변환을 캐시합니다.
페이지 폴트란?주소 변환이나 접근 권한 조건을 만족하지 못할 때 발생하는 예외입니다. 비상주 페이지뿐 아니라 COW 쓰기나 보호 위반도 원인이 됩니다.
OS가 매핑을 준비하거나 페이지를 가져와 재개할 수도 있고, 잘못된 접근이면 시그널을 보낼 수도 있습니다. 디스크 I/O 없는 minor fault도 있으므로 모든 폴트가 수 밀리초의 디스크 읽기를 뜻하지는 않습니다.
빈 프레임이 없으면 페이지 교체 알고리즘(LRU 등)으로 교체할 프레임을 결정합니다.
내부 단편화 vs 외부 단편화?내부 단편화: 할당된 블록 내부의 사용되지 않는 공간 (페이징에서 발생).
외부 단편화: 할당된 블록 사이의 남는 공간 (세그멘테이션에서 발생).
일반 페이지 매핑은 물리적으로 연속된 큰 구간을 요구하지 않습니다. 하지만 커널의 연속 물리 메모리 할당이나 huge page에는 여전히 외부 단편화가 문제가 될 수 있으며, 컴팩션도 비용과 이동 제약이 있습니다.
동기화
데드락의 4가지 조건은?상호 배제, 점유와 대기, 비선점, 순환 대기입니다.
네 조건이 동시에 충족되어야 데드락이 발생합니다.
하나라도 깨뜨리면 예방됩니다.
실무에서는 락 순서 규칙과 타임아웃이 가장 많이 사용됩니다.
뮤텍스와 세마포어의 차이는?일반적인 POSIX 뮤텍스는 소유권을 가지며 잠근 스레드가 해제해야 합니다.
세마포어는 카운터로 N개까지 동시 접근을 허용합니다.
소유권 개념이 없어 다른 스레드가 signal할 수 있습니다.
뮤텍스는 락, 세마포어는 신호라고 기억하면 됩니다.
스핀락은 언제 쓰나?임계 영역이 매우 짧아 컨텍스트 스위칭보다 바쁜 대기가 더 빠른 경우에 씁니다.
대기 중에도 락 소유자가 진행할 수 있어야 하며, 코어 수·선점·인터럽트 조건과 대기 길이를 함께 봅니다. 커널의 잠들 수 없는 문맥 등에서 쓰이지만, 구현과 커널 설정에 따른 제약을 확인해야 합니다.
시스템
사용자 모드와 커널 모드의 차이는?사용자 모드에서는 제한된 명령어만 실행 가능합니다.
특권 명령과 자원 접근은 제한되며, 허용된 장치 매핑처럼 OS가 부여한 접근은 가능합니다.
시스템 콜에서는 커널이 요청과 권한을 검사해 작업합니다. 호출한 애플리케이션이 임의의 커널 권한을 얻는 것은 아닙니다. 전환 비용도 CPU·보안 완화 설정과 호출 내용에 따라 달라집니다.
동기와 비동기, 블로킹과 논블로킹의 차이는?비동기 요청에서도 기다리는 호출을 별도로 사용할 수 있습니다.
| 예시 호출 | 반환 시점 | 호출 뒤 확인 |
|---|---|---|
| 블로킹 read | 데이터·EOF·오류 등을 반환할 수 있을 때 | 읽은 바이트 수 또는 오류를 확인 |
| 논블로킹 read | 대기 없이 처리 가능한 범위에서 반환 | 데이터가 없으면 EAGAIN 등. 준비 상태를 기다린 뒤 재시도 가능 |
| aio_read | 비동기 읽기 요청을 제출하고 반환 | aio_error로 상태, 완료 뒤 aio_return으로 결과 확인 |
| aio_suspend | 대상 비동기 요청 중 하나의 완료·시그널·시간 제한까지 대기 | 요청 상태를 검사해 어떤 작업이 완료됐는지 확인 |
- 블로킹 read
- 반환 시점: 데이터·EOF·오류 등을 반환할 수 있을 때호출 뒤 확인: 읽은 바이트 수 또는 오류를 확인
- 논블로킹 read
- 반환 시점: 대기 없이 처리 가능한 범위에서 반환호출 뒤 확인: 데이터가 없으면 EAGAIN 등. 준비 상태를 기다린 뒤 재시도 가능
- aio_read
- 반환 시점: 비동기 읽기 요청을 제출하고 반환호출 뒤 확인: aio_error로 상태, 완료 뒤 aio_return으로 결과 확인
- aio_suspend
- 반환 시점: 대상 비동기 요청 중 하나의 완료·시그널·시간 제한까지 대기호출 뒤 확인: 요청 상태를 검사해 어떤 작업이 완료됐는지 확인
논블로킹 read는 성공 시 데이터를 바로 돌려줍니다. 비동기라는 말은 완료 확인이 반드시 콜백이어야 한다는 뜻이 아닙니다.
동기·비동기는 요청과 완료의 관계를, 블로킹·논블로킹은 특정 호출이 기다리는지를 나눠 설명합니다.
심화 면접 주제
캐시 친화(Cache-friendly) 코드란?CPU가 메모리에 접근할 때 캐시 라인(보통 64바이트) 단위로 가져옵니다.
연속된 메모리에 순서대로 접근하면 캐시 적중률이 높아집니다.
연속 배열은 포인터로 흩어진 연결 리스트보다 공간 지역성을 활용하기 쉽습니다. 아래처럼 C의 연속된 2차원 배열은 행 우선 배치이므로 행 순회가 연속 주소를 읽습니다.
/* 캐시 친화적: 행 우선 순회 (연속 메모리 접근) */
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
sum += matrix[i][j]; /* 인접 원소의 캐시 라인 재사용 기대 */
/* 캐시 비친화적: 열 우선 순회 (점프 접근) */
for (int j = 0; j < N; j++)
for (int i = 0; i < N; i++)
sum += matrix[i][j]; /* 큰 행 간격이면 캐시 미스가 늘 수 있음 */N이 큰 행렬에서 행 우선 순회가 수배 빠를 수 있습니다.
Zero-Copy란?전통적인 read/write 파일 전송은 데이터를 사용자 버퍼로 읽었다가 다시 커널에 전달합니다.
sendfile()은 파일에서 출력 fd로 전달하는 작업을 커널 안에서 처리해 사용자 버퍼 왕복 복사를 줄입니다. 모든 장치에서 물리적 복사가 전혀 없거나 항상 NIC로 직접 전달된다는 뜻은 아닙니다.
Nginx, Kafka 등 고성능 서버가 활용합니다.
epoll vs select?select는 매번 fd 집합을 전달하고, 반환 뒤 호출자가 준비된 항목을 확인합니다.
epoll은 관심 fd를 등록해 두고 준비된 이벤트 목록을 받습니다. 반환할 이벤트 수와 등록·변경 비용도 있으므로 전체 처리를 무조건 O(1)로 표현하지 않습니다.
큰 연결 집합에서 유용하지만 실제 선택은 준비된 fd 비율과 처리 방식에 달려 있습니다. macOS에는 kqueue가 있고 Windows의 IOCP는 준비 상태가 아닌 I/O 완료 통지를 중심으로 합니다.
정리
운영체제는 컴퓨터 과학의 기초이자, 실무 개발의 근간입니다.
프로세스와 스레드를 이해하면 동시성 버그를 잡을 수 있고, 가상 메모리를 이해하면 메모리 문제를 진단할 수 있습니다.
파일 시스템을 알면 데이터를 안전하게 관리하고, 보안 원리를 알면 견고한 시스템을 설계할 수 있습니다.
이 교재에서 다룬 개념들은 특정 기술이 아니라 원리입니다.
언어와 프레임워크는 바뀌어도, OS의 원리는 수십 년 동안 유효했고 앞으로도 유효할 것입니다.