들어가며
"면접 준비를 어디서부터 해야 할지 모르겠어요." 이직을 준비하는 3~7년차 백엔드 개발자들이 가장 많이 하는 고민입니다. CS 전공 지식부터 Spring, JPA, DB, 인프라까지 범위가 너무 넓고, 어디까지 깊게 공부해야 하는지 감이 잡히지 않습니다. 특히 미드 레벨 이상에서는 단순 암기가 아니라 "왜"와 "어떻게"를 설명할 수 있어야 합니다.
이 글에서는 실제 면접에서 자주 출제되는 50개 핵심 질문을 카테고리별로 정리하고, 각 답변을 "결론 → 이유 → 예시" 3단 구조로 구성했습니다. 면접관이 기대하는 답변 포인트와 이어질 수 있는 꼬리 질문도 함께 다루겠습니다.
Java 핵심 (10문)
Q1. JVM 메모리 구조를 설명해주세요.
결론: JVM 메모리는 크게 Heap, Stack, Method Area, PC Register, Native Method Stack으로 나뉩니다.
이유: 각 영역이 서로 다른 데이터의 생명주기를 관리하기 위해 분리되어 있습니다.
- Heap: 객체 인스턴스 저장. Young Generation(Eden, Survivor)과 Old Generation으로 나뉨. GC 대상.
- Stack: 스레드별 생성. 지역 변수, 메서드 호출 프레임 저장. LIFO 구조.
- Method Area (Metaspace): 클래스 메타데이터, static 변수, 상수 풀 저장. Java 8 이후 네이티브 메모리 사용.
꼬리 질문: Young Generation과 Old Generation의 GC 차이, G1GC vs ZGC 비교
Q2. GC(Garbage Collection)의 동작 원리를 설명해주세요.
결론: GC는 참조되지 않는(Unreachable) 객체를 자동으로 메모리에서 해제하는 메커니즘입니다.
이유: 수동 메모리 관리의 버그(메모리 릭, Double Free)를 방지합니다.
// GC 동작 과정
// 1. Mark: GC Root(스택 변수, static 변수 등)에서 시작하여 도달 가능한 객체를 표시
// 2. Sweep: 표시되지 않은 객체의 메모리를 해제
// 3. Compact(선택): 메모리 단편화 방지를 위해 살아있는 객체를 한쪽으로 이동
// GC Root가 되는 것들:
// - 스택의 지역 변수
// - static 변수
// - JNI 레퍼런스
// - 활성 스레드
꼬리 질문: Stop-the-World란? GC 튜닝 경험이 있다면? -Xms, -Xmx 차이
Q3. HashMap의 내부 동작 원리를 설명해주세요.
결론: HashMap은 배열(버킷) + 연결 리스트(또는 Red-Black Tree)로 구현된 해시 테이블입니다.
// put(key, value) 동작:
// 1. key.hashCode() 호출
// 2. hash값을 배열 크기로 모듈러 연산 → 버킷 인덱스
// 3. 해당 버킷이 비어있으면 삽입
// 4. 충돌 시 → equals()로 동일 키 확인
// - 동일 키: 값 업데이트
// - 다른 키: 연결 리스트에 추가
// 5. 연결 리스트 길이가 8 이상이면 Red-Black Tree로 변환 (Java 8+)
// 6. 전체 요소가 capacity * loadFactor(0.75)를 넘으면 2배로 resize
꼬리 질문: hashCode()와 equals()를 둘 다 오버라이드해야 하는 이유, ConcurrentHashMap과의 차이
Q4. synchronized, volatile, Atomic의 차이를 설명해주세요.
결론: 모두 동시성을 제어하는 도구이지만, 적용 범위와 성능이 다릅니다.
| 항목 | synchronized | volatile | Atomic |
|---|---|---|---|
| 적용 범위 | 블록/메서드 단위 락 | 변수 가시성 보장 | CAS 기반 원자적 연산 |
| 원자성 | 보장 | 미보장 (읽기/쓰기만) | 보장 |
| 가시성 | 보장 | 보장 | 보장 |
| 성능 | 느림 (락 경합) | 빠름 | 빠름 (Lock-free) |
| 사용 예 | 복합 연산 보호 | flag 변수 | 카운터, 참조 업데이트 |
// volatile: 가시성만 보장 (count++은 안전하지 않음)
private volatile boolean running = true;
// AtomicInteger: CAS로 원자적 증가
private AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 스레드 안전
// synchronized: 복합 연산 보호
synchronized (lock) {
if (map.containsKey(key)) {
map.put(key, map.get(key) + 1);
}
}
Q5. Java의 Stream API에서 중간 연산과 최종 연산의 차이를 설명해주세요.
결론: 중간 연산은 지연(Lazy)되고, 최종 연산이 호출될 때 한 번에 실행됩니다.
List<String> result = users.stream()
.filter(u -> u.getAge() > 20) // 중간 연산 (Lazy)
.map(User::getName) // 중간 연산 (Lazy)
.sorted() // 중간 연산 (Lazy)
.collect(Collectors.toList()); // 최종 연산 → 여기서 전부 실행
// 중간 연산: filter, map, flatMap, sorted, distinct, limit, skip, peek
// 최종 연산: collect, forEach, count, reduce, findFirst, anyMatch, toList
꼬리 질문: parallelStream의 위험성, Stream vs for문 성능 차이
Q6. Java Record와 기존 DTO 클래스의 차이를 설명해주세요.
결론: Record는 불변 데이터 전달 객체를 위한 간결한 문법으로, equals/hashCode/toString이 자동 생성됩니다.
// Before: Lombok이나 수동으로 작성
@Getter @AllArgsConstructor @EqualsAndHashCode @ToString
public class UserResponse {
private final String name;
private final String email;
}
// After: Record (Java 16+)
public record UserResponse(String name, String email) {
// 컴팩트 생성자로 검증 가능
public UserResponse {
Objects.requireNonNull(name, "name must not be null");
}
}
Q7. Checked Exception과 Unchecked Exception의 차이를 설명해주세요.
결론: Checked Exception은 컴파일 타임에 처리를 강제하고, Unchecked Exception은 런타임에 발생합니다.
| 구분 | Checked Exception | Unchecked Exception |
|---|---|---|
| 상속 | Exception | RuntimeException |
| 처리 강제 | try-catch 또는 throws 필수 | 선택 |
| 예시 | IOException, SQLException | NullPointerException, IllegalArgumentException |
| 롤백 (Spring) | 기본적으로 롤백 안 됨 | 기본적으로 롤백됨 |
꼬리 질문: Spring에서 Checked Exception도 롤백하려면? (@Transactional(rollbackFor = ...))
Q8. 제네릭의 타입 소거(Type Erasure)란 무엇인가요?
결론: 컴파일 후 제네릭 타입 정보가 제거되어 런타임에는 원시 타입(Raw Type)으로 동작합니다.
// 컴파일 전
List<String> list = new ArrayList<>();
// 컴파일 후 (바이트코드)
List list = new ArrayList(); // 타입 정보 소거
// 이로 인한 제약:
// 1. new T() 불가
// 2. instanceof T 불가
// 3. T[] 배열 생성 불가
// 4. List<String>과 List<Integer>는 런타임에 같은 타입
Q9. String, StringBuilder, StringBuffer의 차이를 설명해주세요.
결론: String은 불변, StringBuilder는 가변+비동기, StringBuffer는 가변+동기화입니다.
| 항목 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 불변성 | 불변 (Immutable) | 가변 (Mutable) | 가변 (Mutable) |
| 스레드 안전 | 안전 (불변이므로) | 안전하지 않음 | 안전 (synchronized) |
| 성능 | 문자열 연결 시 느림 | 빠름 | StringBuilder보다 느림 |
| 사용 상황 | 변경이 적은 문자열 | 단일 스레드 문자열 조작 | 멀티 스레드 문자열 조작 |
Q10. Java의 인터페이스와 추상 클래스의 차이를 설명해주세요.
결론: 인터페이스는 "무엇을 할 수 있는가(능력)"를, 추상 클래스는 "무엇인가(정체성)"를 정의합니다.
| 항목 | 인터페이스 | 추상 클래스 |
|---|---|---|
| 다중 상속 | 가능 (implements 여러 개) | 불가 (extends 하나만) |
| 생성자 | 없음 | 있음 |
| 필드 | public static final만 | 모든 접근 제어자 가능 |
| Java 8+ 메서드 | default, static 메서드 가능 | 일반 메서드 구현 가능 |
| 사용 시점 | 서로 관련 없는 클래스에 공통 기능 | 관련 있는 클래스의 공통 로직 |
Spring 핵심 (10문)
Q11. Spring IoC와 DI의 개념을 설명해주세요.
결론: IoC(제어의 역전)는 객체 생성과 생명주기를 프레임워크가 관리하는 것이고, DI(의존성 주입)는 그 구현 방법입니다.
// DI 없이 (직접 의존성 생성)
public class OrderService {
private final OrderRepository repo = new JdbcOrderRepository(); // 강결합
}
// DI 적용 (생성자 주입 - 권장)
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository repo; // 인터페이스에 의존 → 느슨한 결합
}
꼬리 질문: 생성자 주입을 권장하는 이유 (불변성, 순환 참조 감지, 테스트 용이성)
Q12. Spring Bean의 스코프(Scope)를 설명해주세요.
결론: Bean Scope는 Bean 인스턴스의 생성 범위를 결정합니다.
| 스코프 | 설명 | 사용 예 |
|---|---|---|
| singleton (기본) | 앱 전체에서 하나의 인스턴스 | Service, Repository |
| prototype | 요청마다 새 인스턴스 | 상태를 가진 객체 |
| request | HTTP 요청마다 새 인스턴스 | 요청 컨텍스트 정보 |
| session | HTTP 세션마다 새 인스턴스 | 사용자별 상태 관리 |
Q13. @Transactional의 동작 원리를 설명해주세요.
결론: Spring AOP 프록시를 통해 메서드 실행 전후에 트랜잭션을 시작/커밋/롤백합니다.
// 프록시 동작 원리
// 1. Spring이 빈 생성 시 프록시 객체로 감쌈
// 2. 메서드 호출 → 프록시가 가로챔
// 3. TransactionManager.begin()
// 4. 실제 메서드 실행
// 5-a. 정상 종료 → commit()
// 5-b. RuntimeException → rollback()
// 5-c. CheckedException → commit() (기본 동작!)
@Service
public class OrderService {
@Transactional
public void createOrder(OrderRequest request) {
// 이 메서드 전체가 하나의 트랜잭션
}
// 주의: 같은 클래스 내부 호출은 프록시를 거치지 않음!
public void outerMethod() {
this.innerMethod(); // @Transactional 적용 안 됨!
}
@Transactional
public void innerMethod() { ... }
}
꼬리 질문: Self-Invocation 문제 해결 방법, Propagation 옵션, readOnly=true의 효과
Q14. Spring AOP의 동작 원리를 설명해주세요.
결론: 런타임에 프록시 객체를 생성하여 횡단 관심사(로깅, 트랜잭션 등)를 분리합니다.
// JDK Dynamic Proxy: 인터페이스 기반
// CGLIB Proxy: 클래스 기반 (Spring Boot 기본)
@Aspect
@Component
public class PerformanceAspect {
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint)
throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long elapsed = System.currentTimeMillis() - start;
log.info("{} 실행 시간: {}ms",
joinPoint.getSignature().getName(), elapsed);
return result;
}
}
Q15. Spring MVC의 요청 처리 흐름을 설명해주세요.
결론: DispatcherServlet → HandlerMapping → HandlerAdapter → Controller → ViewResolver 순으로 처리됩니다.
- 1. 클라이언트 요청 → DispatcherServlet (Front Controller)
- 2. HandlerMapping: URL에 매핑된 컨트롤러 탐색
- 3. HandlerAdapter: 컨트롤러 메서드 호출
- 4. Controller: 비즈니스 로직 실행, 결과 반환
- 5. ViewResolver: 뷰 이름 → 실제 뷰 변환 (REST API는 @ResponseBody로 JSON 직접 반환)
- 6. 응답 반환
Q16. Filter, Interceptor, AOP의 차이를 설명해주세요.
| 항목 | Filter | Interceptor | AOP |
|---|---|---|---|
| 실행 위치 | Servlet 앞 | Controller 앞뒤 | 메서드 앞뒤 |
| 관리 주체 | Servlet Container | Spring MVC | Spring AOP |
| Spring Bean 접근 | 제한적 (DelegatingFilterProxy) | 가능 | 가능 |
| 주요 용도 | 인코딩, 보안, CORS | 인증/인가, 로깅 | 트랜잭션, 캐싱 |
Q17. @RestController와 @Controller의 차이를 설명해주세요.
결론: @RestController = @Controller + @ResponseBody 입니다. 모든 메서드가 JSON/XML을 직접 반환합니다.
Q18. Spring Boot Auto-Configuration의 동작 원리를 설명해주세요.
결론: 클래스패스의 라이브러리와 조건(@Conditional)을 기반으로 빈을 자동 등록합니다.
// 동작 순서:
// 1. @SpringBootApplication → @EnableAutoConfiguration
// 2. META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 로드
// 3. 각 AutoConfiguration 클래스의 @Conditional 조건 평가
// 4. 조건 충족 시 빈 자동 등록
// 예: DataSource Auto-Configuration
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // 클래스패스에 DataSource가 있으면
@ConditionalOnMissingBean(DataSource.class) // 수동 등록된 빈이 없으면
public class DataSourceAutoConfiguration {
// DataSource 빈 자동 생성
}
Q19. Spring의 Event 시스템을 설명해주세요.
결론: ApplicationEventPublisher를 통해 도메인 이벤트를 발행하고, @EventListener로 처리합니다.
// 이벤트 정의
public record OrderCompletedEvent(Long orderId, Long userId, BigDecimal amount) {}
// 이벤트 발행
@Service
@RequiredArgsConstructor
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
@Transactional
public void completeOrder(Long orderId) {
// ... 주문 완료 처리
eventPublisher.publishEvent(
new OrderCompletedEvent(orderId, userId, amount));
}
}
// 이벤트 리스너 (트랜잭션 커밋 후 실행)
@Component
public class NotificationListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async
public void handleOrderCompleted(OrderCompletedEvent event) {
// 알림 발송 (비동기, 트랜잭션 커밋 후)
}
}
Q20. Spring WebFlux와 Spring MVC의 차이를 설명해주세요.
결론: MVC는 동기/블로킹(Thread-per-Request), WebFlux는 비동기/논블로킹(Event Loop) 모델입니다.
| 항목 | Spring MVC | Spring WebFlux |
|---|---|---|
| 스레드 모델 | Thread-per-Request | Event Loop (Netty) |
| 반환 타입 | 일반 객체 | Mono/Flux (Reactor) |
| 적합한 상황 | CRUD, 블로킹 I/O | 대량 동시 접속, 스트리밍 |
| 학습 곡선 | 낮음 | 높음 |
JPA 핵심 (8문)
Q21. 영속성 컨텍스트(Persistence Context)란 무엇인가요?
결론: 엔티티를 저장하고 관리하는 1차 캐시 역할의 논리적 영역입니다.
- 1차 캐시: 같은 트랜잭션 내에서 동일 엔티티 조회 시 DB 쿼리 없이 반환
- 변경 감지(Dirty Checking): 트랜잭션 커밋 시 엔티티 변경을 자동 감지하여 UPDATE 쿼리 발생
- 쓰기 지연: persist(), update 등의 쿼리를 모아서 flush 시점에 한꺼번에 실행
- 동일성 보장: 같은 ID로 조회하면 항상 같은 객체 참조(==) 반환
Q22. N+1 문제란 무엇이고 어떻게 해결하나요?
결론: 1번의 쿼리로 N개의 엔티티를 조회한 후, 연관 엔티티를 가져오기 위해 N번의 추가 쿼리가 발생하는 문제입니다.
// 문제 발생
List<Team> teams = teamRepository.findAll(); // 1번 쿼리
for (Team team : teams) {
team.getMembers().size(); // 팀마다 추가 쿼리 → N번!
}
// 해결 1: Fetch Join
@Query("SELECT t FROM Team t JOIN FETCH t.members")
List<Team> findAllWithMembers();
// 해결 2: EntityGraph
@EntityGraph(attributePaths = {"members"})
List<Team> findAll();
// 해결 3: BatchSize
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members;
// → IN 쿼리로 100개씩 묶어서 조회
꼬리 질문: Fetch Join 시 페이징 문제(MultipleBagFetchException), BatchSize가 더 나은 상황
Q23. OSIV(Open Session In View)를 설명해주세요.
결론: 영속성 컨텍스트를 뷰 렌더링까지 유지하여 Lazy Loading을 가능하게 하는 패턴입니다.
| 항목 | OSIV ON (기본) | OSIV OFF |
|---|---|---|
| 영속성 컨텍스트 범위 | 요청 시작 ~ 응답 완료 | 트랜잭션 범위 내 |
| 장점 | Lazy Loading 어디서든 가능 | DB 커넥션 빨리 반환 |
| 단점 | DB 커넥션 오래 점유 | 트랜잭션 밖 Lazy Loading 불가 |
| 권장 상황 | 트래픽 적은 관리자 화면 | API 서버, 고트래픽 |
Q24. JPA의 락(Lock) 전략을 설명해주세요.
결론: 낙관적 락(Optimistic)은 버전 충돌을 감지하고, 비관적 락(Pessimistic)은 DB 레벨에서 락을 겁니다.
// 낙관적 락 - 충돌이 적을 때 사용
@Entity
public class Product {
@Version
private Long version; // 수정 시마다 자동 증가
}
// UPDATE product SET ... WHERE id = ? AND version = ?
// 버전 불일치 → OptimisticLockException
// 비관적 락 - 충돌이 잦을 때 사용
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Optional<Product> findByIdWithLock(@Param("id") Long id);
// SELECT ... FOR UPDATE
Q25. 즉시 로딩(EAGER)과 지연 로딩(LAZY)의 차이를 설명해주세요.
결론: EAGER는 엔티티 조회 시 연관 엔티티도 즉시 로딩하고, LAZY는 실제 접근 시점에 로딩합니다.
권장: 모든 연관 관계는 LAZY로 설정하고, 필요할 때 Fetch Join으로 가져오세요.
@ManyToOne(fetch = FetchType.LAZY) // 권장
private Team team;
// @ManyToOne의 기본값은 EAGER → 반드시 LAZY로 변경!
// @OneToMany의 기본값은 LAZY
Q26. JPA Auditing이란 무엇인가요?
결론: 엔티티의 생성 시간, 수정 시간, 생성자, 수정자를 자동으로 관리하는 기능입니다.
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String updatedBy;
}
Q27. JPQL과 QueryDSL의 차이를 설명해주세요.
| 항목 | JPQL | QueryDSL |
|---|---|---|
| 타입 안전 | 문자열 기반 (런타임 오류) | 코드 기반 (컴파일 타임 검증) |
| 동적 쿼리 | 불편 (문자열 조합) | 편리 (BooleanBuilder, BooleanExpression) |
| IDE 지원 | 제한적 | 자동 완성 가능 |
| 학습 곡선 | 낮음 | 중간 |
Q28. Spring Data JPA의 save()와 saveAndFlush()의 차이를 설명해주세요.
결론: save()는 영속성 컨텍스트에 저장하고, saveAndFlush()는 저장 후 즉시 DB에 반영(flush)합니다.
사용 시점: DB 기본값을 바로 읽어야 하거나, unique 제약 조건 위반을 즉시 감지해야 할 때 saveAndFlush()를 사용합니다.
데이터베이스 핵심 (7문)
Q29. 인덱스(Index)의 동작 원리를 설명해주세요.
결론: 인덱스는 B-Tree(또는 B+Tree) 구조로 데이터를 정렬하여 빠른 검색을 가능하게 합니다.
-- 풀 스캔: O(N) - 100만 건이면 100만 번 비교
SELECT * FROM users WHERE email = 'kim@email.com';
-- 인덱스 스캔: O(log N) - 100만 건이면 약 20번 비교
CREATE INDEX idx_users_email ON users(email);
-- 복합 인덱스: 왼쪽부터 순서대로 매칭 (Leftmost Prefix Rule)
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
-- O: WHERE user_id = 1
-- O: WHERE user_id = 1 AND created_at > '2025-01-01'
-- X: WHERE created_at > '2025-01-01' (첫 번째 컬럼 미사용 → 인덱스 무시)
꼬리 질문: 커버링 인덱스, 인덱스를 걸면 안 되는 상황, Cardinality
Q30. 트랜잭션 격리 수준(Isolation Level)을 설명해주세요.
| 격리 수준 | Dirty Read | Non-Repeatable Read | Phantom Read |
|---|---|---|---|
| READ UNCOMMITTED | 발생 | 발생 | 발생 |
| READ COMMITTED | 방지 | 발생 | 발생 |
| REPEATABLE READ | 방지 | 방지 | 발생 (MySQL InnoDB는 방지) |
| SERIALIZABLE | 방지 | 방지 | 방지 |
실무: MySQL의 기본은 REPEATABLE READ, PostgreSQL의 기본은 READ COMMITTED입니다.
Q31. 정규화와 반정규화를 설명해주세요.
결론: 정규화는 중복을 제거하여 데이터 무결성을 높이고, 반정규화는 성능을 위해 의도적으로 중복을 허용합니다.
- 1NF: 모든 컬럼이 원자값
- 2NF: 부분 함수 종속 제거 (복합 키의 일부에만 종속되는 컬럼 분리)
- 3NF: 이행 함수 종속 제거 (A→B→C에서 A→C 제거)
- 반정규화 예: 주문 테이블에 상품명을 중복 저장 (조인 비용 감소)
Q32. DB 커넥션 풀의 동작 원리를 설명해주세요.
결론: 미리 일정 수의 DB 커넥션을 생성해두고 재사용하여, 커넥션 생성/해제 비용을 절감합니다.
spring:
datasource:
hikari:
maximum-pool-size: 20 # 최대 커넥션 수
minimum-idle: 5 # 최소 유휴 커넥션
connection-timeout: 3000 # 커넥션 대기 최대 시간 (ms)
max-lifetime: 1800000 # 커넥션 최대 수명 (30분)
idle-timeout: 600000 # 유휴 커넥션 유지 시간 (10분)
# 적정 pool size 공식 (HikariCP 권장):
# pool_size = (core_count * 2) + effective_spindle_count
# 예: 4코어 서버 → (4 * 2) + 1 = 9개
Q33. EXPLAIN 실행 계획을 어떻게 읽나요?
EXPLAIN ANALYZE
SELECT u.name, COUNT(o.id) as order_count
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2025-01-01'
GROUP BY u.name;
-- 확인 포인트:
-- 1. type: ALL(풀스캔) → ref/range(인덱스) 인지
-- 2. rows: 예상 스캔 행 수
-- 3. Extra: Using filesort, Using temporary → 개선 필요
-- 4. key: 실제 사용된 인덱스
Q34. 샤딩(Sharding)과 레플리케이션(Replication)의 차이를 설명해주세요.
| 항목 | 샤딩 | 레플리케이션 |
|---|---|---|
| 목적 | 쓰기 분산 | 읽기 분산 + 가용성 |
| 데이터 | 분할 (각 노드가 부분 데이터) | 복제 (모든 노드가 전체 데이터) |
| 장점 | 수평 확장 | 읽기 성능 + 장애 대비 |
| 단점 | 복잡한 조인, 리밸런싱 | 동기화 지연 |
Q35. DB 데드락이란 무엇이고 어떻게 해결하나요?
결론: 두 트랜잭션이 서로 상대방의 락을 기다리는 교착 상태입니다.
-- 트랜잭션 A: UPDATE accounts SET balance = 100 WHERE id = 1; (락)
-- UPDATE accounts SET balance = 200 WHERE id = 2; (대기)
-- 트랜잭션 B: UPDATE accounts SET balance = 300 WHERE id = 2; (락)
-- UPDATE accounts SET balance = 400 WHERE id = 1; (대기)
-- 해결:
-- 1. 자원 접근 순서 통일 (항상 id 오름차순으로 접근)
-- 2. 트랜잭션 범위 최소화
-- 3. 타임아웃 설정
-- 4. DB 데드락 감지 → 하나의 트랜잭션 롤백
인프라 핵심 (5문)
Q36. Docker와 VM(가상 머신)의 차이를 설명해주세요.
| 항목 | Docker (컨테이너) | VM (가상 머신) |
|---|---|---|
| 가상화 수준 | OS 커널 공유 | 하드웨어 가상화 |
| 시작 시간 | 초 단위 | 분 단위 |
| 리소스 사용 | 경량 | 무거움 (전체 OS 포함) |
| 격리 수준 | 프로세스 수준 | 완전 격리 |
Q37. CI/CD 파이프라인을 설명해주세요.
결론: CI(지속적 통합)는 코드 변경 시 자동 빌드/테스트, CD(지속적 배포)는 자동 배포까지의 파이프라인입니다.
# 일반적인 CI/CD 파이프라인 단계:
# 1. 코드 Push → 2. 빌드 → 3. 단위 테스트 → 4. 통합 테스트
# → 5. 코드 품질 분석(SonarQube) → 6. Docker 이미지 빌드
# → 7. 스테이징 배포 → 8. E2E 테스트 → 9. 프로덕션 배포
Q38. Blue-Green 배포와 Canary 배포의 차이를 설명해주세요.
| 항목 | Blue-Green | Canary |
|---|---|---|
| 방식 | 새 환경 준비 후 한 번에 전환 | 일부 트래픽만 새 버전으로 |
| 리스크 | 전환 시 전체 영향 | 일부만 영향, 점진적 확대 |
| 롤백 | 즉시 (이전 환경으로 전환) | 트래픽 비율 조정 |
| 비용 | 2배의 인프라 필요 | 점진적 증가 |
Q39. 로드 밸런서의 동작 원리를 설명해주세요.
결론: 여러 서버에 트래픽을 분산하여 가용성과 성능을 높이는 장치입니다.
- Round Robin: 순서대로 분배
- Least Connection: 연결이 가장 적은 서버로
- IP Hash: 클라이언트 IP 기반 고정 서버 배정 (세션 유지)
- Weighted: 서버 성능에 따라 가중치 부여
L4 vs L7: L4는 TCP/UDP 레벨, L7은 HTTP 레벨(URL 기반 라우팅 가능)
Q40. 모니터링에서 중요한 4가지 골든 시그널을 설명해주세요.
- Latency: 요청 처리 시간 (P50, P95, P99)
- Traffic: 초당 요청 수 (RPS)
- Errors: 에러율 (5xx / 전체 요청)
- Saturation: 리소스 포화도 (CPU, 메모리, 커넥션 풀)
시스템 설계 핵심 (5문)
Q41. CAP 정리를 설명해주세요.
결론: 분산 시스템에서 Consistency(일관성), Availability(가용성), Partition tolerance(분단 내성) 3가지 중 2가지만 동시에 만족할 수 있습니다.
| 선택 | 특성 | 예시 |
|---|---|---|
| CP | 일관성 + 분단 내성 | HBase, MongoDB (strong consistency) |
| AP | 가용성 + 분단 내성 | Cassandra, DynamoDB |
| CA | 일관성 + 가용성 (분산이 아닌 경우) | 단일 DB (RDBMS) |
Q42. 캐시 전략(Cache Strategy)을 설명해주세요.
- Cache-Aside (Lazy Loading): 캐시 미스 시 DB 조회 후 캐시 저장. 가장 일반적.
- Write-Through: 쓰기 시 캐시와 DB 동시 업데이트. 일관성 높음.
- Write-Behind: 캐시에만 쓰고, 비동기로 DB 반영. 쓰기 성능 높음.
- Read-Through: 캐시가 자동으로 DB 조회. 캐시 제공 라이브러리가 관리.
// Cache-Aside 패턴 (Spring)
@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
return userRepository.findById(id).orElseThrow();
}
@CacheEvict(value = "users", key = "#id")
public void updateUser(Long id, UpdateRequest request) {
// ...
}
Q43. 이벤트 기반 아키텍처(EDA)를 설명해주세요.
결론: 서비스 간 통신을 동기 API 호출 대신 이벤트(메시지)로 처리하는 아키텍처입니다.
- 장점: 느슨한 결합, 확장성, 장애 격리
- 단점: 최종 일관성, 디버깅 어려움, 이벤트 순서 보장
- 구현: Kafka, RabbitMQ, SNS/SQS
Q44. 마이크로서비스에서 분산 트랜잭션을 어떻게 처리하나요?
결론: 2PC는 성능 문제가 있어 SAGA 패턴을 주로 사용합니다.
- Choreography SAGA: 이벤트 기반으로 각 서비스가 자율적으로 처리. 간단하지만 흐름 파악 어려움.
- Orchestration SAGA: 중앙 Orchestrator가 흐름 관리. 복잡하지만 흐름 명확.
- 보상 트랜잭션: 실패 시 이전 단계를 취소하는 역방향 트랜잭션 실행.
Q45. Rate Limiting은 어떻게 구현하나요?
결론: 일정 시간 내 요청 수를 제한하여 서버를 보호합니다.
- Token Bucket: 일정 속도로 토큰 충전, 요청 시 토큰 소비. 버스트 허용.
- Sliding Window: 이동 구간 내 요청 수 카운트. 정밀한 제어.
- 구현: Spring Cloud Gateway, Redis + Lua Script, Bucket4j
네트워크 핵심 (5문)
Q46. HTTP/1.1, HTTP/2, HTTP/3의 차이를 설명해주세요.
| 항목 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 프로토콜 | TCP | TCP | QUIC (UDP) |
| 멀티플렉싱 | 불가 (HOL Blocking) | 가능 | 가능 |
| 헤더 압축 | 없음 | HPACK | QPACK |
| 서버 푸시 | 없음 | 가능 | 가능 |
| 연결 설정 | TCP 3-way + TLS | TCP 3-way + TLS | 0-RTT 가능 |
Q47. TCP 3-way Handshake를 설명해주세요.
결론: 클라이언트와 서버 간 신뢰성 있는 연결을 수립하기 위한 3단계 과정입니다.
- SYN: 클라이언트 → 서버 (연결 요청, 시퀀스 번호 전송)
- SYN-ACK: 서버 → 클라이언트 (요청 수락, 서버 시퀀스 번호 전송)
- ACK: 클라이언트 → 서버 (확인, 데이터 전송 시작)
꼬리 질문: 4-way Handshake(연결 종료), TIME_WAIT 상태
Q48. REST API 설계 원칙을 설명해주세요.
- URI는 리소스를 표현:
/users/123(동사 X, 명사 O) - HTTP 메서드로 행위 표현: GET(조회), POST(생성), PUT(전체 수정), PATCH(부분 수정), DELETE(삭제)
- 적절한 상태 코드: 200(성공), 201(생성), 204(No Content), 400(잘못된 요청), 404(없음), 409(충돌), 500(서버 오류)
- 버전 관리: /api/v1/users
- 페이징: ?page=0&size=20&sort=createdAt,desc
Q49. HTTPS의 동작 원리를 설명해주세요.
결론: TLS/SSL 위에서 HTTP를 암호화하여 전송하는 프로토콜입니다.
- 1. TCP 연결 (3-way Handshake)
- 2. TLS Handshake: 서버 인증서 전송 → 클라이언트가 CA 검증 → 대칭키 교환
- 3. 암호화 통신: 교환된 대칭키로 데이터 암호화/복호화
비대칭키(공개키/개인키)는 키 교환에만 사용하고, 실제 데이터 전송은 대칭키(빠름)로 합니다.
Q50. WebSocket과 HTTP의 차이를 설명해주세요.
| 항목 | HTTP | WebSocket |
|---|---|---|
| 통신 방식 | 단방향 (요청-응답) | 양방향 (전이중) |
| 연결 상태 | 비연결 (Stateless) | 연결 유지 (Stateful) |
| 오버헤드 | 매 요청마다 헤더 | 초기 핸드셰이크 후 경량 |
| 사용 예 | 일반 API | 채팅, 실시간 알림, 주식 시세 |
면접 팁
- "결론 먼저" 원칙: 모든 답변은 한 문장 결론부터 시작하세요. 면접관은 바쁩니다.
- 깊이 vs 넓이: 3~7년차에게는 "왜"를 설명하는 깊이가 더 중요합니다. "사용해봤다"가 아니라 "왜 그렇게 동작하는지"를 설명하세요.
- 실무 경험 연결: 이론 답변 후 "실제 프로젝트에서는..."으로 경험을 연결하면 신뢰도가 올라갑니다.
- 모르면 솔직히: 모르는 질문에 아는 척하면 꼬리 질문에서 바로 드러납니다. "정확히 알지 못하지만, ~라고 이해하고 있습니다"가 더 좋은 인상을 줍니다.
- 트레이드오프: 기술 선택에는 항상 장단점이 있습니다. "A가 무조건 좋다"보다 "이런 상황에서는 A가, 저런 상황에서는 B가 적합하다"고 답하세요.
마치며
기술 면접은 단순 암기 시험이 아닙니다. 면접관이 정말 확인하고 싶은 것은 "이 사람이 실무에서 문제를 만났을 때 어떻게 사고하고 해결하는가"입니다.
- Java: JVM 메모리와 GC 원리를 이해하면 성능 이슈를 진단할 수 있습니다.
- Spring: 프록시 기반 동작 원리를 알면 @Transactional이 안 먹히는 상황을 디버깅할 수 있습니다.
- JPA: 영속성 컨텍스트와 N+1 문제를 이해하면 쿼리 성능을 최적화할 수 있습니다.
- DB: 인덱스와 실행 계획을 읽을 줄 알면 슬로우 쿼리를 해결할 수 있습니다.
- 인프라: Docker와 CI/CD를 이해하면 배포 자동화와 장애 대응이 가능합니다.
- 시스템 설계: CAP 정리와 캐시 전략을 알면 확장 가능한 시스템을 설계할 수 있습니다.
이 50개 질문은 출발점입니다. 각 답변을 자신의 경험과 연결하여 "나만의 답변"으로 만들어 보세요. 그것이 진정한 면접 준비입니다. 좋은 결과가 있기를 응원합니다.
'CS' 카테고리의 다른 글
| DNS와 CDN 동작 원리 - 웹 성능 최적화의 기초 (0) | 2026.04.08 |
|---|---|
| 실시간 통신 완벽 가이드 - WebSocket, SSE, gRPC 비교 분석 (0) | 2026.04.08 |
| OAuth 2.0 / OIDC 완벽 가이드 - 인증 프로토콜의 모든 것 (1) | 2026.04.08 |
| REST API 설계 원칙 - 실무에서 바로 쓰는 베스트 프랙티스 (0) | 2026.03.31 |
| HTTP 완벽 가이드 - 백엔드 개발자가 알아야 할 모든 것 (0) | 2026.03.31 |