성에 들어가기 전, 신입 기사는 네 가지 답변법을 주머니에 넣었다.
먼저 한 문장으로 정의한다.
비슷한 개념과 차이를 말한다.
어디에 쓰는지 실무 예시를 붙인다.
단점이나 주의점을 하나 말한다.
왕은 말했다.
“외운 단어만 말하면 얇다. 정의, 차이, 예시, 주의점까지 말하면 기초를 이해한 사람처럼 들린다.”
예를 들어 해시 테이블(Hash Table)을 물으면 이렇게 답한다.
“해시 테이블은 key를 hash 함수로 버킷 위치에 매핑해서 평균적으로 빠르게 조회하는 자료구조입니다. 배열처럼 인덱스로 바로 접근하는 것은 아니고,
key를 hashCode로 바꿔 위치를 찾습니다. 평균 조회는 O(1)에 가깝지만, 충돌이 많으면 성능이 나빠질 수 있어 체이닝이나 오픈 어드레싱 같은 충돌 해
결이 필요합니다.”
이 문서의 각 개념은 가능하면 아래 틀로 읽는다.
개념 이름
= 한 문장 정의
= 왜 필요한지
= 비슷한 개념과 차이
= 코드나 실무 예시
= 면접에서 말할 한 문장
예를 들어 Heap을 설명한다면 이렇게 잡는다.
Heap
= 실행 중 동적으로 만든 객체가 사는 공유 메모리 영역
= 객체가 메서드 호출이 끝난 뒤에도 살아야 할 수 있어서 필요함
= Stack은 각 스레드의 메서드 호출 기록이고, Heap은 여러 스레드가 볼 수 있는 객체 창고
= Java의 new User()에서 실제 User 객체가 Heap에 놓임
= "Heap은 객체 창고, Stack은 각 스레드의 작업 메모장입니다."
———
백엔드 왕국(서버 시스템)의 신입 기사는 내일 면접 성(기술 면접장)으로 떠나야 했다. 성문에는 어려운 마법보다 기초를 확인하는 문지기들이 많았다.
첫 번째 문지기는 물었다.
“자료구조 숲을 지나왔는가?”
두 번째 문지기는 물었다.
“네트워크 강이 몇 층으로 흐르는지 아는가?”
세 번째 문지기는 물었다.
“자료형, 트리, 운영체제, DB, Java와 Spring을 말로 풀 수 있는가?”
신입 기사는 두꺼운 책을 모두 외우려 하지 않았다. 대신 왕국 전체의 길을 하나의 이야기로 연결해 머릿속에 넣기로 했다.
———
면접 성의 첫 방은 자기소개 관문(Self Introduction)이었다. 이 방에서는 대단한 수식보다 방향이 중요했다.
신입 기사는 이렇게 말하기로 했다.
“저는 요청이 들어와서 응답이 나가기까지의 전체 흐름을 이해하려고 공부해 온 백엔드 개발자입니다. API, DB, 트랜잭션, 네트워크, 운영 관점까지 연
결해서 안정적으로 동작하는 서비스를 만드는 데 관심이 있습니다. 최근에는 기초 CS를 실제 백엔드 상황과 연결해 설명하는 방식으로 정리하고 있습니
다.”
왕은 조용히 덧붙였다.
“자기소개는 인생 전체를 말하는 시간이 아니다. 면접관이 다음 질문을 던지기 쉽게 길을 놓는 시간이다.”
그래서 기사는 자기소개 끝에 세 개의 갈고리를 남겼다.
API를 만들 때 요청과 응답 계약을 중요하게 본다.
DB는 인덱스, 트랜잭션, 락을 함께 본다.
장애나 성능 문제는 로그, 메트릭, 병목 위치를 먼저 확인한다.
이 갈고리들은 면접관이 “그럼 트랜잭션이 뭔가요?”, “인덱스는 왜 빠른가요?”, “네트워크 흐름을 설명해 보세요”라고 이어가기 좋게 만들었다.
———
자료구조 숲(Data Structure Forest)에는 물건을 담는 방식이 서로 다른 가게들이 있었다.
긴 선반(Array)은 칸 번호(index)를 알면 바로 물건을 찾을 수 있었다. 그래서 조회는 빠르다(O(1)). 하지만 중간에 새 물건을 끼우면 뒤 물건을 밀어야
해서 삽입과 삭제가 느릴 수 있다(O(n)).
사슬 목걸이(Linked List)는 물건들이 서로 다음 위치를 가리키며 이어져 있었다. 원하는 물건까지 따라가야 하므로 조회는 느리다(O(n)). 하지만 이미
위치를 알고 있다면 고리만 바꿔 삽입과 삭제를 할 수 있다.
마법 사전(Hash Table)은 key를 마법 숫자(hash)로 바꿔 버킷(bucket)을 찾았다. 평균적으로 조회, 삽입, 삭제가 빠르다. 하지만 서로 다른 key가 같은
버킷으로 가면 해시 충돌(Hash Collision)이 생긴다. 충돌은 같은 버킷에 줄을 세우는 체이닝(Chaining)이나 다른 빈 버킷을 찾는 오픈 어드레싱(Open
Addressing)으로 해결한다.
접시 더미(Stack)는 마지막에 올린 접시가 먼저 나간다(LIFO). 함수 호출 기록, 뒤로 가기, 괄호 검사에 자주 등장한다.
대기 줄(Queue)은 먼저 온 사람이 먼저 나간다(FIFO). 작업 대기열, BFS, 메시지 큐의 기본 감각과 연결된다.
우선순위 대기 줄(Priority Queue)은 먼저 온 순서가 아니라 중요한 순서대로 나간다. 보통 힙(Heap)으로 구현하며, 가장 작은 값이나 큰 값을 빠르게 꺼
내야 할 때 쓴다.
정리하면 자료구조는 “어디에 저장하느냐”보다 “어떤 연산을 자주 하느냐”로 고른다.
Array
= 번호가 붙은 긴 선반
= index를 알면 바로 찾기 좋음
= 중간에 넣거나 빼면 뒤 물건을 밀어야 함
= 조회가 많고 순서가 중요한 목록에 잘 맞음
LinkedList
= 다음 물건 위치를 적은 사슬
= 중간 노드를 이미 알고 있으면 연결만 바꾸면 됨
= 하지만 그 위치까지 찾아가는 비용이 있음
= "삽입 삭제가 무조건 빠르다"가 아니라 "위치를 이미 알 때 유리하다"가 정확함
HashMap
= key를 hash로 바꿔 버킷을 찾는 사전
= 전체를 훑지 않아도 되어서 get/put이 평균적으로 빠름
= 충돌, load factor, hashCode 품질에 영향을 받음
Java로 감각을 잡으면 이렇다.
List names = new ArrayList<>();
names.add("kim"); // 뒤에 추가
names.get(0); // index 조회
Map<String, Integer> score = new HashMap<>();
score.put("kim", 90); // key로 저장
score.get("kim"); // key로 조회
면접에서 말할 핵심 한 줄은 이렇다.
“자료구조는 조회, 삽입, 삭제, 정렬, 중복 허용 여부 중 무엇이 중요한지에 따라 선택합니다.”
면접관이 “ArrayList와 LinkedList 차이는?”이라고 물으면 기사는 이렇게 답한다.
“ArrayList는 내부 배열 기반이라 인덱스 조회가 빠르지만 중간 삽입과 삭제는 뒤 요소 이동 비용이 있습니다. LinkedList는 노드 연결 기반이라 위치
를 이미 알고 있으면 연결만 바꾸면 되지만, 그 위치를 찾는 비용이 O(n)입니다. 실무에서는 조회가 많은 경우가 흔해서 ArrayList를 더 자주 쓰고,
LinkedList가 항상 삽입 삭제에 유리하다고 단정하면 안 됩니다.”
면접관이 “HashMap은 왜 빠른가요?”라고 물으면 이렇게 답한다.
“key의 hashCode로 버킷 위치를 계산하기 때문에 전체를 처음부터 훑지 않아도 됩니다. 다만 해시 충돌이 많으면 한 버킷 안에서 추가 탐색이 필요합니
다. 그래서 좋은 hashCode 분포와 충돌 해결 방식이 중요합니다.”
왕은 마지막으로 말했다.
“자료구조 질문은 이름 맞히기가 아니다. 어떤 상황에서 무엇을 고르고, 왜 그 선택이 맞는지 말하는 시험이다.”
———
자료구조 숲 안쪽에는 트리 왕의 정원(Tree Garden)이 있었다. 트리는 위에서 아래로 뻗는 계층 구조였다.
가장 위의 노드는 뿌리(Root)였다. 아래로 이어진 노드는 자식(Child), 위쪽 노드는 부모(Parent), 자식이 없는 끝 노드는 잎(Leaf)이었다. 뿌리에서 얼
마나 내려왔는지는 깊이(Depth), 가장 긴 길이는 높이(Height)라고 불렀다.
이진 트리(Binary Tree)는 한 노드가 자식을 최대 두 개까지 가질 수 있는 트리다. 이진 탐색 트리(Binary Search Tree)는 왼쪽에는 작은 값, 오른쪽에는
큰 값을 두는 규칙을 가진다. 규칙이 잘 지켜지고 균형이 잡혀 있으면 탐색이 빠르다(O(log n)). 하지만 한쪽으로 치우치면 연결 리스트처럼 느려질 수
있다(O(n)).
균형 잡힌 나무(Balanced Tree)는 너무 한쪽으로 기울지 않게 스스로 모양을 조정한다. 레드-블랙 트리(Red-Black Tree), AVL 트리가 대표로 언급된다.
DB 금고에서는 B-Tree와 B+Tree가 자주 나온다. B+Tree는 실제 데이터나 데이터 위치가 주로 리프 노드에 모이고, 리프 노드끼리 이어져 범위 검색에 유
리하다. 그래서 DB 인덱스 설명에서 자주 등장한다.
트리 산책법(Tree Traversal)도 있었다.
전위 순회(Preorder): 나를 먼저 보고 왼쪽, 오른쪽으로 간다.
중위 순회(Inorder): 왼쪽을 보고 나를 보고 오른쪽으로 간다. 이진 탐색 트리에서는 정렬된 순서로 볼 수 있다.
후위 순회(Postorder): 자식들을 보고 마지막에 나를 본다.
레벨 순회(Level-order): 위층부터 차례대로 본다. 보통 Queue를 쓴다.
트리를 설명할 때는 “계층”과 “탐색 규칙”을 나눠 말하면 안전하다.
Tree
= 부모와 자식 관계로 이어진 계층 구조
= 파일 시스템, 조직도, HTML DOM처럼 위아래 관계가 있는 데이터를 표현하기 좋음
Binary Search Tree
= 왼쪽은 작은 값, 오른쪽은 큰 값이라는 탐색 규칙이 있는 이진 트리
= 균형이 잡히면 매번 탐색 범위를 줄일 수 있음
= 한쪽으로 기울면 연결 리스트처럼 느려질 수 있음
B-Tree / B+Tree
= DB 인덱스에서 자주 언급되는 트리 계열
= 디스크에서 많은 데이터를 범위로 찾는 상황에 맞게 설계된 구조로 이해하면 됨
면접에서 트리를 말할 때 실수하기 쉬운 부분은 이것이다.
“모든 트리가 빠른 탐색을 보장하는 것은 아닙니다. 빠른 탐색은 이진 탐색 트리처럼 규칙이 있고, 균형까지 어느 정도 유지될 때 기대할 수 있습니
다.”
면접관이 “트리 구조를 설명해 보세요”라고 물으면 기사는 이렇게 답한다.
“트리는 루트에서 시작해 부모-자식 관계로 이어지는 계층형 자료구조입니다. 파일 시스템, 조직도, DOM, DB 인덱스 같은 곳에서 볼 수 있습니다. 이진
탐색 트리는 왼쪽은 작은 값, 오른쪽은 큰 값이라는 규칙으로 탐색을 빠르게 만들지만, 균형이 깨지면 O(n)까지 나빠질 수 있어 균형 트리가 중요합니
다.”
———
면접 성 밖에는 네트워크 강(Network River)이 흘렀다. 강은 한 층이 아니라 여러 층으로 흘렀다.
왕은 일곱 층 지도(OSI 7 Layer)를 펼쳤다.
물리 계층(Physical): 전기 신호, 케이블, 무선 신호의 길
데이터링크 계층(Data Link): MAC 주소, 프레임의 길
네트워크 계층(Network): IP 주소와 라우팅의 길
전송 계층(Transport): TCP, UDP, 포트의 길
세션 계층(Session): 대화 연결 관리의 길
표현 계층(Presentation): 인코딩, 암호화, 압축의 길
응용 계층(Application): HTTP, DNS 같은 서비스의 길
실무에서는 네 층 지도(TCP/IP 4 Layer)로 단순화해서 말하기도 한다.
네트워크 인터페이스 계층
인터넷 계층
전송 계층
응용 계층
등기 우편 배(TCP)는 연결을 맺고, 순서를 맞추고, 손실되면 다시 보낸다. 일반 엽서 배(UDP)는 연결과 순서 보장을 줄이고 빠르게 보낸다.
TCP 연결은 세 번 인사(3-way handshake)로 열린다.
Client -> Server: SYN
Server -> Client: SYN + ACK
Client -> Server: ACK
HTTPS 성문은 HTTP에 TLS 갑옷을 입힌 길이다. 처음에는 인증서와 암호화 방식으로 안전한 대화 준비를 하고, 이후에는 암호화된 통신을 한다.
네트워크 계층을 나누는 이유는 각 층이 자기 역할만 책임지게 하기 위해서다.
응용 계층
= HTTP처럼 "무슨 요청을 보낼지" 정함
전송 계층
= TCP/UDP처럼 "어느 프로그램의 어느 포트로 어떻게 보낼지" 정함
네트워크 계층
= IP처럼 "어느 컴퓨터로 갈지" 정함
데이터링크/물리 계층
= 같은 네트워크 안에서 실제 신호와 프레임을 보냄
TCP와 UDP는 이렇게 구분한다.
TCP
= 등기 우편
= 연결을 맺고, 순서와 재전송을 챙김
= 정확성이 중요한 HTTP, DB 연결 같은 곳에 어울림
UDP
= 빠른 엽서
= 연결과 재전송 보장을 줄여 가볍게 보냄
= 실시간성이나 단순 질의가 중요한 상황에서 쓰일 수 있음
면접에서 말할 핵심 한 줄은 이렇다.
“네트워크는 HTTP만 보는 것이 아니라, DNS로 주소를 찾고 TCP/TLS로 통신 준비를 한 뒤 HTTP 요청을 보내는 전체 흐름으로 설명하는 게 좋습니다.”
면접관이 “브라우저에 URL을 입력하면 무슨 일이 일어나나요?”라고 물으면 기사는 길을 그린다.
브라우저
-> DNS로 도메인을 IP로 변환
-> 서버와 TCP 연결
-> HTTPS라면 TLS Handshake
-> HTTP 요청 전송
-> 로드밸런서나 웹 서버를 거쳐 애플리케이션 서버 도착
-> 서버가 DB, Cache, 외부 API 등을 사용
-> HTTP 응답 반환
-> 브라우저가 HTML, CSS, JS를 해석하고 화면 렌더링
면접관이 “L4와 L7 로드밸런서 차이는?”이라고 물으면 이렇게 답한다.
“L4는 전송 계층 정보인 IP와 포트 중심으로 트래픽을 분산하고, L7은 HTTP URL, Header, Host 같은 응용 계층 정보를 보고 더 세밀하게 라우팅할 수
있습니다. L7은 기능이 풍부하지만 그만큼 처리 비용과 설정 복잡도가 생길 수 있습니다.”
왕은 말했다.
“네트워크 질문은 층 이름만 외우는 게 아니다. 요청 하나가 어떤 길을 지나 서버까지 오는지 말할 수 있어야 한다.”
———
운영체제 성(OS Castle)에는 독립된 방을 가진 주민(프로세스)과 같은 방에서 일하는 룸메이트(스레드)가 있었다.
프로세스(Process)는 실행 중인 프로그램의 독립된 단위다. 자기 메모리 공간을 가진다. 스레드(Thread)는 프로세스 안에서 실행되는 작업 흐름이다. 같
은 프로세스의 스레드들은 Code, Data, Heap 영역을 공유하고 Stack은 각자 가진다.
여기서 Code, Data, Heap, Stack은 이렇게 잡으면 된다.
Code
= 실행할 명령어, 함수, 메서드 코드
= 같은 프로그램을 실행하므로 스레드마다 코드를 복사할 필요가 없음
Data
= 전역 변수, static 변수처럼 프로그램 실행 동안 유지되는 공유 데이터
= new로 만든 객체라기보다 "프로그램 전체에서 공유되는 이름 붙은 값"에 가까움
Heap
= 실행 중 동적으로 만든 객체와 배열이 사는 공간
= Java라면 new User()의 실제 User 객체가 사는 곳으로 이해하면 됨
Stack
= 각 스레드의 메서드 호출 기록과 지역 변수가 쌓이는 공간
= 스레드마다 실행 위치가 다르므로 각자 따로 가져야 함
회사 비유로 보면 더 쉽다.
프로세스 = 한 회사 사무실
스레드 = 그 안에서 동시에 일하는 직원들
Code = 회사 업무 매뉴얼
Data = 회사 공용 게시판에 붙은 전역/static 정보
Heap = 직원들이 함께 쓰는 공용 창고의 객체들
Stack = 각 직원이 들고 다니는 개인 메모장
C 스타일 예시로 보면 Data가 가장 선명하다.
int globalCount = 10; // Data 영역
static int staticCount = 20; // Data 영역
void work() {
int localCount = 1; // Stack 영역
User* user = malloc(...); // 실제 User 데이터는 Heap 영역
}
Java로 감각만 대응하면 이렇다.
class Counter {
static int total = 0; // Data 영역 같은 성격: 클래스 단위 공유 상태
int count; // 객체 필드: 객체가 Heap에 있으면 이것도 Heap 쪽
void work() {
int local = 1; // Stack
User user = new User(); // user 참조는 Stack, 실제 User 객체는 Heap
}
}
단, Java/JVM의 메모리 구조는 운영체제의 Code/Data/Heap/Stack과 1:1로 완전히 같지는 않다. 면접에서 운영체제 메모리 구조를 물으면 Data = 전역/
static 성격의 공유 데이터로 답하고, Java 기준으로 묻는다면 static 필드, 클래스 정보, 메서드 정보는 JVM의 Method Area 쪽 설명과 연결해서 말하면
안전하다.
핵심 한 줄은 이렇다.
“Heap은 같이 일하려고 공유하고, Stack은 각 스레드가 자기 실행 흐름을 유지하려고 따로 가집니다.”
CPU 기사단은 여러 일을 번갈아 처리했다. 한 일을 멈추고 다른 일을 시작할 때 현재 상태를 저장하고 다음 상태를 불러오는 일을 작업 교대(Context
Switching)라고 한다. 너무 잦은 작업 교대는 비용이 된다.
성문에는 하나뿐인 열쇠(Mutex)와 여러 장 입장권(Semaphore)이 있었다.
뮤텍스(Mutex)는 한 번에 하나의 스레드만 임계 구역에 들어가게 한다. 세마포어(Semaphore)는 정해진 개수만큼 여러 스레드가 들어가게 할 수 있다.
어느 날 네 기사가 서로 열쇠를 쥔 채 멈췄다. 이것이 교착 상태(Deadlock)였다. 데드락은 보통 네 조건이 함께 있을 때 생긴다.
상호 배제: 자원을 한 번에 하나만 쓴다.
점유 대기: 이미 가진 자원을 놓지 않고 다른 자원을 기다린다.
비선점: 남이 가진 자원을 강제로 빼앗을 수 없다.
순환 대기: 서로가 서로를 원형으로 기다린다.
면접관이 “프로세스와 스레드 차이는?”이라고 물으면 기사는 이렇게 답한다.
“프로세스는 독립된 메모리 공간을 가진 실행 단위이고, 스레드는 프로세스 내부의 실행 흐름입니다. 같은 프로세스의 스레드들은 Heap 같은 메모리를
공유하기 때문에 통신은 쉽지만 동시 접근 문제가 생길 수 있습니다. 그래서 동기화와 락 관리가 중요합니다.”
———
자료형 대장간(Type Forge)에서는 값의 모양과 저장 방식을 정했다.
기본형(Primitive Type)은 값 자체를 다루는 작은 쇳조각이었다. Java에서는 int, long, boolean, double 같은 것이 기본형이다.
참조형(Reference Type)은 실제 물건이 있는 위치를 가리키는 지도였다. Java의 객체, 배열, String은 참조형이다.
기사는 ==와 equals()의 차이도 챙겼다.
==는 기본형에서는 값 자체를 비교한다. 객체에서는 같은 객체를 가리키는지, 즉 참조를 비교한다. equals()는 기본 구현은 참조 비교와 비슷하지만,
String처럼 논리적 값 비교로 재정의해서 쓰는 경우가 많다.
HashMap 시장에서는 equals()와 hashCode() 계약이 중요했다. 두 객체가 equals()로 같다면 hashCode()도 같아야 한다. 그래야 같은 key로 제대로 찾을
수 있다.
자료형은 “값을 직접 들고 있느냐, 위치를 들고 있느냐”로 나눠 보면 쉽다.
기본형
= 값 자체를 다루는 타입
= int a = 10 이라면 a는 숫자 10이라는 값으로 이해하면 됨
참조형
= 객체가 있는 위치를 가리키는 타입
= User user = new User()에서 user는 객체 자체가 아니라 객체를 가리키는 참조
Java에서 이 차이는 비교할 때 바로 드러난다.
int a = 10;
int b = 10;
System.out.println(a == b); // true, 값 비교
User u1 = new User("kim");
User u2 = new User("kim");
System.out.println(u1 == u2); // false일 수 있음, 참조 비교
System.out.println(u1.equals(u2)); // equals 구현에 따라 논리 비교
면접에서 말할 핵심 한 줄은 이렇다.
“기본형은 값 자체, 참조형은 객체 위치를 다루며, 객체 비교에서는 ==와 equals()의 의미를 구분해야 합니다.”
면접관이 “자료형을 설명해 보세요”라고 물으면 기사는 범위를 먼저 좁힌다.
“Java 기준으로 말씀드리면, 자료형은 크게 기본형과 참조형으로 나눌 수 있습니다. 기본형은 값 자체를 저장하고, 참조형은 객체 위치를 참조합니다.
그래서 객체 비교에서는 ==가 같은 참조인지 보는 반면, equals()는 논리적 동등성을 표현하도록 재정의할 수 있습니다.”
만약 JavaScript 기준으로 물으면 이렇게 답한다.
“JavaScript에서는 원시값과 객체를 나눠 볼 수 있습니다. 문자열, 숫자, boolean, null, undefined, symbol, bigint 같은 원시값은 불변 값으로 다뤄
지고, 객체와 배열, 함수는 참조를 통해 다룹니다. 그래서 객체 비교는 내용이 같아 보여도 같은 참조가 아니면 다르게 판단될 수 있습니다.”
왕은 말했다.
“자료형 질문은 언어 기준을 먼저 확인하면 안전하다. Java 기준인지, JavaScript 기준인지 말하고 답하면 흔들림이 줄어든다.”
———
데이터베이스 금고(DB Vault)에는 네 가지 법(ACID)이 있었다.
원자성(Atomicity)은 거래가 전부 성공하거나 전부 실패해야 한다는 법이다. 일관성(Consistency)은 거래 전후에 DB 규칙이 깨지지 않아야 한다는 법이
다. 격리성(Isolation)은 동시에 실행되는 거래가 서로 이상하게 끼어들지 않게 하는 법이다. 지속성(Durability)은 커밋된 결과가 사라지지 않아야 한다
는 법이다.
금고지기는 빠른 조회를 위해 책 뒤 찾아보기(Index)를 만들었다. 인덱스는 조건에 맞는 데이터를 빨리 찾게 해 주지만, 쓰기 작업에서는 인덱스도 함께
갱신해야 하므로 비용이 늘 수 있다.
트랜잭션 격리 수준(Isolation Level)은 손님들이 서로의 거래를 얼마나 볼 수 있는지 정한다. 낮으면 동시성은 좋아지지만 이상 현상이 생길 수 있고,
높으면 안전하지만 대기와 잠금 비용이 커질 수 있다.
JPA 마을에서는 N+1 문제가 자주 나타났다. 목록을 한 번 가져온 뒤, 각 항목의 연관 데이터를 가져오려고 N번 더 쿼리하는 문제다. Fetch Join, Batch
Size, EntityGraph 같은 방법으로 줄일 수 있다.
DB 질문은 “정합성”과 “속도”를 나눠 말하면 답이 정리된다.
Transaction
= 여러 DB 작업을 하나의 논리적 작업 단위로 묶는 것
= 중간에 실패하면 일부만 반영되는 사고를 막기 위해 필요함
= commit은 확정, rollback은 되돌리기
Index
= 책 뒤의 찾아보기
= 조건에 맞는 row를 빨리 찾기 위해 필요함
= 대신 쓰기 작업 때 인덱스도 같이 관리해야 하므로 비용이 생김
Isolation Level
= 동시에 실행되는 트랜잭션이 서로를 얼마나 볼 수 있는지 정하는 규칙
= 강하게 잡으면 안전하지만 대기와 충돌 비용이 늘 수 있음
예를 들어 주문 생성에서는 이렇게 연결해서 말할 수 있다.
주문 저장
-> 주문 row insert
-> 주문 상품 row insert
-> 재고 차감 update
-> 결제 기록 insert
이 중 하나라도 실패하면 전체 rollback이 필요하다.
그래서 이 흐름은 하나의 Transaction으로 묶는 것이 자연스럽다.
면접에서 말할 핵심 한 줄은 이렇다.
“DB는 빠르게 찾는 인덱스와, 틀어지지 않게 묶는 트랜잭션을 같이 봐야 합니다.”
면접관이 “인덱스는 왜 쓰나요?”라고 물으면 이렇게 답한다.
“인덱스는 테이블 전체를 매번 훑는 비용을 줄이기 위해 사용합니다. 보통 B-Tree나 B+Tree 계열 구조로 정렬된 탐색 경로를 만들어 조건 검색과 범위
검색을 빠르게 합니다. 다만 insert, update, delete 때 인덱스 유지 비용이 생기므로 많이 만든다고 항상 좋은 것은 아닙니다.”
면접관이 “트랜잭션이 뭔가요?”라고 물으면 이렇게 답한다.
“트랜잭션은 여러 DB 작업을 하나의 논리적 작업 단위로 묶는 것입니다. 중간에 실패하면 롤백하고, 모두 성공하면 커밋해서 데이터 정합성을 지킵니
다. 대표 성질은 ACID이고, 동시성 상황에서는 격리 수준과 락까지 함께 고려해야 합니다.”
———
Java 마법탑(JVM Tower)에는 객체 창고(Heap)와 호출 기록장(Stack)이 있었다. 객체는 주로 Heap에 살고, 메서드 호출과 지역 변수 정보는 각 스레드의
Stack에 쌓인다.
청소부(GC)는 더 이상 참조되지 않는 객체를 치운다. 새 객체가 많이 생기고 금방 사라지는 특성을 고려해 Young Generation과 Old Generation으로 나눠
관리하는 방식이 자주 설명된다.
문자 두루마리(String)는 불변(Immutable)이다. 문자열을 자주 이어 붙이면 새 객체가 많이 생길 수 있으므로 반복적인 조합에는 StringBuilder를 쓴다.
Spring 마을에서는 객체를 직접 만들기보다 관리소(IoC Container)가 Bean을 만들고 필요한 의존성을 넣어 준다. 이것이 DI(Dependency Injection)다.
거래소 직원(@Transactional)은 프록시(Proxy)로 일한다. 외부에서 메서드가 호출될 때 트랜잭션을 열고, 성공하면 커밋하고, 예외 상황에 따라 롤백한
다. 같은 클래스 내부에서 자기 메서드를 직접 부르는 self-invocation은 프록시를 거치지 않아 트랜잭션이 기대대로 적용되지 않을 수 있다.
Java와 Spring은 “JVM이 어떻게 실행하느냐”와 “Spring이 객체를 어떻게 관리하느냐”를 나눠 말하면 좋다.
JVM Heap
= 모든 JVM 스레드가 공유하는 객체/배열 공간
= new로 만든 객체가 주로 여기 놓임
JVM Stack
= 각 스레드마다 있는 메서드 호출 기록
= 지역 변수, 중간 계산, 호출 흐름이 쌓임
Method Area
= 클래스 단위 정보, 메서드 코드, static 필드 정보와 연결되는 공유 영역
= OS의 Code/Data 설명과 Java 설명을 이어 줄 때 언급하면 좋음
Java 코드로 보면 이렇게 잡는다.
public void join() {
int age = 30; // 현재 스레드 Stack의 지역 변수
Member member = new Member(); // member 참조는 Stack, 실제 객체는 Heap
}
Spring은 객체 생성을 직접 new로 흩뿌리지 않고 컨테이너가 관리한다.
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
}
이 코드에서 OrderService가 OrderRepository 구현체를 직접 만들지 않는다. Spring Container가 필요한 Bean을 찾아 생성자에 넣어 준다. 그래서 테스트
에서는 가짜 Repository를 넣거나, 구현체를 바꾸는 일이 쉬워진다.
@Transactional은 특히 “프록시를 거치느냐”가 핵심이다.
Controller -> OrderService 프록시 -> 실제 OrderService
-> 트랜잭션 시작
-> 실제 메서드 실행
-> commit 또는 rollback
면접에서 말할 핵심 한 줄은 이렇다.
“Spring의 DI는 객체 생성과 연결 책임을 컨테이너로 옮기는 것이고, @Transactional은 보통 프록시가 메서드 앞뒤에 트랜잭션 처리를 붙이는 방식입니
다.”
면접관이 “Spring의 DI가 뭔가요?”라고 물으면 이렇게 답한다.
“DI는 객체가 필요한 의존 객체를 직접 생성하지 않고 외부에서 주입받는 방식입니다. Spring에서는 IoC Container가 Bean을 생성하고 의존성을 연결해
줍니다. 이렇게 하면 구현체 교체와 테스트가 쉬워지고 객체 간 결합도를 낮출 수 있습니다.”
면접관이 “@Transactional은 어떻게 동작하나요?”라고 물으면 이렇게 답한다.
“Spring의 선언적 트랜잭션은 보통 프록시 기반 AOP로 동작합니다. 프록시가 메서드 호출 앞뒤에서 트랜잭션을 시작하고 커밋 또는 롤백합니다. 그래서
같은 클래스 내부 호출처럼 프록시를 거치지 않는 경우에는 적용되지 않을 수 있다는 점을 주의해야 합니다.”
———
면접 성 한쪽에는 JavaScript와 React 다리(JS/React Bridge)가 있었다. 백엔드 기사는 이 다리를 깊게 건너지는 않더라도, 기본 표지판은 읽을 수 있어
야 했다.
JavaScript 마을에는 원시값(Primitive)과 객체(Object)가 있었다. 원시값은 값 자체처럼 다뤄지고, 객체는 참조로 다뤄진다. 함수도 객체처럼 다룰 수
있어 변수에 담고 인자로 넘길 수 있다.
비동기 전령(Promise)은 지금 당장 결과가 없지만 나중에 성공하거나 실패할 일을 표현한다. async/await는 Promise 흐름을 동기 코드처럼 읽기 쉽게 써
주는 문법이다.
이벤트 루프(Event Loop)는 호출 스택이 비었을 때 대기 중인 작업을 꺼내 실행 흐름으로 올린다. 그래서 JavaScript는 단일 스레드 실행 모델을 기본으
로 설명하지만, 비동기 I/O와 이벤트 루프를 통해 많은 작업을 처리할 수 있다.
React 마을에는 입력 상자(props)와 내부 기억(state)이 있었다. props는 부모가 자식에게 내려주는 값이고, state는 컴포넌트가 기억하는 값이다. state
가 바뀌면 React는 화면을 다시 계산해 필요한 부분을 반영한다.
열쇠표(key)는 목록에서 각 항목의 정체성을 알려준다. key가 불안정하면 React가 어떤 항목이 바뀌었는지 헷갈릴 수 있다.
JavaScript는 “값이냐 참조냐”와 “지금 끝나느냐 나중에 끝나느냐”를 먼저 잡으면 된다.
Primitive
= 문자열, 숫자, boolean 같은 원시값
= 값 자체처럼 비교하고 다룸
Object
= 객체, 배열, 함수
= 내용이 같아 보여도 같은 참조가 아니면 다른 객체
Promise
= 비동기 작업의 미래 결과
= 지금 값이 없지만 나중에 성공 또는 실패로 끝남
async/await
= Promise 흐름을 읽기 쉽게 쓰는 문법
= 비동기 작업을 동기 코드처럼 보이게 정리함
JavaScript 예시로 보면 이렇다.
const a = { name: "kim" };
const b = { name: "kim" };
console.log(a === b); // false, 내용이 같아 보여도 참조가 다름
async function loadUser() {
const response = await fetch("/users/1");
return response.json();
}
React는 “부모가 내려주는 값”과 “컴포넌트가 기억하는 값”을 나누면 된다.
props
= 부모가 자식에게 내려주는 입력값
= 함수의 인자처럼 생각하면 쉬움
state
= 컴포넌트가 기억하는 내부 값
= 바뀌면 다시 렌더링될 수 있음
key
= 목록에서 항목을 구분하는 안정적인 이름표
= React가 어떤 항목이 추가, 삭제, 변경됐는지 판단하는 데 도움
React 예시는 이렇다.
function UserRow({ user }) { // user는 props
return
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<UserRow key={user.id} user={user} />
))}
</ul>
);
}
면접에서 말할 핵심 한 줄은 이렇다.
“JavaScript 객체는 참조로 비교되고, React에서는 props는 부모 입력, state는 내부 기억, key는 목록 항목의 정체성입니다.”
면접관이 “React에서 props와 state 차이는?”이라고 물으면 이렇게 답한다.
“props는 부모 컴포넌트가 자식에게 전달하는 입력값이고, state는 컴포넌트 내부에서 관리하는 값입니다. props는 자식 입장에서는 직접 바꾸는 값이
아니라 전달받는 값이고, state는 변경되면 렌더링 결과에 영향을 줄 수 있습니다.”
면접관이 “async/await가 뭔가요?”라고 물으면 이렇게 답한다.
“async/await는 Promise 기반 비동기 코드를 더 읽기 쉽게 작성하는 문법입니다. await는 Promise가 처리될 때까지 이후 흐름을 기다리는 것처럼 표현
하지만, 전체 JavaScript 런타임을 전부 멈춘다는 뜻은 아닙니다. 실패 처리는 try/catch로 다룰 수 있습니다.”
———
면접 성의 마지막 방에는 비즈니스 나침반(Business Compass)이 있었다. 기술만 아는 기사는 “이걸 만들 수 있습니다”라고 말했지만, 나침반을 가진 기사
는 “왜 이걸 만들어야 하고, 어떤 비용과 효과가 있는지 보겠습니다”라고 말했다.
비즈니스 관점은 거창한 말이 아니었다. 네 가지 질문으로 시작했다.
이 기능은 누구의 어떤 문제를 줄이는가?
성공했는지 어떤 지표로 볼 수 있는가?
기술 선택의 비용은 무엇인가?
빠르게 만들 것과 안정적으로 만들 것 사이의 균형은 어디인가?
기술 답변을 비즈니스 답변으로 바꾸는 틀은 이렇다.
기술 선택
= 무엇을 쓸 것인가
사용자 문제
= 왜 필요한가
운영 비용
= 어떤 관리 비용이 생기는가
위험
= 장애, 정합성, 보안, 비용 중 무엇을 조심해야 하는가
지표
= 좋아졌는지 무엇으로 확인할 것인가
예를 들어 캐시(Cache)를 붙이는 일도 비즈니스 나침반으로 보면 달라진다.
단순한 기술 답변은 이렇다.
“Redis를 붙이면 빨라집니다.”
나침반을 든 답변은 이렇다.
“조회가 많고 원본 DB 부하가 큰 화면이라면 Redis 캐시로 응답 시간을 줄일 수 있습니다. 다만 원본 데이터 변경 시 캐시 무효화 전략이 필요하고, 틀
린 값을 오래 보여주면 사용자 신뢰가 떨어질 수 있습니다. 그래서 TTL, Cache-Aside, 변경 시 삭제 같은 정책을 같이 설계하겠습니다.”
면접관이 “비즈니스를 바라보는 관점이 있나요?”라고 물으면 기사는 이렇게 답한다.
“기능을 구현할 때 단순히 동작 여부만 보지 않고, 사용자 문제, 운영 비용, 장애 가능성, 지표를 함께 보려고 합니다. 예를 들어 성능 개선도 빠르게
만드는 것에서 끝내지 않고, 어떤 화면의 이탈이나 대기 시간을 줄이는지, 캐시 정합성이나 장애 시 대체 흐름은 있는지까지 같이 보겠습니다.”
다른 예시도 같은 틀로 말할 수 있다.
검색 API가 느리다
-> 기술 선택: 인덱스 추가, 쿼리 개선, 캐시 검토
-> 사용자 문제: 검색 결과 대기 시간이 길어짐
-> 운영 비용: 인덱스 추가 시 쓰기 비용과 저장 공간 증가
-> 위험: 캐시를 쓰면 오래된 결과를 보여줄 수 있음
-> 지표: 평균/상위 응답 시간, DB 부하, 검색 이탈률
왕은 고개를 끄덕였다.
“비즈니스 관점은 개발을 덜 기술적으로 말하는 게 아니다. 기술 선택의 이유와 결과를 더 넓게 설명하는 것이다.”
———
면접 성에서는 모르는 질문도 나온다. 신입 기사는 모르는 것을 숨기려고 하지 않기로 했다.
나쁜 답변은 성급한 추측이다.
“그건 아마 이런 것 같습니다.”
좋은 답변은 경계를 긋고 아는 부분부터 말한다.
“정확한 세부 구현은 지금 바로 단정하기 어렵습니다. 다만 제가 이해한 범위에서는 이 개념이 이런 문제를 해결하기 위한 것이고, 비슷한 개념과는 이
부분이 다릅니다. 실제 적용 시에는 공식 문서나 구현체 기준으로 확인하겠습니다.”
자료구조를 모르면 이렇게 말한다.
“정확한 구현 세부는 부족하지만, 이 자료구조가 어떤 연산을 빠르게 하려는지는 먼저 보겠습니다. 조회, 삽입, 삭제 중 무엇이 중요한지에 따라 배열,
해시, 트리 계열 선택이 달라집니다.”
네트워크를 모르면 이렇게 말한다.
“세부 패킷 수준까지는 더 확인이 필요합니다. 대신 요청 흐름은 DNS, TCP 연결, TLS, HTTP 요청, 서버 처리, 응답 순서로 설명할 수 있습니다.”
Spring을 모르면 이렇게 말한다.
“Spring 내부 구현은 더 확인해 봐야 하지만, 제가 이해한 핵심은 컨테이너가 Bean을 관리하고 프록시나 필터 체인 같은 구조로 공통 관심사를 처리한
다는 점입니다.”
왕은 말했다.
“모르는 걸 아는 척하면 한 번에 무너진다. 경계를 말하고, 아는 구조를 차분히 설명하면 다시 질문을 이어갈 수 있다.”
———
면접 성 앞에서 기사는 작은 카드를 꺼냈다.
Array: 인덱스 조회 빠름, 중간 삽입 삭제는 이동 비용.
LinkedList: 노드 연결, 위치 탐색은 느림, 위치를 알면 연결 변경.
HashMap: hash로 버킷 찾기, 평균 O(1), 충돌 주의.
Stack: LIFO, 함수 호출과 괄호 검사.
Queue: FIFO, BFS와 작업 대기열.
Tree: 계층 구조, 균형이 중요.
Heap: 우선순위 큐 구현에 자주 사용.
TCP: 연결 기반, 순서와 신뢰성 보장.
UDP: 가볍고 빠르지만 순서와 도착 보장 없음.
HTTP: 요청과 응답의 응용 계층 프로토콜.
HTTPS: HTTP에 TLS 암호화 적용.
DNS: 도메인을 IP로 변환.
L4: IP/Port 기반 분산.
L7: HTTP 정보 기반 분산.
Process: 독립 메모리 공간.
Thread: 프로세스 안 실행 흐름, 메모리 일부 공유.
Context Switching: 실행 상태 저장 후 다른 작업으로 교체.
Mutex: 하나만 들어가는 잠금.
Semaphore: 정해진 수만큼 들어가는 잠금.
Deadlock: 상호 배제, 점유 대기, 비선점, 순환 대기.
Transaction: 여러 작업을 하나로 묶는 논리 단위.
ACID: 원자성, 일관성, 격리성, 지속성.
Index: 조회 빠르게, 쓰기 비용 증가.
Isolation Level: 동시 거래 간 보이는 범위 조절.
N+1: 목록 1번 + 연관 조회 N번 문제.
JVM Heap: 객체 저장.
Stack: 메서드 호출과 지역 변수.
GC: 참조되지 않는 객체 정리.
equals/hashCode: 동등하면 hashCode도 같아야 함.
DI: 의존성을 외부에서 주입.
@Transactional: 프록시 기반 트랜잭션 처리.
Primitive: 값처럼 다루는 원시값.
Object: 참조로 다루는 객체.
Promise: 미래의 성공 또는 실패 결과.
async/await: Promise 흐름을 읽기 쉽게 표현.
props: 부모가 내려주는 입력.
state: 컴포넌트 내부 상태.
key: 목록 항목의 정체성.
———
이 문서는 이동 중 암기와 면접 답변 흐름을 위해 쉽게 풀어 쓴 문서다. 아래 내용은 공식 문서에서 확인 가능한 기준으로 맞췄다.
Linux pthreads(7) (https://man7.org/linux/man-pages/man7/pthreads.7.html): 한 프로세스의 스레드들은 같은 data와 heap을 공유하고, 각 스레드는
자기 stack을 가진다.
JVM Specification (https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-2.html): JVM Heap은 모든 JVM 스레드가 공유하며, 클래스 인스턴스와
배열이 할당되는 런타임 데이터 영역이다. JVM Method Area도 모든 JVM 스레드가 공유한다.
Oracle Java HashMap API (https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashMap.html): HashMap은 hash가 버킷에 잘
분산된다는 가정에서 get, put 기본 연산에 constant-time 성능을 제공한다고 설명한다.
Oracle Java List API (https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/List.html): List의 index 접근은 구현체에 따라
index 값에 비례하는 시간이 걸릴 수 있으며, LinkedList가 예시로 언급된다.
Oracle Java PriorityQueue API (https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/PriorityQueue.html): PriorityQueue는
priority heap 기반이며 offer, poll, add, remove()가 O(log n)이라고 설명한다.
PostgreSQL Transaction Isolation (https://www.postgresql.org/docs/current/transaction-iso.html): 표준 트랜잭션 격리 수준을 설명하며, 구현체별
세부 동작은 DB마다 다를 수 있다.
Spring Framework @Transactional (https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html):
기본 proxy mode에서는 프록시를 통해 들어오는 외부 메서드 호출만 가로채며, self-invocation은 @Transactional 메서드여도 실제 트랜잭션으로
이어지지 않을 수 있다.
MDN JavaScript data types (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Data_structures), MDN Promise
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), MDN execution model
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model): JavaScript의 built-in data type과 Promise, event loop
모델을 설명한다.
React Rendering Lists (https://react.dev/learn/rendering-lists), React Thinking in React (https://react.dev/learn/thinking-in-react), React
State (https://react.dev/learn/state-a-components-memory): list item에는 같은 배열 안에서 항목을 구분하는 key가 필요하다. React 학습
문서에서는 props를 부모에서 자식으로 전달하는 데이터, state를 시간이 지나며 바뀌는 데이터로 설명한다.
면접에서는 위 공식 설명을 그대로 길게 외우기보다, 이 문서의 템플릿처럼 정의 -> 이유 -> 예시 -> 주의점 순서로 짧게 말하는 것이 좋다.
———
면접 성문 앞에서 신입 기사는 마지막으로 중얼거렸다.
“기초 CS는 따로 떨어진 암기 과목이 아니다. 사용자가 요청을 보내면 네트워크를 지나 서버에 도착하고, 스레드가 일을 맡고, 자료구조와 알고리즘이
데이터를 다루고, DB가 정합성을 지키고, Spring이 객체와 트랜잭션을 관리하고, 다시 응답이 돌아가는 하나의 길이다.”
그리고 그는 문을 열었다.
외운 정답을 보여주러 가는 것이 아니라, 길을 이해하고 있다는 것을 말하러 가는 것이었다.