etc
Interview Question

중급 백엔드 개발자 기술 심층 면접 질문 81선

📋 목차


이 질문 목록은 6년 차 백엔드 개발자의 기술적 깊이를 측정하기 위해 설계되었습니다. 각 질문은 '무엇'을 아는지를 넘어, '왜' 그렇게 동작하는지, 그 기술의 핵심 원리와 트레이드오프는 무엇인지를 설명하는 데 중점을 둡니다.

☕ Java (15 Questions)

1. JVM의 메모리 구조(Runtime Data Area)에 대해 설명하고, 각 영역의 역할을 설명해주세요. (출제 확률: 매우 높음)

정답: JVM 메모리는 크게 모든 스레드가 공유하는 Heap과 Method Area, 그리고 각 스레드마다 독립적인 Stack, PC Register, Native Method Stack으로 구성됩니다. Heap은 객체가 생성되는 공간이고, Method Area는 클래스 메타데이터가 저장되며, Stack은 메서드 호출과 지역 변수를 관리합니다. 정답 해설: 이 구조를 이해하는 것은 Java 애플리케이션의 메모리 사용 방식을 이해하는 첫걸음입니다. Heap과 Stack의 차이를 명확히 아는 것이 중요합니다. +-----------------------------------------------------------------+ | JVM Runtime Data Area | +-----------------------------------------------------------------+ | | | [[[ 모든 스레드 공유 영역 (Shared by All Threads) ]]] | | | | +---------------------+ +---------------------------+ | | | Method Area | | Heap Area | | | |---------------------| |---------------------------| | | | - Class Metadata | | - Object Instances | | | | - Static Variables | | - Arrays | | | | - Runtime Constants | | (Garbage Collection 대상) | | | +---------------------+ +---------------------------+ | | | +-----------------------------------------------------------------+ | | | [[[ 스레드별 개별 영역 (Per Thread) ]]] | | | | +---------------------+ +---------------------------+ | | | Stack Area | | PC Register | | | | (Thread 1) | | (Thread 1) | | | +---------------------+ +---------------------------+ | | | | +---------------------+ +---------------------------+ | | | Stack Area | | PC Register | | | | (Thread 2) | | (Thread 2) | | | +---------------------+ +---------------------------+ | | ... ... | +-----------------------------------------------------------------+

Heap Area: new 키워드로 생성된 모든 객체 인스턴스와 배열이 저장됩니다. GC에 의해 관리되는 유일한 영역으로, 여러 스레드가 이 영역의 데이터를 공유하므로 동시성 문제가 발생할 수 있습니다. 따라서 Heap 영역의 데이터에 접근할 때는 동기화 처리에 주의해야 합니다. OutOfMemoryError는 대부분 이 Heap 영역의 메모리가 부족할 때 발생합니다. Stack Area: 각 스레드마다 하나씩 생성되며, 메서드가 호출될 때마다 **스택 프레임(Stack Frame)**이 쌓이는 구조입니다. 스택 프레임 안에는 해당 메서드의 지역 변수, 매개변수, 중간 연산 결과, 그리고 호출한 메서드로 돌아갈 주소 등이 저장됩니다. 메서드 실행이 끝나면 해당 스택 프레임은 자동으로 제거됩니다. 이 영역의 데이터는 다른 스레드에서 접근할 수 없으므로 **스레드에 안전(Thread-safe)**합니다. 재귀 호출이 너무 깊어지면 StackOverflowError가 발생합니다. 이처럼 Heap과 Stack의 역할과 데이터 저장 방식을 이해해야, 메모리 누수나 성능 저하 문제 발생 시 원인을 정확하게 진단하고 해결할 수 있습니다.


2. GC(Garbage Collection)의 기본 원리와, G1 GC가 이전 세대 GC(CMS 등)에 비해 갖는 장점은 무엇인가요? (출제 확률: 매우 높음)

정답: G1 GC는 힙을 여러 개의 작은 리전(Region) 단위로 나누어, 가비지가 가장 많이 쌓인 리전부터 우선적으로 정리합니다. 이를 통해 예측 가능한 짧은 STW(Stop-The-World) 시간을 보장하고, 객체 복사 과정에서 자연스럽게 힙 압축을 수행하여 메모리 파편화 문제를 해결하는 장점이 있습니다. 정답 해설: CMS(Concurrent Mark Sweep) GC는 'Mark-Sweep' 과정 대부분을 애플리케이션 스레드와 동시에 실행하여 STW를 줄이는 데 초점을 맞췄지만, 두 가지 치명적인 단점이 있었습니다. 첫째, 메모리 파편화입니다. Sweep 단계에서 메모리를 회수만 할 뿐, 조각난 메모리들을 한 곳으로 모으는 압축(Compaction)을 하지 않아, 나중에 큰 객체를 할당할 공간이 부족해져 결국 긴 STW를 유발하는 Full GC가 발생했습니다. 둘째, 예측 불가능한 STW 시간입니다. G1 GC는 이 두 문제를 모두 해결합니다. 파편화 해결: G1 GC는 'Evacuation'이라는 단계를 통해 살아있는 객체들을 완전히 비어있는 새로운 리전으로 복사합니다. 이 과정 자체가 곧 압축이므로, 메모리 파편화가 발생하지 않습니다. 예측 가능성 확보: G1 GC는 전체 힙을 대상으로 동작하는 대신, 각 리전의 가비지 양을 추적하여 가장 청소 효율이 좋은(Garbage-First) 리전들을 우선적으로 선택하여 GC를 수행합니다. 개발자는 -XX:MaxGCPauseMillis 옵션으로 목표 STW 시간을 설정할 수 있고, G1 GC는 이 시간 내에 처리할 수 있는 만큼의 리전만 선택하여 GC를 수행하므로, STW 시간이 훨씬 짧고 예측 가능해집니다. 이는 실시간 응답성이 중요한 최신 애플리케이션 환경에 더 적합하며, Java 9부터 기본 GC로 채택된 이유입니다.


3. java.util.concurrent.ConcurrentHashMap은 어떻게 동시성을 보장하며, 기존의 Hashtable이나 Collections.synchronizedMap에 비해 어떤 장점이 있나요? (출제 확률: 매우 높음)

정답: ConcurrentHashMap은 락 스트라이핑(Lock Striping) 기법을 사용합니다. 맵 전체에 하나의 락을 거는 Hashtable과 달리, 내부적으로 데이터를 여러 개의 세그먼트로 나누고 각 세그먼트마다 별도의 락을 두어 락의 범위를 최소화합니다. 이를 통해 락 경합을 줄여 동시 처리 성능을 크게 향상시킵니다. 정답 해설: Hashtable이나 Collections.synchronizedMap은 맵의 모든 메서드에 synchronized가 걸려있어, 한 스레드가 put() 작업을 하는 동안 다른 스레드는 get() 작업조차 할 수 없이 대기해야 합니다. 이는 동시성이 높은 환경에서 심각한 병목 현상을 유발합니다. ConcurrentHashMap은 이 문제를 락의 범위를 줄이는 방식으로 해결합니다. 읽기 작업(get): 대부분의 경우 락 없이 값을 읽습니다. volatile을 활용하여 최신 값을 읽어오므로 매우 빠릅니다. 쓰기 작업(put, remove): 맵 전체가 아닌, 해당 키가 속한 특정 세그먼트(Java 7 이전) 또는 해시 버킷의 첫 번째 노드(Java 8 이후)에만 락을 겁니다. 이러한 구조 덕분에, 한 스레드가 특정 세그먼트에 쓰기 작업을 하는 동안, 다른 스레드는 다른 세그먼트에 자유롭게 쓰기 작업을 할 수 있으며, 동시에 모든 스레드는 락 없이 읽기 작업을 수행할 수 있습니다. 이처럼 불필요한 락 경합을 제거하여 읽기/쓰기 처리량(Throughput)을 획기적으로 향상시킨 것이 ConcurrentHashMap의 핵심적인 장점입니다.


4. hashCode() 메서드를 오버라이딩할 때, 좋은 해시 코드를 만드는 일반적인 원칙은 무엇인가요? (출제 확률: 매우 높음)

정답: 좋은 해시 코드는 해시 충돌을 최소화하도록 고르게 분포된 값을 만들어야 합니다. 이를 위해 equals() 비교에 사용된 핵심 필드들을 포함시키고, 소수(주로 31)를 곱하는 방식을 사용하여 각 필드의 영향력이 고르게 반영되도록 합니다. Objects.hash() 헬퍼 메서드를 사용하면 이를 간편하게 구현할 수 있습니다. 정답 해설: HashMap의 성능은 해시 코드가 얼마나 잘 분산되느냐에 달려있습니다. 만약 모든 객체가 같은 해시 코드를 반환한다면, 모든 데이터가 하나의 버킷에만 저장됩니다. 이 경우, HashMap은 사실상 **링크드 리스트(Linked List)**처럼 동작하게 되어 탐색 성능이 O(N)으로 저하됩니다. 좋은 해시 함수는 각 객체를 해시 테이블의 여러 버킷에 최대한 균등하게 분배하여, 어떤 버킷을 찾아가더라도 평균적으로 처리할 데이터가 적도록 만들어 O(1)에 가까운 성능을 유지시켜 줍니다. 소수를 사용하는 이유는 곱셈 연산 시 특정 비트 패턴이 반복되는 것을 막아, 결과값의 분포를 더 균일하게 만드는 수학적 특성 때문입니다.


5. String, StringBuilder, StringBuffer의 차이점은 무엇인가요? (출제 확률: 매우 높음)

정답: String은 불변(Immutable) 객체이고, StringBuilder와 StringBuffer는 가변(Mutable) 객체입니다. 둘의 차이는 동기화 여부로, StringBuffer는 스레드에 안전(thread-safe)하지만 성능이 떨어지고, StringBuilder는 스레드에 안전하지 않지만 단일 스레드 환경에서 성능이 더 좋습니다. 정답 해설: String이 불변인 이유: String 객체는 JVM 내의 특별한 공간(String Constant Pool)에서 관리되며, 여러 변수가 같은 문자열 리터럴을 공유할 수 있습니다. 만약 String이 가변이라면, 한 변수에서 문자열을 변경했을 때 같은 문자열을 참조하는 다른 모든 변수에도 예기치 않은 영향을 미치게 됩니다. 또한, 불변 객체는 상태가 변하지 않으므로 해시맵의 키로 사용되기에 적합하고, 여러 스레드에서 안전하게 공유될 수 있는 등 여러 이점을 가집니다. 동기화의 비용: StringBuffer의 메서드는 synchronized 처리되어 있어, 여러 스레드가 동시에 접근할 때 하나의 스레드만 실행되도록 보장(Lock)합니다. 이 Lock을 획득하고 해제하는 과정은 시스템에 부하를 주므로, 스레드 안전성이 필요 없는 환경에서 StringBuffer를 사용하는 것은 불필요한 성능 저하를 유발합니다. 따라서 상황에 맞는 클래스를 선택하는 것이 중요합니다.


6. volatile 키워드는 무엇이며, 어떤 문제를 해결하기 위해 사용되나요? (출제 확률: 높음)

정답: volatile은 특정 변수에 대한 읽기/쓰기 작업이 항상 메인 메모리를 통해 이루어지도록 보장하는 키워드입니다. 이를 통해 멀티 스레드 환경에서 각 스레드의 CPU 캐시와 메인 메모리 간의 데이터 불일치로 발생하는 '가시성(Visibility)' 문제를 해결합니다. 단, 연산의 원자성은 보장하지 않습니다. 정답 해설: 멀티코어 환경에서 각 CPU는 성능 향상을 위해 자체 캐시(L1, L2 캐시 등)를 가집니다. 한 스레드(Thread A)가 특정 변수 flag를 true로 변경했을 때, 그 변경 사항이 자신의 CPU 캐시에만 머물러 있고 다른 스레드(Thread B)가 사용하는 CPU 캐시나 메인 메모리에는 즉시 반영되지 않을 수 있습니다. 이것이 가시성 문제입니다. Thread B는 flag가 여전히 false라고 생각하고 오작동을 일으킬 수 있습니다. volatile은 컴파일러와 CPU에게 **"이 변수에 대해서는 최적화를 하지 말고, 항상 메인 메모리에서 직접 읽고 써라"**고 지시하는 역할을 합니다. 이를 통해 한 스레드의 변경 사항이 다른 모든 스레드에게 즉시 보이도록 보장합니다. 하지만 count++와 같은 '읽기-수정-쓰기' 형태의 복합 연산은 volatile만으로는 동기화할 수 없습니다. 이 연산은 원자적이지 않아, 여러 스레드가 동시에 count를 읽고, 각자 1을 더한 뒤, 다시 쓰는 과정에서 경쟁 상태(Race Condition)가 발생하여 최종 결과값이 예상과 달라질 수 있습니다. 따라서 volatile은 주로 상태 플래그(flag) 변수처럼 단일 읽기/쓰기 작업의 가시성 보장이 필요할 때 사용하며, 복합 연산의 동기화에는 synchronized나 Atomic 클래스를 사용해야 합니다.


7. Serializable 인터페이스의 역할과 serialVersionUID의 중요성에 대해 설명해주세요. (출제 확률: 높음)

정답: Serializable은 해당 객체가 직렬화될 수 있음을 나타내는 마커 인터페이스입니다. serialVersionUID는 직렬화된 객체와 클래스 간의 버전 호환성을 확인하는 ID로, 역직렬화 시 이 값이 다르면 InvalidClassException이 발생합니다. 따라서 클래스가 변경되더라도 이전 버전과의 호환성을 유지하려면 이 값을 명시적으로 고정해야 합니다. 정답 해설: serialVersionUID를 명시적으로 선언하지 않으면, JVM은 클래스의 구조(필드, 메서드 등)를 기반으로 자동으로 이 값을 생성합니다. 문제는, 개발자가 클래스에 사소한 변경(예: 메서드 추가, 필드 순서 변경)만 가해도 JVM이 완전히 다른 serialVersionUID를 생성할 수 있다는 것입니다. 이 경우, 이전에 직렬화하여 파일이나 DB에 저장해 둔 객체는 새로운 버전의 클래스로 역직렬화할 수 없게 되어 시스템 장애로 이어집니다. 따라서, 클래스의 내용이 바뀌어도 이전 버전과 호환성을 유지하고 싶다면 반드시 serialVersionUID를 private static final long 타입의 고정된 값으로 명시적으로 선언해야 합니다. 이는 분산 시스템 환경이나 장기적으로 데이터를 저장하고 관리해야 하는 시스템에서 예기치 않은 데이터 유실을 막기 위한 매우 중요한 관행입니다.


8. CompletableFuture는 기존의 Future에 비해 어떤 장점이 있나요? (출제 확률: 높음)

정답: CompletableFuture는 기존 Future와 달리, 논블로킹(non-blocking) 방식으로 후속 작업을 처리할 수 있는 다양한 콜백(Callback)을 지원하는 것이 가장 큰 장점입니다. 또한, 여러 비동기 작업을 체이닝하거나 조합하여 복잡한 워크플로우를 쉽게 구성할 수 있습니다. 정답 해설: 기존 Future의 가장 큰 문제는 결과를 얻기 위해 get() 메서드를 통해 스레드를 블로킹시켜야 한다는 점과, 여러 비동기 작업을 조합하기가 매우 어렵다는 점이었습니다. CompletableFuture는 이러한 문제를 해결합니다. 블로킹 문제 해결: future.get()으로 스레드를 멈추는 대신, future.thenAccept(result -> ...)와 같이 **"작업이 끝나면 이 로직을 실행해줘"**라는 콜백을 등록할 수 있습니다. 이를 통해 스레드는 다른 작업을 계속 수행할 수 있어 시스템 자원을 효율적으로 사용합니다. 복잡한 워크플로우 구성:

  • thenApply(): 비동기 작업 결과를 받아 다른 값으로 변환합니다. (A -> B)
  • thenCompose(): 비동기 작업 결과를 받아 또 다른 비동기 작업을 실행하고 연결합니다. (A -> Future<B>)
  • thenCombine(): 두 비동기 작업이 모두 완료되면 그 결과들을 조합합니다. (A + B -> C) 이러한 기능들은 "콜백 지옥"에 빠지지 않으면서도 시스템 자원을 효율적으로 사용하는 반응형(Reactive) 프로그래밍 스타일을 가능하게 해주는 강력한 도구입니다.

9. Java의 리플렉션(Reflection) API는 무엇이며, 어떤 경우에 사용되고 어떤 단점이 있나요? (출제 확률: 높음)

정답: 리플렉션은 프로그램 실행 중에 클래스의 메타 정보를 동적으로 얻고 조작하는 API입니다. 주로 Spring의 DI나 JPA처럼 프레임워크가 런타임에 유연한 기능을 제공하기 위해 사용됩니다. 하지만 성능이 느리고, 캡슐화를 위반하며, 컴파일 타임에 에러를 잡을 수 없다는 명확한 단점이 있어 일반적인 비즈니스 코드에서는 사용을 지양해야 합니다. 정답 해설: 리플렉션은 유연성과 일반화를 위한 강력한 도구이지만, 그에 따르는 비용이 명확합니다. Spring이 @Autowired 어노테이션이 붙은 필드에 자동으로 의존성을 주입할 수 있는 것도, 실행 중에 리플렉션을 사용하여 해당 필드 정보를 읽고 적절한 빈(Bean)을 찾아 설정해주기 때문입니다. 성능 저하: 일반적인 메서드 호출은 컴파일 시점에 대상이 결정되어 바로 실행되지만, 리플렉션은 런타임에 클래스 이름을 찾고, 메서드를 찾고, 접근 권한을 확인하는 등 여러 단계를 거치므로 훨씬 느릴 수밖에 없습니다. 캡슐화 위반: setAccessible(true) 옵션을 사용하면 private 필드나 메서드에도 강제로 접근하고 수정할 수 있습니다. 이는 객체의 상태를 예측 불가능하게 만들어 유지보수를 매우 어렵게 만듭니다. 따라서 리플렉션은 프레임워크나 라이브러리처럼 일반화된 코드가 필요한 경우가 아니라면, 일반적인 비즈니스 로직 코드에서 남용하는 것은 피해야 합니다.


10. 스트림(Stream)의 중간 연산(Intermediate Operation)과 최종 연산(Terminal Operation)의 차이점은 무엇인가요? (출제 확률: 높음)

정답: 중간 연산은 스트림을 반환하며 **지연 평가(Lazy Evaluation)**됩니다. 즉, 최종 연산이 호출될 때까지 실행되지 않고 여러 개를 연결할 수 있습니다. 반면, 최종 연산은 스트림이 아닌 다른 결과를 반환하며, 스트림 파이프라인의 모든 연산을 실행시키고 스트림을 닫습니다. 정답 해설: **지연 평가(Lazy Evaluation)**가 스트림의 핵심적인 특징이자 성능 최적화의 비결입니다. 예를 들어, list.stream().filter(s -> s.startsWith("a")).map(String::toUpperCase) 라는 코드가 있을 때, filter나 map은 즉시 실행되지 않습니다. 이들은 "이런 작업을 할 것이다"라는 계획만 세워둡니다. 이후 collect(Collectors.toList()) 같은 최종 연산이 호출되면, 그제서야 스트림은 데이터를 하나씩 흘려보내며 filter와 map을 한 번에 처리합니다. 이는 불필요한 중간 컬렉션을 생성하지 않고, 때로는 findFirst()처럼 모든 데이터를 처리할 필요가 없는 경우(short-circuiting) 연산을 조기에 중단하여 성능을 높이는 효과를 가져옵니다.


11. Object 클래스의 wait(), notify(), notifyAll() 메서드는 어떤 역할을 하며, synchronized 블록 안에서만 호출해야 하는 이유는 무엇인가요? (출제 확률: 높음)

정답: wait()과 notify()는 스레드 간의 협력을 위한 통신 메서드입니다. wait()은 현재 스레드를 대기시키며 락을 반납하고, notify()는 대기 중인 스레드 하나를 깨웁니다. 이들은 synchronized 블록 내에서만 호출해야 하는데, 이는 스레드 상태 변경과 락의 획득/반납이 원자적으로 처리되어야 경쟁 상태(Race Condition)를 막을 수 있기 때문입니다. 이를 어기면 IllegalMonitorStateException이 발생합니다. 정답 해설: 이 메커니즘은 고전적인 생산자-소비자(Producer-Consumer) 문제를 해결하는 데 사용됩니다. wait()과 notify()는 스레드가 특정 조건이 만족될 때까지 CPU 자원을 낭비하며 계속 확인(busy-waiting)하는 대신, 효율적으로 대기하고 통지받을 수 있는 메커니즘을 제공합니다. synchronized 블록 내에서 호출해야 하는 이유는 스레드 동기화의 핵심인 **'모니터 락(Monitor Lock)'**과 관련이 있습니다. wait()은 자신이 획득한 모니터 락을 놓고 대기 상태로 들어가는 것이고, notify()는 대기 중인 스레드에게 락을 얻을 기회를 주는 것입니다. 만약 락을 획득하지 않은 상태(synchronized 블록 밖)에서 wait()을 호출하면, 어떤 락을 놓아야 할지 알 수 없으므로 IllegalMonitorStateException이 발생합니다. 또한, wait() 호출 직전에 조건이 변경되고 notify()가 먼저 호출되는 경쟁 상태를 막기 위해, 조건 검사와 wait() 호출은 반드시 synchronized 블록으로 묶여 원자적으로 실행되어야 합니다.


12. 제네릭(Generics)의 타입 소거(Type Erasure)란 무엇이며, 이로 인해 어떤 제약이 발생하나요? (출제 확률: 높음)

정답: 타입 소거는 Java 컴파일러가 제네릭 코드를 컴파일할 때, <T>와 같은 타입 파라미터 정보를 제거하고 Object나 지정된 타입으로 변환하는 과정입니다. 이 때문에 런타임에는 제네릭 타입 정보를 알 수 없으며, new T()와 같이 제네릭 타입의 객체나 배열을 생성할 수 없는 등의 제약이 발생합니다. 정답 해설: 타입 소거는 제네릭을 사용하는 개발자가 반드시 이해해야 하는 핵심적인 제약사항입니다. 이 때문에 런타임에 제네릭 타입 정보가 필요한 경우, Class<T> 객체를 명시적으로 전달받거나 리플렉션을 사용하는 등의 우회적인 방법을 사용해야 합니다. 예를 들어, T 타입의 객체를 만들어야 한다면 new T() 대신, Class<T> 객체를 받아 clazz.getDeclaredConstructor().newInstance() 와 같이 리플렉션을 사용해야 합니다. 타입 소거는 제네릭을 "컴파일 시점의 타입 체크를 위한 문법적 설탕(Syntactic Sugar)"으로 만들었지만, 동시에 런타임의 유연성을 일부 제한하는 트레이드오프를 가집니다.


13. 어노테이션(Annotation)의 리텐션 정책(Retention Policy)에는 어떤 종류가 있으며, 각각 어떤 차이가 있나요? (출제 확률: 높음)

정답: 리텐션 정책은 어노테이션이 유지되는 시점을 결정합니다. SOURCE는 컴파일 시점에 사라지고, CLASS는 클래스 파일까지 남지만 런타임에는 로드되지 않습니다. RUNTIME은 프로그램 실행 중에도 유지되어 리플렉션을 통해 조회할 수 있습니다. Spring의 @Component나 JPA의 @Entity처럼 프레임워크가 런타임에 동작해야 하는 경우 RUNTIME 정책을 사용합니다. 정답 해설: 리텐션 정책은 어노테이션의 사용 목적과 직접적으로 연결됩니다. SOURCE는 "컴파일러에게만 필요한 정보"라는 의미입니다. 런타임에는 아무런 영향을 주지 않으므로, 성능에 전혀 부담이 없습니다. Lombok이 대표적인 예입니다. RUNTIME은 "프로그램 실행 중에 이 정보를 사용하겠다"는 의미입니다. Spring이 빈을 등록하거나 DI를 수행할 수 있는 것은, 리플렉션을 통해 RUNTIME 정책을 가진 어노테이션들을 읽을 수 있기 때문입니다. 따라서, 프레임워크가 런타임에 동적으로 무언가를 처리하게 만들고 싶다면 반드시 RUNTIME 정책을 사용해야 합니다.


14. try-with-resources 구문은 기존의 try-catch-finally에 비해 어떤 장점이 있나요? (출제 확률: 보통)

정답: try-with-resources는 AutoCloseable 자원을 자동으로 해제해주어, finally 블록과 자원 해제 관련 상용구 코드를 제거해 코드를 매우 간결하게 만듭니다. 또한, try 블록과 자원 해제 과정에서 모두 예외가 발생했을 때, 원본 예외 정보가 누락되지 않도록 '억제된 예외'로 함께 보고해주는 장점이 있습니다. 정답 해설: 기존 finally 블록에서 close()를 호출하는 코드는 생각보다 복잡하고 실수하기 쉽습니다. resource가 null이 아닌지 체크해야 하고, close() 자체도 IOException을 던질 수 있어 또다시 try-catch로 감싸야 합니다. 만약 이 중첩된 try-catch를 생략하면, close()에서 예외 발생 시 try 블록에서 발생했던 원래의 중요한 예외가 덮어씌워져 사라지는 문제가 발생합니다. try-with-resources는 이러한 상용구 코드(boilerplate code)를 JVM이 대신 처리해주고, close()에서 발생한 예외는 addSuppressed() 메서드를 통해 원래 예외에 '억제된 예외'로 추가해줍니다. 이는 개발자의 실수를 줄여 자원 누수(resource leak)를 효과적으로 방지하고, 예외 추적을 용이하게 하여 코드를 더욱 안전하고 간결하게 만들어주는 매우 유용한 기능입니다. 15. Java의 직렬화(Serialization) 가능한 클래스를 작성할 때, transient 키워드는 어떤 역할을 하나요? (출제 확률: 보통) 정답: transient 키워드는 특정 필드가 직렬화 과정에서 제외되도록 지정하는 역할을 합니다. 비밀번호와 같이 보안상 민감한 정보나, 굳이 저장할 필요가 없는 파생 필드에 사용하여 데이터 유출을 막거나 불필요한 데이터 저장을 방지합니다. 정답 해설: 직렬화는 객체의 모든 상태를 그대로 저장하는 강력한 기능이지만, 모든 정보를 저장하는 것이 항상 바람직한 것은 아닙니다. 예를 들어, 사용자의 비밀번호를 평문으로 파일이나 네트워크를 통해 전송하는 것은 심각한 보안 문제입니다. 이럴 때 password 필드를 transient로 선언하면, 직렬화 메커니즘이 이 필드를 의도적으로 무시하여 데이터가 외부로 유출되는 것을 막을 수 있습니다. 또한, 스레드 객체나 데이터베이스 커넥션 객체처럼 직렬화될 수 없거나 의미 없는 필드를 제외하는 데도 사용됩니다.


15. Java의 직렬화(Serialization) 가능한 클래스를 작성할 때, transient 키워드는 어떤 역할을 하나요? (출제 확률: 보통)

정답: transient 키워드는 특정 필드가 직렬화 과정에서 제외되도록 지정하는 역할을 합니다. 비밀번호와 같이 보안상 민감한 정보나, 굳이 저장할 필요가 없는 파생 필드에 사용하여 데이터 유출을 막거나 불필요한 데이터 저장을 방지합니다.

정답 해설: 직렬화는 객체의 모든 상태를 그대로 저장하는 강력한 기능이지만, 모든 정보를 저장하는 것이 항상 바람직한 것은 아닙니다. 예를 들어, 사용자의 비밀번호를 평문으로 파일이나 네트워크를 통해 전송하는 것은 심각한 보안 문제입니다. 이럴 때 password 필드를 transient로 선언하면, 직렬화 메커니즘이 이 필드를 의도적으로 무시하여 데이터가 외부로 유출되는 것을 막을 수 있습니다. 또한, 스레드 객체나 데이터베이스 커넥션 객체처럼 직렬화될 수 없거나 의미 없는 필드를 제외하는 데도 사용됩니다.


🌱 Spring / Spring Boot (15 Questions)

16. Spring Framework의 DI(Dependency Injection) 방식 중 생성자 주입을 권장하는 이유는 무엇인가요? (출제 확률: 매우 높음)

정답: 생성자 주입을 권장하는 이유는 크게 세 가지입니다. 첫째, final 키워드를 통해 의존성의 불변성을 보장할 수 있습니다. 둘째, 의존성이 주입되지 않으면 객체 생성이 불가능해 NPE를 방지합니다. 셋째, 순환 참조 문제를 애플리케이션 시작 시점에 바로 발견할 수 있어 안정성이 높습니다. 정답 해설: Spring 팀이 공식적으로 생성자 주입을 권장하는 이유는 **'안정적이고 예측 가능한 소프트웨어'**를 만드는 데 가장 유리하기 때문입니다. 필드 주입은 코드는 간결하지만, final 선언이 불가능하여 객체가 런타임에 변경될 수 있고, 순환 참조를 발견하지 못하며, 테스트 시 DI 컨테이너에 강하게 의존하게 되는 등 여러 단점이 있습니다. 생성자 주입은 약간의 코드가 더 필요하지만, 컴파일 시점과 애플리케이션 시작 시점에 오류를 최대한 많이 잡을 수 있도록 유도하여, 런타임에 발생할 수 있는 잠재적인 문제들을 예방해주는 가장 안전한 방식입니다. 17. @Transactional 어노테이션의 동작 원리와, 프록시(Proxy) 기반 AOP와 관련하여 발생하는 주의사항은 무엇인가요? (출제 확률: 매우 높음) 정답: @Transactional은 AOP를 통해 프록시 객체를 만들어 동작합니다. 외부에서 메서드를 호출하면 프록시가 트랜잭션을 시작하고 원본 메서드를 호출한 뒤, 결과에 따라 커밋 또는 롤백을 수행합니다. 이 때문에 프록시를 거치지 않는 내부 호출(self-invocation) 시에는 트랜잭션이 적용되지 않으며, private 같은 외부에서 호출할 수 없는 메서드에도 적용되지 않는 점을 주의해야 합니다. 정답 해설: @Transactional이 마법처럼 동작하는 비밀은 바로 AOP와 프록시에 있습니다. 이 원리를 모르면 내부 호출 시 트랜잭션이 적용되지 않는 문제를 이해할 수 없어 디버깅에 큰 어려움을 겪게 됩니다. someBean.methodA()를 호출하면, 실제로는 TransactionProxy의 methodA()가 호출됩니다. 이 프록시가 트랜잭션을 시작하고, OriginalBean의 methodA()를 호출합니다. 그런데 OriginalBean의 methodA() 내부에서 this.methodB()를 호출하면, 이는 OriginalBean 자기 자신의 methodB()를 직접 호출하는 것이므로, 트랜잭션을 관리하는 TransactionProxy를 다시 거칠 기회가 없습니다. 이것이 내부 호출이 동작하지 않는 이유입니다. 18. Spring Boot의 Auto-Configuration은 어떻게 동작하며, 특정 자동 구성을 제외하는 방법은 무엇인가요? (출제 확률: 매우 높음) 정답: Auto-Configuration은 클래스패스에 있는 라이브러리를 기반으로, @Conditional 어노테이션을 통해 특정 조건이 맞을 때 관련 빈들을 자동으로 설정해주는 기능입니다. 예를 들어, spring-boot-starter-data-jpa가 있으면 DataSource 관련 빈들을 자동으로 등록해줍니다. 특정 자동 구성을 제외하려면 @SpringBootApplication의 exclude 속성을 사용하거나, application.properties에 spring.autoconfigure.exclude 프로퍼티를 설정하면 됩니다. 정답 해설: 자동 구성의 핵심은 @Conditional 어노테이션입니다. 이는 **"특정 조건이 만족될 때만 이 설정을 활성화하라"**는 의미입니다. Spring Boot는 수많은 자동 구성 클래스를 미리 만들어두고, 개발자의 환경(클래스패스, 프로퍼티 설정 등)에 맞춰 필요한 것들만 골라서 활성화해줍니다. exclude 옵션은 이러한 자동 구성 후보 중에서 특정 클래스를 명시적으로 제외시키는 역할을 합니다. 예를 들어, Spring Boot가 자동으로 구성해주는 DataSource 대신, 개발자가 직접 커스텀한 DataSource를 사용하고 싶을 때, 자동 구성과의 충돌을 피하기 위해 DataSourceAutoConfiguration.class를 exclude 할 수 있습니다. 19. Filter, Interceptor, AOP의 차이점과 각각의 적절한 사용 사례를 비교 설명해주세요. (출제 확률: 매우 높음) 정답: 셋 모두 공통 기능을 처리하지만 동작 계층이 다릅니다. Filter는 Servlet 레벨에서 동작하여 전역적인 요청/응답 처리에 적합합니다. Interceptor는 Spring MVC 레벨에서 동작하여 컨트롤러와 관련된 인증/인가 등에 사용됩니다. AOP는 메서드 실행 레벨에서 동작하여 트랜잭션, 로깅 등 비즈니스 로직에 대한 부가 기능에 가장 적합합니다. 정답 해설: 이 셋을 구분하는 가장 좋은 방법은 **'적용 범위'와 '가져올 수 있는 정보'**를 기준으로 생각하는 것입니다. Filter는 HttpServletRequest, HttpServletResponse 객체만 다룰 수 있고 Spring의 내부 사정은 모릅니다. 가장 넓은 범위에 적용됩니다. Interceptor는 HandlerMethod 정보(어떤 컨트롤러의 어떤 메서드가 호출될지)나 ModelAndView 객체(컨트롤러 처리 결과)에 접근할 수 있어, MVC 흐름에 더 깊이 개입할 수 있습니다. AOP는 가장 좁고 깊은 범위에 적용됩니다. 특정 서비스 클래스의 특정 메서드 파라미터 값까지 들여다보고 조작할 수 있습니다. 따라서, 단순히 "로깅"이라는 기능을 구현하더라도, 모든 요청에 대한 원시 URL을 로깅하고 싶다면 Filter, 특정 API 컨트롤러의 성공/실패 여부를 로깅하고 싶다면 Interceptor, 특정 서비스 메서드의 파라미터와 실행 시간을 정밀하게 측정하고 싶다면 AOP를 사용하는 것이 적절합니다. 20. Spring Security의 아키텍처에서 SecurityContextHolder, Authentication, GrantedAuthority의 역할은 무엇인가요? (출제 확률: 매우 높음) 정답: SecurityContextHolder는 현재 인증된 사용자 정보를 보관하는 전역 저장소입니다. Authentication은 인증된 사용자의 주체(Principal)와 권한(Authorities)을 담고 있는 객체입니다. GrantedAuthority는 ROLE_USER와 같이 사용자에게 부여된 개별 권한 하나를 나타냅니다. 정답 해설: 이 세 가지는 Spring Security의 인증/인가 모델의 핵심입니다. 사용자가 로그인을 성공하면, Spring Security는 사용자의 ID와 DB에서 조회한 권한 목록을 가지고 Authentication 객체를 만듭니다. 그리고 이 Authentication 객체를 SecurityContextHolder에 저장합니다. 이후 사용자가 특정 URL에 접근할 때, Spring Security는 SecurityContextHolder에서 현재 사용자의 Authentication 객체를 꺼내, 그 안의 GrantedAuthority 목록을 확인합니다. 그리고 해당 URL에 접근하는 데 필요한 권한과 사용자가 가진 권한을 비교하여 접근 허용 여부(인가)를 결정합니다. 즉, Authentication은 '누구인지'와 '무엇을 할 수 있는지'를 모두 담고 있는 증명서이고, SecurityContextHolder는 이 증명서를 보관하는 지갑과 같습니다. 21. Spring에서 발생하는 순환 참조(Circular Dependency) 문제는 무엇이며, 생성자 주입 방식이 이 문제를 어떻게 해결(방지)하나요? (출제 확률: 매우 높음) 정답: 순환 참조는 둘 이상의 빈이 서로를 상호 참조하여 생성에 무한 루프가 발생하는 문제입니다. 생성자 주입 방식은 객체 생성 시점에 모든 의존성이 해결되어야 하므로, 이 문제를 애플리케이션 시작 시점에 BeanCurrentlyInCreationException을 발생시켜 즉시 감지하고 방지합니다. 정답 해설: Spring이 싱글톤 빈의 순환 참조를 해결해주는 메커니즘은 3단계 캐시(싱글톤 팩토리, 초기화된 싱글톤 객체, 생성 중인 싱글톤 객체)를 사용하는 복잡한 방식입니다. 하지만 이는 생성자가 아닌 수정자(setter) 주입이나 필드 주입 방식일 때만 제한적으로 동작합니다. 생성자 주입은 객체를 생성하는 시점에 모든 의존성이 준비되어야 한다는 원칙을 가지고 있습니다. 순환 참조 상황에서는 이 원칙이 깨지므로, 객체 생성이 원천적으로 불가능합니다. 따라서 Spring은 이를 '해결'하려 시도하는 대신, **'명확한 실패(Fail-Fast)'**를 통해 개발자에게 설계 오류를 알려줍니다. 이는 잠재적인 런타임 오류를 애플리케이션 시작 시점의 명확한 예외로 전환시켜주는, 생성자 주입의 매우 중요한 장점입니다. 22. Spring Test에서 @SpringBootTest와 @WebMvcTest의 차이점은 무엇인가요? (출제 확률: 매우 높음) 정답: @SpringBootTest는 통합 테스트용으로, 실제 애플리케이션처럼 모든 빈을 로드하여 무겁지만 전체 흐름을 테스트할 수 있습니다. 반면, @WebMvcTest는 웹 계층 슬라이스 테스트용으로, 컨트롤러 관련 빈만 로드하여 가볍고 빠릅니다. 따라서 컨트롤러 로직만 독립적으로 테스트할 때 사용하며, 서비스 계층은 @MockBean으로 대체해야 합니다. 정답 해설: 테스트는 목적에 맞게 범위를 격리하는 것이 중요합니다. 컨트롤러의 유효성 검사(Validation) 로직이나 JSON 직렬화/역직렬화 로직을 테스트하고 싶은데, DB 연결까지 포함하는 무거운 @SpringBootTest를 사용하는 것은 비효율적입니다. 이럴 때는 @WebMvcTest를 사용하여 웹 계층만 딱 잘라내어(slice) 테스트하는 것이 올바른 접근 방식입니다. 반면, 회원 가입부터 DB 저장까지 이어지는 전체 비즈니스 흐름을 검증하고 싶다면, 모든 계층이 실제 객체로 동작하는 @SpringBootTest가 필요합니다. 이처럼 두 어노테이션의 차이를 이해하고 상황에 맞게 사용하는 것은 효과적인 테스트 전략을 수립하는 데 필수적입니다. 23. Spring Bean의 생명주기(Lifecycle)에 대해 설명해주세요. (출제 확률: 높음) 정답: Spring Bean은 컨테이너 생성 → 빈 생성 → 의존성 주입 → 초기화 → 사용 → 소멸 단계를 거칩니다. 특히 중요한 것은 의존성 주입이 완료된 후 호출되는 초기화 콜백(예: @PostConstruct)과, 컨테이너 종료 시 호출되는 소멸 콜백(예: @PreDestroy)입니다. 이를 통해 특정 시점에 필요한 작업을 수행할 수 있습니다. 정답 해설: Bean의 생명주기를 이해하는 것은 특정 로직을 정확한 시점에 실행하기 위해 필수적입니다. 예를 들어, 의존성이 모두 주입된 후에 특정 초기화 작업을 수행해야 한다면, 생성자가 아닌 @PostConstruct가 붙은 초기화 메서드에서 수행해야 합니다. 생성자 시점에는 아직 다른 빈들이 주입되지 않았을 수 있기 때문입니다. 마찬가지로, 애플리케이션이 종료되기 직전에 사용하던 리소스(예: 소켓, 파일 핸들)를 안전하게 닫아야 한다면 @PreDestroy가 붙은 소멸 메서드가 적절한 위치입니다. 이처럼 생명주기 콜백은 개발자가 빈의 생성과 소멸 과정에 개입하여 필요한 작업을 수행할 수 있도록 해주는 중요한 인터페이스입니다. 24. Spring의 RestTemplate과 WebClient의 가장 큰 차이점은 무엇이며, 어떤 상황에서 WebClient를 사용해야 하나요? (출제 확률: 높음) 정답: 가장 큰 차이는 RestTemplate은 동기/블로킹 방식이고, WebClient는 비동기/논블로킹 방식이라는 점입니다. WebClient는 적은 수의 스레드로 많은 요청을 효율적으로 처리할 수 있어, 높은 동시성 처리가 필요하거나 여러 외부 API를 동시에 호출해야 하는 MSA 환경에 더 적합합니다. 정답 해설: RestTemplate의 문제는 스레드가 I/O를 기다리는 동안 아무 일도 하지 않고 낭비된다는 것입니다. 만약 100개의 외부 API를 동시에 호출해야 한다면, 100개의 스레드가 각각 응답을 기다리며 멈춰있게 됩니다. 이는 시스템 자원의 심각한 낭비입니다. WebClient는 논블로킹 I/O를 기반으로 합니다. 요청을 보낸 스레드는 결과를 기다리지 않고 즉시 다른 일을 하러 갑니다. 나중에 네트워크 I/O 작업이 완료되면, 이벤트 루프가 이를 감지하여 미리 등록된 콜백 함수를 실행시켜 결과를 처리합니다. 이처럼 스레드가 쉬지 않고 계속 일하게 만들어 시스템 전체의 처리 효율을 극대화하는 것이 WebClient와 리액티브 프로그래밍의 핵심입니다. 25. Spring Boot의 Actuator는 어떤 용도로 사용되며, 유용한 엔드포인트 몇 가지를 설명해주세요. (출제 확률: 높음) 정답: Actuator는 실행 중인 애플리케이션의 내부 상태를 모니터링하고 관리하기 위한 운영 기능들을 제공합니다. 유용한 엔드포인트로는 애플리케이션의 건강 상태를 보여주는 /health, JVM 메모리나 CPU 사용률 같은 다양한 지표를 제공하는 /metrics, 그리고 런타임에 로거 레벨을 변경할 수 있는 /loggers 등이 있습니다. 정답 해설: Actuator가 없다면, 애플리케이션이 현재 메모리를 얼마나 쓰는지, DB 연결은 정상인지 확인하기 위해 서버에 직접 접속하여 로그를 뒤지거나 jstat 같은 명령어를 사용해야 합니다. Actuator는 이러한 운영에 필수적인 정보들을 표준화된 HTTP 엔드포인트를 통해 매우 쉽게 접근할 수 있도록 해줍니다. 이는 특히 MSA나 클라우드 환경에서 매우 중요합니다. 로드 밸런서는 /actuator/health 엔드포인트를 주기적으로 호출하여 응답이 없는(DOWN 상태인) 인스턴스를 자동으로 서비스에서 제외시킬 수 있습니다. 또한, 모니터링 시스템은 /actuator/metrics 엔드포인트에서 정기적으로 데이터를 수집하여 대시보드에 시각화하고, 특정 임계치를 넘으면 알람을 보내는 등 자동화된 모니터링 및 관제 시스템을 구축하는 기반이 됩니다. 26. @SpringBootApplication 어노테이션은 어떤 세 가지 어노테이션으로 구성되어 있으며, 각각의 역할은 무엇인가요? (출제 확률: 높음) 정답: @SpringBootApplication은 @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan 세 가지의 조합입니다. @ComponentScan은 컴포넌트를 스캔하여 빈으로 등록하고, @EnableAutoConfiguration은 클래스패스 기반으로 필요한 설정을 자동으로 구성하며, @SpringBootConfiguration은 해당 클래스가 설정 클래스임을 나타냅니다. 정답 해설: 이 세 가지 어노테이션이 합쳐져 Spring Boot 애플리케이션의 기본적인 설정과 실행 환경을 구성합니다. 개발자는 복잡한 XML 설정 없이 @SpringBootApplication 어노테이션 하나만 메인 클래스에 붙이면 됩니다. @ComponentScan이 컴포넌트들을 찾아 빈으로 등록하고, @EnableAutoConfiguration이 라이브러리 의존성에 따라 필요한 프레임워크 빈들(예: DataSource, Tomcat)을 자동으로 등록해주며, @SpringBootConfiguration이 이 모든 과정의 중심이 되는 설정 클래스 역할을 합니다. 이처럼 "규약에 의한 설정(Convention over Configuration)" 철학을 통해, 개발자가 최소한의 설정만으로 빠르게 애플리케이션 개발을 시작할 수 있도록 돕는 것이 Spring Boot의 핵심적인 장점이며, @SpringBootApplication은 그 출발점입니다. 27. Spring ApplicationEvent는 어떤 경우에 사용하며, 사용하는 이유는 무엇인가요? (출제 확률: 높음) 정답: ApplicationEvent는 옵서버 패턴을 구현하여, 이벤트 발행자와 구독자 간의 결합도를 낮추기 위해 사용합니다. 예를 들어 '회원 가입' 서비스는 단순히 UserSignedUpEvent를 발행하고, '메일 발송'이나 '쿠폰 발급' 같은 후속 처리 로직은 해당 이벤트를 구독하는 별도의 리스너에서 독립적으로 처리하도록 하여 시스템의 유연성과 확장성을 높입니다. 정답 해설: ApplicationEvent를 사용하지 않으면, 회원 가입 서비스(UserService)는 메일 발송 서비스, 쿠폰 서비스, 알림 서비스를 직접 알고 의존해야 합니다(강한 결합). 이 경우, 쿠폰 발급 로직이 변경되면 UserService도 영향을 받게 되고, 새로운 후속 작업이 추가될 때마다 UserService 코드를 수정해야 합니다. ApplicationEvent를 사용하면 UserService는 오직 '회원 가입 이벤트를 발행한다'는 책임만 가지게 됩니다. 메일, 쿠폰, 알림 서비스는 UserService의 존재를 몰라도 되며, 오직 '회원 가입 이벤트'에만 의존합니다. 이처럼 관심사를 분리하고 결합도를 낮춤으로써, 시스템을 더 유연하고 확장 가능하며 유지보수하기 쉬운 구조로 만들 수 있습니다. 28. @Async 어노테이션의 동작 원리와 주의사항은 무엇인가요? (출제 확률: 높음) 정답: @Async는 AOP 프록시를 통해 메서드 호출을 가로채, 실제 실행을 별도의 스레드 풀(TaskExecutor)에 위임하여 비동기적으로 처리합니다. @Transactional과 마찬가지로 내부 호출 시에는 동작하지 않으며, 안정적인 운영을 위해 반드시 커스텀 스레드 풀을 설정하고, 비동기 예외 처리를 위한 AsyncUncaughtExceptionHandler를 구현하는 등의 주의가 필요합니다. 정답 해설: @Async는 시간이 오래 걸리는 작업을 백그라운드로 보내 현재 스레드의 블로킹을 막기 위해 사용됩니다. 예를 들어, 사용자의 API 요청에 대한 응답은 빠르게 보내주고, 시간이 오래 걸리는 데이터 처리나 이메일 발송 등은 @Async 메서드로 위임하여 백그라운드에서 처리하게 할 수 있습니다. 이 기능의 핵심은 TaskExecutor라는 스레드 풀입니다. @EnableAsync 어노테이션이 활성화되면 Spring은 기본 스레드 풀을 찾거나 생성하고, @Async 프록시는 모든 작업을 이 스레드 풀에 던지는 역할을 합니다. 따라서 안정적인 운영을 위해서는 서비스의 특성에 맞는 커스텀 스레드 풀을 반드시 설정해야 합니다. 29. 웹 애플리케이션에서 CORS(Cross-Origin Resource Sharing) 문제를 해결하는 방법은 무엇인가요? (출제 확률: 높음) 정답: CORS 문제는 다른 출처의 리소스 요청을 브라우저가 보안상 차단하기 때문에 발생합니다. Spring에서는 WebMvcConfigurer를 통해 전역적으로 CORS 정책을 설정하는 것이 가장 권장됩니다. 이를 통해 허용할 출처, 메서드, 헤더 등을 중앙에서 일관되게 관리할 수 있습니다. 간단하게는 @CrossOrigin 어노테이션을 사용할 수도 있습니다. 정답 해설: CORS의 핵심은 브라우저가 다른 출처로 API 요청을 보내기 전에, 먼저 **OPTIONS 메서드를 사용한 사전 요청(Preflight Request)**을 보내 서버가 해당 요청을 허용하는지 확인하는 과정에 있습니다. 서버는 이 사전 요청에 대한 응답으로 Access-Control-Allow-Origin, Access-Control-Allow-Methods 등의 헤더를 보내주어야 합니다. 만약 이 헤더 정보가 브라우저의 요청과 일치하지 않으면 브라우저는 실제 요청을 보내지 않고 CORS 오류를 발생시킵니다. Spring의 CORS 설정 방법들은 결국 이 사전 요청(Preflight)에 대한 올바른 응답 헤더를 자동으로 생성하고 설정해주는 역할을 합니다. 전역 설정을 권장하는 이유는, 여러 컨트롤러에 산재된 @CrossOrigin 어노테이션은 관리가 어렵고 정책 변경 시 모든 코드를 수정해야 하는 불편함이 있기 때문입니다. 30. @Configuration(proxyBeanMethods = true) 옵션은 어떤 의미를 가지나요? (출제 확률: 보통) 정답: proxyBeanMethods가 true(기본값)이면, @Configuration 클래스 내에서 @Bean 메서드를 여러 번 호출해도 항상 싱글톤으로 등록된 동일한 빈 인스턴스를 반환하도록 Spring이 보장합니다. false로 설정하면 프록시를 사용하지 않아 호출할 때마다 새로운 객체가 생성되므로, 빈 간의 의존관계가 없는 경우 성능 향상을 위해 false를 사용하기도 합니다. 정답 해설: 이 옵션은 빈 간의 의존 관계가 설정 클래스 내에서 직접 호출을 통해 이루어질 때 의미가 있습니다.

@Configuration(proxyBeanMethods = true)
public class AppConfig {
    @Bean
    public BeanA beanA() {
        return new BeanA(beanB()); // beanB() 직접 호출
    }
    @Bean
    public BeanB beanB() {
        return new BeanB();
    }
}

위 코드에서 proxyBeanMethods가 true이면, beanA() 내부에서 beanB()를 호출해도 Spring 컨테이너가 관리하는 싱글톤 BeanB 객체가 반환됩니다. 하지만 false라면, new BeanB()가 호출되어 새로운 BeanB 객체가 생성됩니다. 이는 의도치 않은 동작을 유발할 수 있습니다. 따라서 빈 간의 의존 관계가 있다면 true(기본값)를 유지해야 합니다. 하지만 최근에는 의존 관계를 파라미터로 주입받는 방식이 권장되므로, proxyBeanMethods를 false로 설정하여 불필요한 프록시 생성 오버헤드를 줄이는 것이 좋습니다.

🏛️ JPA / Hibernate (15 Questions)

  1. JPA의 영속성 컨텍스트(Persistence Context)는 무엇이며, 1차 캐시와 쓰기 지연, 변경 감지는 어떻게 동작하나요? (출제 확률: 매우 높음) 정답: 영속성 컨텍스트는 엔티티를 관리하는 논리적인 영역으로, 트랜잭션 범위 내에서 유지됩니다. 1차 캐시를 통해 같은 트랜잭션 내에서 엔티티의 동일성을 보장하고, 쓰기 지연 기능으로 SQL을 모았다가 트랜잭션 커밋 시 한 번에 전송하여 I/O를 최적화합니다. 또한, 변경 감지(Dirty Checking) 기능으로 객체의 상태 변경만으로 자동으로 UPDATE 쿼리를 생성해줍니다. 정답 해설: 영속성 컨텍스트는 객체지향 프로그래밍과 관계형 데이터베이스 사이의 패러다임 불일치를 해결해주는 핵심적인 중간 계층입니다. 개발자는 SQL 중심이 아닌 객체 중심으로 코드를 작성할 수 있게 됩니다. 예를 들어, 변경 감지 덕분에 개발자는 UPDATE 쿼리를 직접 작성할 필요 없이, 단순히 객체의 상태를 변경(user.setName("new"))하는 것만으로 DB에 데이터가 자동으로 반영되는 편리함을 누릴 수 있습니다. 쓰기 지연은 매번 쿼리를 실행하는 대신 버퍼링을 통해 DB I/O를 최적화하는 역할을 합니다. 이러한 기능들은 모두 하나의 트랜잭션 범위 내에서 영속성 컨텍스트를 통해 동작합니다.
  2. 연관관계 매핑 시 즉시 로딩(Eager Loading) 대신 지연 로딩(Lazy Loading)을 사용해야 하는 이유는 무엇이며, N+1 문제는 어떻게 해결하나요? (출제 확률: 매우 높음) 정답: 모든 연관관계는 지연 로딩(LAZY)을 기본 원칙으로 해야 합니다. 즉시 로딩(EAGER)은 사용하지도 않을 데이터를 조회하여, 특히 JPQL 사용 시 N+1 문제를 유발하기 때문입니다. N+1 문제는 Fetch Join을 사용하여 연관된 엔티티를 한 번의 쿼리로 함께 조회하는 것이 가장 효과적인 해결책입니다. 정답 해설: 즉시 로딩의 가장 큰 위험은 개발자가 예측하지 못한 SQL이 실행된다는 점입니다. memberRepository.findAll()처럼 단순한 조회 메서드를 호출했는데, Member가 Team을 EAGER로 참조한다면 뒤에서는 회원 수만큼의 팀 조회 쿼리가 추가로 발생하여 시스템에 심각한 성능 저하를 일으킬 수 있습니다. 지연 로딩은 이러한 숨겨진 동작을 막고, 연관 엔티티가 필요할 때만 조회하도록 하여 예측 가능성을 높입니다. 그리고 정말로 연관 엔티티가 처음부터 필요하다면, Fetch Join을 통해 개발자가 명시적으로 "나는 이 데이터들을 한 번에 가져오겠다"고 선언하는 것이 올바른 접근 방식입니다. 이는 실행되는 쿼리를 개발자가 완벽하게 통제할 수 있게 해줍니다.
  3. 양방향 연관관계에서 연관관계의 주인(Owner)은 무엇이며, 편의 메서드를 작성하는 이유는 무엇인가요? (출제 확률: 매우 높음) 정답: 연관관계의 주인은 외래 키(FK)를 관리할 책임이 있는 쪽으로, 보통 @ManyToOne이 있는 쪽입니다. 주인이 아닌 쪽에는 mappedBy 속성을 명시해야 합니다. 편의 메서드는 양쪽 엔티티에 모두 관계를 설정해주는 작업을 하나의 메서드로 묶어, 객체 상태와 DB 상태의 불일치를 막고 개발자의 실수를 방지하기 위해 작성합니다. 정답 해설: JPA는 연관관계의 주인 쪽 엔티티의 값으로만 외래 키를 등록/수정합니다. mappedBy가 설정된 주인 아닌 쪽의 컬렉션을 아무리 변경해도 외래 키에는 아무런 영향을 주지 않습니다. 이것이 주인의 개념이 중요한 이유입니다. 하지만 객체지향 관점에서는 양쪽 모두에서 관계를 탐색할 수 있어야 자연스럽습니다. 만약 member.setTeam(team)만 호출하고 team.getMembers().add(member)를 누락하면, DB에는 FK가 정상적으로 저장되지만, 해당 트랜잭션 내에서 team.getMembers()를 조회했을 때 방금 추가한 member가 보이지 않는 객체 상태 불일치가 발생합니다. 편의 메서드는 this.members.add(member); member.setTeam(this); 와 같이 양쪽의 관계를 모두 설정하여 이러한 불일치를 원천적으로 차단하고 코드의 안정성을 높여줍니다.
  4. JPA의 변경 감지(Dirty Checking) 기능의 장단점과, 읽기 전용 쿼리에서 성능을 최적화하는 방법은 무엇인가요? (출제 확률: 매우 높음) 정답: 변경 감지는 객체 상태 변경만으로 UPDATE가 가능해 생산성이 높은 장점이 있지만, 스냅샷 유지를 위한 메모리 사용과 비교 연산 오버헤드라는 단점이 있습니다. 데이터 변경이 없는 읽기 전용 쿼리에서는 @Transactional(readOnly = true) 옵션을 사용하여, 스냅샷 생성과 변경 감지 자체를 생략함으로써 성능을 최적화할 수 있습니다. 정답 해설: 변경 감지는 개발자의 편의성과 성능 오버헤드 사이의 트레이드오프입니다. 대부분의 경우 이 편의성이 더 크지만, 수천 개의 엔티티를 조회하여 단순히 화면에 보여주기만 하는 로직에서는 변경 감지 오버헤드가 부담이 될 수 있습니다. **@Transactional(readOnly = true)**는 JPA에게 **"이 트랜잭션에서는 데이터 변경이 절대 없으니, 변경 감지를 위한 스냅샷 생성이나 비교 등 비싼 작업을 모두 생략해도 좋다"**고 알려주는 힌트입니다. 이를 통해 불필요한 메모리 사용과 연산을 줄여, 순수 조회 기능의 성능을 크게 개선할 수 있습니다.
  5. 엔티티를 DTO로 변환하여 반환해야 하는 이유는 무엇이며, 조회 성능을 고려한 최적의 변환 방식은 무엇인가요? (출제 확률: 매우 높음) 정답: 엔티티를 DTO로 변환하는 이유는 API 스펙과 도메인 모델을 분리하여 유연성과 안정성을 확보하고, 순환 참조나 데이터 노출 문제를 방지하기 위함입니다. 조회 성능을 최적화하려면, JPQL의 NEW 연산자를 사용하여 처음부터 필요한 필드만으로 DTO를 직접 조회하는 방식이 가장 좋습니다. 정답 해설: 엔티티를 직접 반환하는 것은 View 계층이 Model 계층을 직접 침범하는 것과 같아, 계층 간의 책임 분리 원칙을 위배합니다. 이는 시스템을 경직되고 유지보수하기 어렵게 만듭니다. 최적의 변환 방식으로 JPQL 생성자 표현식이 꼽히는 이유는, 일반적인 방식(엔티티 조회 후 DTO 변환)과 달리 DB에서부터 애플리케이션까지 데이터가 흘러오는 파이프라인 전체를 최적화하기 때문입니다. DB는 꼭 필요한 컬럼만 읽고, 네트워크로는 꼭 필요한 데이터만 전송되며, 애플리케이션은 영속성 컨텍스트에 불필요한 엔티티를 로드할 필요가 없어집니다. 이는 시스템 자원을 가장 효율적으로 사용하는 방법입니다.
  6. JPA에서 동시성 제어를 위한 잠금(Lock) 방식 중, 낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)의 차이점은 무엇인가요? (출제 확률: 매우 높음) 정답: 비관적 락은 실제 DB 락(SELECT ... FOR UPDATE)을 사용하여 데이터 접근을 원천적으로 막는 방식입니다. 반면, 낙관적 락은 DB 락을 사용하지 않고, 엔티티에 @Version 필드를 두어 커밋 시점에 데이터 변경 여부를 확인하는 방식입니다. 충돌이 빈번하면 비관적 락, 그렇지 않으면 낙관적 락이 유리합니다. 정답 해설: 비관적 락은 데이터 정합성이 매우 중요하고 충돌이 빈번하게 예상되는 작업(예: 계좌 이체, 재고 차감)에 적합합니다. 하지만 락을 기다리는 동안 다른 트랜잭션들이 모두 대기해야 하므로 시스템 전체의 처리량(Throughput)이 낮아질 수 있습니다. 낙관적 락은 실제 락을 사용하지 않으므로 비관적 락보다 성능이 좋습니다. 조회 작업이 대부분이고 가끔 수정 작업이 일어나는 시스템에 적합합니다. JPA에서는 엔티티에 @Version 어노테이션을 붙인 필드를 추가하는 것만으로 낙관적 락을 쉽게 구현할 수 있습니다. 충돌이 발생하면 예외를 던지므로, 개발자는 이 예외를 잡아서 재시도를 하거나 사용자에게 알리는 등의 후속 처리를 구현해야 합니다.
  7. OSIV(Open Session In View) 전략이란 무엇이며, 어떤 장단점이 있나요? (출제 확률: 매우 높음) 정답: OSIV는 영속성 컨텍스트의 생존 범위를 View 렌더링 시점까지 열어두는 전략입니다. 컨트롤러에서 지연 로딩이 가능해져 개발이 편리한 장점이 있지만, DB 커넥션을 너무 오래 점유하여 커넥션 풀 고갈을 유발할 수 있는 심각한 단점이 있습니다. 이 때문에 최근에는 OSIV를 끄는 것을 권장합니다. 정답 해설: OSIV는 과거에 개발 편의성을 위해 도입된 전략입니다. 트랜잭션이 끝난 서비스 계층에서 엔티티를 반환하면, 영속성 컨텍스트가 닫혀 더 이상 지연 로딩이 불가능해집니다. 이 상태에서 View 템플릿(Thymeleaf 등)이 지연 로딩 관계의 필드에 접근하면 LazyInitializationException이 발생합니다. OSIV는 이 문제를 해결하기 위해 요청이 끝날 때까지 영속성 컨텍스트를 살려두는 것입니다. 하지만 이는 컨트롤러나 뷰 계층에서 DB 커넥션을 사용하는 쿼리가 나갈 수 있다는 심각한 문제를 야기합니다. DB 커넥션은 매우 소중한 자원이므로, 가능한 한 짧게 사용하고 반납해야 합니다. OSIV는 커넥션 점유 시간을 불필요하게 늘려 대규모 서비스에서는 치명적인 단점이 됩니다. 따라서 지연 로딩은 반드시 트랜잭션 안에서 Fetch Join이나 DTO를 사용하여 해결하는 것이 올바른 설계 방향입니다.
  8. CascadeType과 orphanRemoval=true의 차이점은 무엇인가요? (출제 확률: 높음) 정답: CascadeType은 부모의 영속성 작업(persist, remove 등)을 자식에게 전이시키는 것입니다. 반면, orphanRemoval=true는 부모의 컬렉션에서 자식이 제거되면, '고아'가 된 자식을 DB에서도 자동으로 DELETE하는 기능입니다. 즉, CascadeType.REMOVE는 부모가 삭제될 때, orphanRemoval은 부모와의 관계가 끊어질 때 자식이 삭제됩니다. 정답 해설: 동작의 주체 차이: CascadeType은 **부모 엔티티의 영속성 상태가 '전이'**되는 것입니다. 즉, em.persist(parent)를 하면 자식에게도 persist가 일어나는 식입니다. 반면, orphanRemoval은 **부모 엔티티의 컬렉션 조작이 '트리거'**가 되어 자식 엔티티의 삭제를 유발합니다. 예를 들어 parent.getChildren().remove(child)라는 코드를 실행하면, 이 child는 '고아'가 되었다고 판단하여 JPA가 자동으로 DELETE 쿼리를 실행합니다. 사용 시나리오: CascadeType.ALL과 orphanRemoval=true를 모두 사용하면, 부모 엔티티를 통해 자식의 생명주기를 완벽하게 관리할 수 있습니다. 예를 들어, 게시물(Parent)과 첨부파일(Child) 관계에서, 게시물을 저장하면 첨부파일도 함께 저장되고, 게시물에서 특정 첨부파일을 제거하면 DB에서도 삭제되며, 게시물을 삭제하면 모든 첨부파일이 함께 삭제되도록 만들 수 있습니다.
  9. JPA의 Auditing 기능은 어떻게 구현하나요? (출제 확률: 높음) 정답: JPA Auditing은 @EnableJpaAuditing으로 기능을 활성화하고, 공통 필드를 담은 BaseEntity 클래스에 @MappedSuperclass와 @EntityListeners를 붙여 구현합니다. 그리고 BaseEntity 내의 필드에 @CreatedDate, @LastModifiedDate 어노테이션을 붙이면, 엔티티 저장 및 수정 시 해당 필드에 시간이 자동으로 기록됩니다. 정답 해설: @EnableJpaAuditing을 통해 활성화된 Auditing 기능은, @EntityListeners(AuditingEntityListener.class)가 붙은 엔티티의 영속성 이벤트를 감지합니다. 엔티티가 저장(persist)될 때, AuditingEntityListener는 @CreatedDate, @LastModifiedDate 어노테이션이 붙은 필드를 찾아 현재 시간을 자동으로 채워줍니다. 엔티티가 수정(update)될 때는 @LastModifiedDate 필드의 값만 현재 시간으로 업데이트합니다. @MappedSuperclass는 이 BaseEntity가 테이블로 매핑되는 엔티티가 아니라, 자식 엔티티에게 필드 정보만 상속해주기 위한 클래스임을 명시하는 중요한 어노테이션입니다. 이를 통해 여러 엔티티에서 공통적인 필드(생성일, 수정일 등)를 코드 중복 없이 관리할 수 있습니다.
  10. JPQL이란 무엇이며, SQL 및 QueryDSL과 어떻게 다른가요? (출제 확률: 높음) 정답: **JPQL(Java Persistence Query Language)**은 테이블이 아닌 엔티티 객체를 대상으로 하는 객체지향 쿼리 언어입니다. SQL과 문법이 유사하지만, 특정 데이터베이스에 종속되지 않습니다. SQL은 데이터베이스 테이블을 직접 조회하는 언어이며, QueryDSL은 JPQL을 자바 코드로 타입-세이프하게 작성할 수 있도록 돕는 쿼리 빌더 라이브러리입니다. 정답 해설: JPQL vs SQL: 가장 큰 차이는 쿼리 대상입니다. SQL은 SELECT * FROM member처럼 테이블과 컬럼 이름을 직접 사용하지만, JPQL은 SELECT m FROM Member m처럼 엔티티 클래스와 필드 이름을 사용합니다. JPQL은 최종적으로 각 데이터베이스 방언(Dialect)에 맞는 SQL로 변환되어 실행되므로, DB에 비종속적인 코드를 작성할 수 있습니다. JPQL vs QueryDSL: JPQL은 문자열 기반이므로, "SELECT m FROM Membr m"과 같이 오타가 있어도 컴파일 시점에는 오류를 잡을 수 없고 런타임에 예외가 발생합니다. QueryDSL은 이러한 JPQL을 QMember member = QMember.member; query.select(member).from(member)와 같이 자바 코드로 작성하게 해줍니다. 이를 통해 컴파일 시점에 타입 체크와 문법 오류를 모두 잡을 수 있어 코드의 안정성이 획기적으로 향상됩니다.
  11. 엔티티의 생명주기 상태(New/Transient, Managed, Detached, Removed)에 대해 설명해주세요. (출제 확률: 높음) 정답: 엔티티는 영속성 컨텍스트와의 관계에 따라 4가지 상태를 가집니다. New/Transient (비영속): 영속성 컨텍스트와 전혀 관계가 없는, 새로 생성된 객체 상태입니다. Managed (영속): em.persist() 등을 통해 영속성 컨텍스트에 의해 관리되는 상태입니다. 변경 감지, 1차 캐시 등의 기능이 적용됩니다. Detached (준영속): 영속성 컨텍스트에 저장되었다가 분리된 상태입니다. em.detach()나 em.close()로 만들 수 있으며, 더 이상 JPA의 관리를 받지 못합니다. Removed (삭제): em.remove()를 통해 삭제된 상태입니다. 트랜잭션이 커밋되면 DB에서도 삭제됩니다. 정답 해설: 이 상태를 이해하는 것은 JPA가 어떻게 동작하는지 파악하는 데 매우 중요합니다. 오직 Managed 상태의 엔티티만이 **변경 감지(Dirty Checking)**의 대상이 됩니다. 즉, Detached 상태의 객체는 아무리 값을 변경해도 트랜잭션 커밋 시점에 UPDATE 쿼리가 실행되지 않습니다. Detached 상태의 객체를 다시 영속화하려면 em.merge() 메서드를 사용해야 합니다. merge()는 준영속 상태의 객체를 받아 새로운 영속 상태의 객체를 반환하고, 그 객체의 값으로 DB를 업데이트합니다. LazyInitializationException은 보통 트랜잭션이 끝나 영속성 컨텍스트가 닫히면서 엔티티가 Detached 상태가 된 후, 지연 로딩 대상 필드에 접근할 때 발생합니다.
  12. JPA에서 persist()와 merge() 메서드의 차이점은 무엇인가요? (출제 확률: 높음) 정답: persist()는 비영속(New/Transient) 상태의 객체를 영속(Managed) 상태로 만드는 데 사용합니다. 만약 이미 영속성 컨텍스트에 의해 관리되는 객체나 준영속 상태의 객체에 persist()를 호출하면 예외가 발생합니다. 반면, merge()는 준영속(Detached) 상태의 객체를 받아, 그 정보로 새로운 영속 객체를 생성하여 반환하고 DB에 병합(UPDATE 또는 INSERT)하는 역할을 합니다. 정답 해설: 두 메서드의 가장 큰 차이는 파라미터로 받은 객체를 영속화하는지 여부입니다. em.persist(entity): entity 객체 그 자체가 영속성 컨텍스트의 관리를 받게 됩니다. em.merge(detachedEntity): detachedEntity는 영속화되지 않습니다. 대신, JPA는 detachedEntity의 식별자(ID)로 1차 캐시나 DB에서 영속 엔티티를 찾고, detachedEntity의 값으로 그 영속 엔티티의 내용을 채워 넣습니다. 만약 영속 엔티티가 없다면 새로운 영속 엔티티를 생성합니다. 그리고 새롭게 생성되거나 조회된 영속 엔티티를 반환합니다. 따라서 merge() 이후에는 반드시 반환된 객체를 사용해야 변경 감지 등의 기능을 제대로 활용할 수 있습니다. mergedEntity = em.merge(detachedEntity);
  13. JPA의 값 타입(Value Type)과 임베디드 타입(@Embeddable)은 무엇이며 왜 사용하나요? (출제 확률: 높음) 정답: 값 타입은 엔티티와 달리 식별자(ID)를 갖지 않고, 독립적으로 존재할 수 없으며, 생명주기를 엔티티에 의존하는 타입입니다. **임베디드 타입(@Embeddable)**은 이러한 값 타입을 정의하는 방법으로, 주소(시, 구, 상세주소)나 기간(시작일, 종료일)처럼 의미적으로 관련된 여러 필드를 하나의 클래스로 묶어 재사용하고, 엔티티의 코드를 더 객체지향적이고 응집도 높게 만들기 위해 사용합니다. 정답 해설: 임베디드 타입을 사용하지 않는다면, Member 엔티티는 city, street, zipcode 같은 주소 관련 필드들을 모두 직접 가지고 있어야 합니다. 이는 엔티티를 비대하게 만들고, 주소 정보가 다른 엔티티에서도 필요할 경우 코드 중복을 유발합니다. @Embeddable을 사용하여 Address 클래스를 만들고, Member 엔티티에서 @Embedded private Address address; 와 같이 사용하면, 객체지향적으로는 Address라는 응집도 있는 객체를 사용하면서, DB 테이블에는 city, street, zipcode 컬럼이 그대로 펼쳐져(embedded) 매핑됩니다. 이를 통해 코드의 재사용성과 가독성을 높이고, 도메인을 더 풍부하게 모델링할 수 있습니다. 값 타입은 불변 객체(Immutable Object)로 설계하는 것이 안전하며, 공유 참조로 인한 부작용을 막을 수 있습니다.
  14. @JoinColumn 어노테이션은 어떤 역할을 하며, 주요 속성에는 무엇이 있나요? (출제 확률: 높음) 정답: @JoinColumn은 외래 키(Foreign Key)를 매핑할 때 사용하는 어노테이션입니다. 주로 @ManyToOne이나 @OneToOne 관계에서 연관관계의 주인이 외래 키를 관리하는 컬럼을 지정하기 위해 사용됩니다. 주요 속성은 다음과 같습니다. name: 매핑할 외래 키 컬럼의 이름을 지정합니다. (기본값: 필드명 + "_" + 참조하는 테이블의 PK 컬럼명) referencedColumnName: 외래 키가 참조하는 대상 테이블의 컬럼 이름을 지정합니다. (기본값: 대상 테이블의 PK 컬럼명) nullable, unique, insertable, updatable: 외래 키 컬럼에 대한 제약 조건을 설정합니다. 정답 해설: @JoinColumn은 객체지향 모델의 연관관계를 데이터베이스의 외래 키와 연결해주는 다리 역할을 합니다. 이 어노테이션을 통해 개발자는 외래 키 컬럼의 이름을 명시적으로 제어하고, nullable=false와 같은 제약조건을 추가하여 데이터 무결성을 높일 수 있습니다. 만약 @JoinColumn을 생략하면, JPA는 기본 전략에 따라 외래 키 컬럼을 자동으로 생성하지만, 개발자가 의도한 컬럼명과 다를 수 있으므로 명시적으로 지정해주는 것이 좋습니다.
  15. JPA에서 상속 관계 매핑 전략 세 가지(조인, 단일 테이블, 테이블별 클래스)를 비교 설명해주세요. (출제 확률: 보통) 정답: 상속 관계 매핑 전략은 조인, 단일 테이블, 테이블별 클래스 세 가지가 있습니다. 조인 전략은 정규화 수준이 높지만 조인 비용이 발생하고, 단일 테이블 전략은 조회 성능은 가장 좋지만 불필요한 null 컬럼이 많이 생깁니다. 테이블별 클래스 전략은 거의 사용되지 않습니다. 실무에서는 보통 데이터의 특성을 고려하여 조인 전략과 단일 테이블 전략 중에서 선택합니다. 정답 해설: 각 전략은 정규화(데이터 중복 최소화)와 성능 사이의 트레이드오프를 가집니다. 조인 전략: 데이터 무결성이 가장 중요할 때 좋습니다. 각 클래스에 맞는 테이블이 존재하므로 스키마가 명확하고 정규화 수준이 높습니다. 단일 테이블 전략: 조회 성능이 가장 중요하고, 상속 계층이 복잡하지 않을 때 유리합니다. 조인이 전혀 발생하지 않기 때문입니다. 하지만 모든 자식의 컬럼이 한 테이블에 모이므로, NOT NULL 제약조건을 걸기 어렵고 저장 공간이 낭비될 수 있습니다. 실무에서는 데이터의 특성과 조회 패턴을 고려하여 조인 전략과 단일 테이블 전략 중에서 적절한 것을 선택하게 됩니다.

💾 Database (15 Questions)

46. 데이터베이스 인덱스를 사용하는 것이 오히려 성능을 저하시키는 경우는 어떤 경우인가요? (출제 확률: 매우 높음)

정답: 인덱스는 CUD(쓰기) 작업이 매우 빈번하거나, 카디널리티가 매우 낮은 컬럼에 적용될 경우 오히려 성능을 저하시킵니다. 또한, 데이터가 적은 테이블이나, WHERE 절에서 인덱스 컬럼을 가공하여 조회하는 경우에도 인덱스를 타지 못해 비효율적일 수 있습니다. 정답 해설: 인덱스의 본질은 '탐색할 데이터의 범위를 얼마나 효과적으로 줄일 수 있는가' 입니다. 데이터가 적거나, 인덱스를 타더라도 대부분의 데이터를 읽어야 한다면(카디널리티가 낮은 경우) 굳이 인덱스를 거쳐가는 오버헤드를 감수할 이유가 없습니다. 또한, INSERT/UPDATE/DELETE 작업은 데이터뿐만 아니라 인덱스도 함께 수정하는 비용을 발생시킨다는 점을 항상 기억해야 합니다. 인덱스는 '공짜'가 아니며, 조회 성능과 쓰기 성능 사이의 트레이드오프 관계에 있습니다. 따라서 서비스의 데이터 특성과 쿼리 패턴(읽기/쓰기 비율)을 종합적으로 분석하여 신중하게 생성해야 합니다. 47. 트랜잭션의 ACID 특성에 대해 각각 설명해주세요. (출제 확률: 매우 높음) 정답: ACID는 트랜잭션의 안전성을 보장하는 네 가지 성질입니다. **원자성(Atomicity)**은 전부 성공 또는 전부 실패, **일관성(Consistency)**은 트랜잭션 후에도 데이터베이스 제약 조건 유지, **고립성(Isolation)**은 트랜잭션 간의 상호 불간섭, **지속성(Durability)**은 성공한 트랜잭션 결과의 영구 저장을 의미합니다. 정답 해설: ACID는 데이터의 무결성과 신뢰성을 보장하기 위한 데이터베이스 시스템의 근본적인 약속입니다. 원자성은 COMMIT과 ROLLBACK으로 보장됩니다. 일관성은 애플리케이션 레벨과 데이터베이스의 제약 조건을 통해 개발자가 보장해야 하는 측면이 강합니다. 고립성은 동시성 제어를 위한 잠금(Locking)과 MVCC 메커니즘을 통해 데이터베이스가 보장합니다. 지속성은 변경 사항을 실제 데이터 파일에 쓰기 전에 로그(Write-Ahead Logging, WAL)에 먼저 기록하여, 장애가 발생해도 로그를 기반으로 데이터를 복구함으로써 보장됩니다. 이 네 가지 특성 덕분에 개발자들은 복잡한 동시성 제어나 장애 복구를 직접 고민하지 않고, 비즈니스 로직에 집중할 수 있습니다. 48. 데이터베이스 락(Lock)에는 어떤 종류가 있으며, 공유 락(Shared Lock)과 배타 락(Exclusive Lock)은 어떻게 다른가요? (출제 확률: 매우 높음) 정답: **공유 락(Shared Lock)**은 데이터를 읽기 위해 사용하며, 여러 트랜잭션이 동시에 가질 수 있습니다. **배타 락(Exclusive Lock)**은 데이터를 쓰기 위해 사용하며, 오직 하나의 트랜잭션만 가질 수 있고 다른 모든 락과 상호 배타적입니다. 즉, "읽는 것끼리는 가능하지만, 쓰는 것은 혼자만 가능하다"는 규칙입니다. 정답 해설: 공유 락과 배타 락의 관계는 다음과 같이 요약할 수 있습니다. "읽는 것끼리는 서로 방해하지 않지만, 누군가 쓰려고 하면 아무도 읽거나 쓸 수 없고, 누군가 읽고 있다면 아무도 쓸 수 없다." 이 메커니즘을 통해 데이터의 일관성을 보장합니다. 예를 들어, 트랜잭션 A가 데이터를 읽기 위해 공유 락을 걸면, 트랜잭션 B도 해당 데이터를 읽을 수는 있지만(공유 락 획득 가능), 수정할 수는 없습니다(배타 락 획득 대기). 만약 트랜잭션 A가 데이터를 수정하기 위해 배타 락을 걸면, 트랜잭션 B는 해당 데이터를 읽는 것조차 할 수 없게 됩니다(공유 락 획득 대기). 이처럼 락은 동시성을 일부 희생하여 데이터 정합성을 확보하는 중요한 도구입니다. 49. SQL 쿼리의 실행 계획(Execution Plan)을 분석하는 이유는 무엇이며, 어떤 정보를 얻을 수 있나요? (출제 확률: 매우 높음) 정답: 실행 계획은 DB 옵티마이저가 쿼리를 어떻게 실행할지 계획한 절차로, 쿼리 성능 튜닝을 위해 분석합니다. 실행 계획을 통해 테이블 접근 방식(Full Scan/Index Scan), 사용하는 인덱스, 조인 순서와 방식 등의 정보를 얻을 수 있으며, 이를 통해 쿼리의 병목 지점을 찾아낼 수 있습니다. 정답 해설: 쿼리 튜닝은 감으로 하는 것이 아니라 실행 계획이라는 데이터를 기반으로 해야 합니다. "느린 것 같다"는 느낌만으로는 정확한 원인을 알 수 없습니다. EXPLAIN 명령어를 통해 실행 계획을 확인했을 때, type 컬럼에 ALL(Full Table Scan)이 표시되거나, Extra 컬럼에 Using filesort(인덱스를 사용하지 못하는 정렬) 등이 나타난다면 쿼리에 문제가 있다는 명확한 신호입니다. 개발자는 이 정보를 바탕으로 불필요한 인덱스를 제거하거나 새로운 인덱스를 추가하고, 쿼리 구조를 변경하는 등 구체적인 튜닝 작업을 수행할 수 있습니다. 실행 계획 분석은 느린 SQL의 원인을 과학적으로 진단하는 가장 기본적인 수단입니다. 50. 데이터베이스 복제(Replication)는 어떻게 동작하며, 어떤 목적으로 사용되나요? (출제 확률: 매우 높음) 정답: 복제는 Master DB의 데이터 변경 내역(Binary Log)을 Slave DB들이 전달받아 동일하게 적용함으로써 데이터를 동기화하는 기술입니다. 주된 목적은 읽기/쓰기 분리를 통한 부하 분산과, Master 장애 시 Slave를 승격시키는 고가용성 확보입니다. 정답 해설: 복제는 읽기 작업이 많은(Read-heavy) 웹 서비스의 성능과 안정성을 높이는 핵심 기술입니다. 대부분의 서비스는 쓰기보다 읽기 요청이 압도적으로 많기 때문에, 읽기 요청을 처리하는 서버(Slave)를 수평적으로 확장(Scale-out)하는 것만으로도 시스템 전체의 처리량을 크게 향상시킬 수 있습니다. Spring에서는 RoutingDataSource 등을 구현하여, 서비스 계층에서 @Transactional(readOnly = true)가 붙은 메서드는 Slave DB로, 그렇지 않은 메서드는 Master DB로 요청을 자동으로 분기하도록 구성할 수 있습니다. 이를 통해 **읽기/쓰기 분리(Read/Write Splitting)**를 애플리케이션 레벨에서 구현할 수 있습니다. 51. 인덱스에서 복합 인덱스(Composite Index)의 컬럼 순서는 왜 중요한가요? (출제 확률: 매우 높음) 정답: 복합 인덱스는 첫 번째 컬럼부터 순서대로 정렬되기 때문에 컬럼 순서가 매우 중요합니다. 쿼리의 WHERE 절에 인덱스의 첫 번째 컬럼이 조건으로 포함되지 않으면 인덱스를 효과적으로 사용할 수 없습니다. 따라서 자주 사용되고 선택도가 높은 컬럼을 앞 순서에 배치해야 합니다. 정답 해설: 복합 인덱스를 전화번호부에 비유할 수 있습니다. (성, 이름) 순서로 정렬된 전화번호부에서 '김씨'를 찾는 것은 쉽습니다. '김씨' 중에서 '철수'를 찾는 것도 쉽습니다. 하지만 아무런 조건 없이 '철수'라는 이름을 가진 사람을 찾으려면, 전화번호부 전체를 뒤져봐야 합니다. 이와 마찬가지로, (A, B) 인덱스에서 B 조건만으로는 인덱스의 정렬 구조를 활용할 수 없습니다. DB 옵티마이저가 인덱스를 효율적으로 '탈' 수 있도록, 쿼리 조건과 인덱스 컬럼의 순서를 일치시키는 것이 복합 인덱스 설계의 핵심입니다. 52. 교착 상태(Deadlock)는 무엇이며, 데이터베이스에서 교착 상태가 발생하는 시나리오와 해결 방안은 무엇인가요? (출제 확률: 매우 높음) 정답: 교착 상태는 둘 이상의 트랜잭션이 서로 상대방이 점유한 락을 요청하며 무한정 대기하는 상태입니다. DBMS는 보통 교착 상태를 감지하여 둘 중 하나의 트랜잭션을 강제 롤백시켜 해결합니다. 애플리케이션 레벨에서는 모든 트랜잭션이 리소스에 접근하는 순서를 동일하게 통일하는 것이 가장 효과적인 예방책입니다. 정답 해설: 교착 상태는 동시성 시스템에서 피하기 어려운 문제입니다. 데이터베이스는 이를 자동으로 감지하고 해결하는 기능을 제공하지만, 교착 상태가 빈번하게 발생한다는 것은 애플리케이션의 쿼리 패턴이나 트랜잭션 설계에 문제가 있다는 신호입니다. 가장 효과적인 예방책은 '락을 획득하는 순서를 항상 동일하게 유지' 하는 것입니다. 위의 시나리오에서 모든 트랜잭션이 항상 A 테이블 -> B 테이블 순서로 락을 건다면, 한 트랜잭션이 A와 B의 락을 모두 얻을 때까지 다른 트랜잭션은 A의 락부터 기다려야 하므로, 서로의 락을 물고 늘어지는 상황이 발생하지 않습니다. 53. INNER JOIN과 OUTER JOIN(LEFT, RIGHT)의 차이점을 설명해주세요. (출제 확률: 매우 높음) 정답: INNER JOIN은 두 테이블에 모두 일치하는 데이터가 있는 행만 반환합니다. 반면, OUTER JOIN은 한쪽 테이블을 기준으로, 일치하는 데이터가 없더라도 기준 테이블의 행은 모두 반환합니다. LEFT JOIN은 왼쪽 테이블을 기준으로, RIGHT JOIN은 오른쪽 테이블을 기준으로 동작하며, 일치하는 데이터가 없는 쪽의 컬럼은 NULL로 채워집니다. 정답 해설: JOIN은 관계형 데이터베이스의 핵심입니다. INNER JOIN: 가장 기본적인 조인으로, 교집합을 구하는 것과 같습니다. 예를 들어, 회원 테이블과 주문 테이블을 INNER JOIN하면, 주문 기록이 있는 회원만 조회됩니다. LEFT OUTER JOIN: 왼쪽 테이블의 모든 데이터를 포함합니다. 회원 테이블을 기준으로 주문 테이블을 LEFT JOIN하면, 주문 기록이 없는 회원을 포함한 모든 회원이 조회되며, 주문 기록이 없는 회원의 주문 관련 컬럼은 NULL이 됩니다. '모든 회원의 주문 내역(없으면 NULL)'을 조회할 때 사용합니다. RIGHT OUTER JOIN: LEFT JOIN과 반대로, 오른쪽 테이블의 모든 데이터를 포함합니다. FULL OUTER JOIN: 양쪽 테이블의 모든 데이터를 포함하며, 일치하는 데이터가 없는 쪽은 NULL로 채워집니다. 합집합과 유사합니다. (MySQL에서는 지원하지 않음) 54. 커버링 인덱스(Covering Index)란 무엇이며, 어떤 장점이 있나요? (출제 확률: 높음) 정답: 커버링 인덱스는 쿼리에 필요한 모든 데이터를 인덱스만으로 제공할 수 있는 인덱스입니다. 실제 데이터 테이블에 접근할 필요 없이 인덱스 스캔만으로 쿼리를 처리할 수 있어, 디스크 I/O를 크게 줄여 쿼리 성능을 획기적으로 향상시키는 장점이 있습니다. 정답 해설: 일반적인 비클러스터형 인덱스 스캔은, 인덱스에서 조건에 맞는 데이터의 주소를 찾은 뒤, 그 주소를 가지고 다시 데이터 테이블에 접근하여 최종 데이터를 가져오는 두 단계의 과정을 거칩니다. 커버링 인덱스는 이 두 번째 단계인 '데이터 테이블 접근'을 완전히 생략시켜줍니다. 예를 들어, (name, age)로 구성된 인덱스가 있고, SELECT name, age FROM users WHERE name = 'John' 이라는 쿼리가 있다면, 쿼리에 필요한 name과 age 정보가 모두 인덱스에 포함되어 있으므로 DB는 인덱스만 읽고 바로 결과를 반환할 수 있습니다. 이는 조회 성능을 극대화할 수 있는 매우 효과적인 튜닝 기법 중 하나입니다. 55. 데이터베이스 샤딩(Sharding)과 파티셔닝(Partitioning)의 차이점은 무엇인가요? (출제 확률: 높음) 정답: 둘 다 큰 테이블을 나누는 기술이지만, 파티셔닝은 하나의 DB 서버 내에서 테이블을 논리적으로 나누는 것이고, 샤딩은 여러 DB 서버에 데이터를 물리적으로 분산 저장하는 것입니다. 즉, 파티셔닝은 단일 서버의 관리 및 성능 최적화가 목적이고, 샤딩은 단일 서버의 한계를 넘어서는 수평적 확장이 목적입니다. 정답 해설: 가장 큰 차이는 '서버를 넘나드는가' 입니다. 파티셔닝은 수직적 확장(Scale-up)의 한계에 도달했을 때, 단일 서버 내에서 성능을 최적화하는 기법입니다. 예를 들어, 날짜별로 로그 테이블을 파티셔닝하면 특정 날짜의 로그를 조회할 때 전체 테이블이 아닌 해당 날짜의 파티션만 스캔하여 성능을 높일 수 있습니다. 반면, 샤딩은 단일 서버의 용량이나 성능을 완전히 초과했을 때, 여러 서버로 부하를 분산시키는 수평적 확장 기법입니다. 파티셔닝은 DB 내부적으로 처리되지만, 샤딩은 아키텍처 전체의 복잡도를 크게 증가시키는 어려운 기술입니다. 56. 데이터베이스 트랜잭션이란 무엇인가요? (출제 확률: 높음) 정답: 트랜잭션은 데이터베이스의 상태를 변화시키기 위해 수행하는 하나의 논리적인 작업 단위입니다. 여러 개의 쿼리가 하나의 트랜잭션으로 묶일 수 있으며, 이 작업들은 모두 성공하거나 모두 실패해야 하는 원자성(Atomicity)을 가집니다. 정답 해설: 트랜잭션은 데이터의 무결성과 일관성을 보장하는 데 필수적입니다. 예를 들어, 'A가 B에게 1만원을 송금한다'는 작업은 다음과 같은 두 개의 쿼리로 이루어집니다. A의 계좌에서 1만원을 차감한다. (UPDATE) B의 계좌에 1만원을 더한다. (UPDATE) 만약 1번 쿼리만 성공하고 시스템에 장애가 발생하면, 돈은 사라졌지만 아무에게도 가지 않은 심각한 데이터 불일치 상태가 됩니다. 트랜잭션은 이 두 쿼리를 하나의 논리적인 작업 단위로 묶어, 두 작업이 모두 성공했을 때만 COMMIT하여 변경 사항을 영구 반영하고, 중간에 하나라도 실패하면 ROLLBACK하여 모든 작업을 이전 상태로 되돌림으로써 데이터의 일관성을 유지합니다. 57. Primary Key와 Foreign Key는 무엇이며, 어떤 역할을 하나요? (출제 확률: 높음) 정답: Primary Key (기본 키): 테이블의 각 행(row)을 고유하게 식별할 수 있는 컬럼입니다. NULL 값을 가질 수 없으며, 테이블당 하나만 존재할 수 있습니다. Foreign Key (외래 키): 한 테이블의 컬럼이 다른 테이블의 기본 키를 참조하는 것으로, 테이블 간의 관계를 정의하고 데이터의 참조 무결성을 보장하는 역할을 합니다. 정답 해설: **Primary Key(PK)**는 주민등록번호처럼 각 레코드를 유일하게 구분하는 값입니다. PK를 지정하면 자동으로 고유성(Unique)과 NOT NULL 제약조건이 걸리며, 대부분의 데이터베이스에서는 클러스터형 인덱스가 생성되어 빠른 조회를 가능하게 합니다. **Foreign Key(FK)**는 테이블 간의 관계를 맺어주는 역할을 합니다. 예를 들어, ORDER 테이블의 MEMBER_ID 컬럼이 MEMBER 테이블의 ID(PK)를 FK로 참조하도록 설정하면, 존재하지 않는 회원의 주문을 생성하는 것을 막을 수 있습니다. 이처럼 FK는 잘못되거나 존재하지 않는 데이터가 입력되는 것을 방지하여 데이터의 정합성을 유지하는 데 매우 중요한 역할을 합니다. 58. DELETE, TRUNCATE, DROP 명령어의 차이점을 설명해주세요. (출제 확률: 높음) 정답: 세 명령어 모두 데이터를 삭제하지만, 대상과 방식이 다릅니다. DELETE: 테이블에서 특정 행(row)을 삭제하며, WHERE 절을 사용할 수 있습니다. 트랜잭션 로그를 기록하므로 롤백(Rollback)이 가능합니다. TRUNCATE: 테이블의 모든 행을 한 번에 삭제합니다. WHERE 절을 사용할 수 없으며, 로그를 최소한으로 기록하여 DELETE보다 속도가 훨씬 빠르지만 롤백이 불가능합니다. DROP: 테이블의 데이터뿐만 아니라, 테이블 구조 자체를 완전히 삭제합니다. 정답 해설: DELETE (DML): 데이터 조작 언어(DML)로, 한 행씩 삭제하며 삭제된 각 행에 대한 로그를 남깁니다. 이 로그 덕분에 트랜잭션을 롤백할 수 있습니다. WHERE 절로 특정 데이터만 지울 수 있습니다. TRUNCATE (DDL): 데이터 정의 언어(DDL)로, 테이블을 비우는 개념에 가깝습니다. 테이블을 초기 상태로 되돌리므로 속도가 매우 빠릅니다. 하지만 테이블 구조는 그대로 남아있습니다. DROP (DDL): 데이터 정의 언어(DDL)로, 가장 강력한 삭제 명령어입니다. 테이블의 존재 자체를 없애버리므로, 데이터와 인덱스, 제약조건 등 모든 것이 사라집니다. 실무에서는 특정 조건의 데이터를 지울 때는 DELETE, 테이블의 모든 데이터를 빠르게 비우고 싶을 때는 TRUNCATE, 테이블 자체가 더 이상 필요 없을 때는 DROP을 사용합니다. 59. UNION과 UNION ALL의 차이점은 무엇인가요? (출제 확률: 높음) 정답: UNION과 UNION ALL은 모두 두 개 이상의 SELECT 문의 결과를 합치는 데 사용됩니다. 가장 큰 차이점은 중복 제거 여부입니다. UNION은 결과를 합친 후 중복된 행을 제거하는 반면, UNION ALL은 중복 제거 과정 없이 그대로 모든 결과를 합칩니다. 따라서 UNION ALL이 UNION보다 성능 면에서 더 빠릅니다. 정답 해설: UNION이 중복을 제거하기 위해서는 결과를 합친 뒤, 전체 데이터에 대해 정렬(Sort) 또는 해싱(Hashing) 작업을 수행해야 합니다. 이 과정은 데이터의 양이 많을수록 상당한 오버헤드를 유발합니다. 반면, UNION ALL은 이러한 추가적인 작업 없이 단순히 두 결과 집합을 이어 붙이기만 하므로 훨씬 효율적입니다. 따라서, 결과에 중복이 없다는 것이 명확하거나, 중복이 있어도 상관없는 경우에는 반드시 UNION ALL을 사용하여 불필요한 성능 저하를 피해야 합니다. 60. 데이터베이스에서 VIEW는 어떤 경우에 사용하며, 어떤 장단점이 있나요? (출제 확률: 보통) 정답: VIEW는 복잡한 쿼리를 캡슐화한 가상 테이블입니다. 복잡한 로직을 숨기거나, 특정 데이터만 노출시켜 보안을 강화하는 장점이 있습니다. 하지만 VIEW 자체의 성능 튜닝이 어렵고, 복잡한 VIEW는 데이터 수정에 제약이 있다는 단점이 있습니다. 정답 해설: VIEW의 본질은 '쿼리의 재사용'과 '추상화' 입니다. 반복적으로 사용되는 복잡한 쿼리를 하나의 VIEW로 캡슐화하여, 개발자가 매번 긴 쿼리를 작성하는 수고를 덜어주고 실수를 방지합니다. 또한, DB 사용자에게는 복잡한 내부 테이블 구조를 숨기고, 비즈니스 의미에 맞는 단순한 형태의 가상 테이블만 보여줌으로써 데이터 접근을 단순화하고 보안을 강화하는 역할을 합니다. 하지만 VIEW는 단지 저장된 쿼리일 뿐이므로, VIEW 자체를 튜닝하는 것은 불가능하며 기반 테이블과 쿼리의 성능에 직접적으로 의존한다는 점을 유의해야 합니다.

🖥️ Computer Science (11 Questions)

61. 프로세스(Process)와 스레드(Thread)의 차이점을 설명해주세요. (출제 확률: 매우 높음)

정답: 프로세스는 운영체제로부터 자원을 할당받는 작업의 단위이며, 각 프로세스는 독립적인 메모리 공간(Code, Data, Heap)을 가집니다. 스레드는 프로세스 내에서 실행되는 실행의 단위로, 프로세스의 자원(Code, Data, Heap)을 공유하면서 자신만의 Stack과 PC Register를 가집니다. 정답 해설: 프로세스를 '공장', 스레드를 '공장의 일꾼'에 비유할 수 있습니다. 자원 할당: 공장(프로세스)은 운영체제로부터 부지, 전기, 원자재(메모리, 파일 핸들 등)를 할당받습니다. 새로운 공장을 짓는 것은 비용이 많이 듭니다. (프로세스 생성은 무거운 작업) 자원 공유: 공장 내의 일꾼들(스레드)은 공장의 설비와 원자재(Code, Data, Heap)를 공유하며 함께 일합니다. 독립적인 실행 흐름: 각 일꾼(스레드)은 자신만의 작업 목록(PC Register)과 개인 도구함(Stack)을 가지고 독립적으로 작업을 수행합니다. 일꾼 한 명을 추가하는 것은 공장을 새로 짓는 것보다 훨씬 비용이 저렴합니다. (스레드 생성은 가벼운 작업) Context Switching: 여러 프로세스를 전환하는 것은 공장을 통째로 바꾸는 것처럼 비용이 크지만, 한 프로세스 내에서 여러 스레드를 전환하는 것은 일꾼만 교체하는 것이므로 비용이 훨씬 적습니다. 이 때문에 멀티 스레딩이 동시성 프로그래밍에서 효율적인 방식이 됩니다. 단점: 스레드는 자원을 공유하기 때문에, 한 스레드의 문제가 다른 스레드에 영향을 주어 전체 프로세스가 중단될 수 있습니다. 또한, 공유 자원에 대한 동기화 문제를 신중하게 처리해야 합니다. 62. TCP와 UDP의 차이점은 무엇이며, 각각 어떤 경우에 사용되나요? (출제 확률: 매우 높음) 정답: TCP는 연결 지향형 프로토콜로, 3-way-handshake를 통해 연결을 설정하고 데이터의 순서와 전송을 보장하는 신뢰성 높은 통신 방식입니다. 반면, UDP는 비연결형 프로토콜로, 데이터를 일방적으로 전송하며 순서나 전송 여부를 보장하지 않지만 속도가 매우 빠릅니다. 따라서 TCP는 웹 브라우징, 파일 전송처럼 신뢰성이 중요할 때, UDP는 실시간 스트리밍이나 온라인 게임처럼 약간의 데이터 손실을 감수하더라도 빠른 속도가 중요할 때 사용됩니다. 정답 해설: TCP (Transmission Control Protocol): '신뢰성'을 확보하기 위해 여러 장치를 사용합니다. 3-way-handshake: 통신 시작 전, SYN -> SYN+ACK -> ACK 과정을 통해 양쪽이 통신 준비가 되었는지 확인합니다. 흐름 제어(Flow Control): 수신 측의 처리 속도에 맞춰 송신 측이 데이터 전송량을 조절합니다. 혼잡 제어(Congestion Control): 네트워크의 혼잡 상태를 파악하여 전송량을 조절함으로써 네트워크 전체의 안정성을 유지합니다. 오류 제어(Error Control): 전송된 데이터에 오류가 있거나 순서가 맞지 않으면 재전송을 요청합니다. UDP (User Datagram Protocol): '속도'에 집중합니다. TCP의 복잡한 제어 기능이 전혀 없습니다. 데이터를 데이터그램 단위로 단순히 전송만 할 뿐, 상대방이 받았는지, 순서가 맞는지 전혀 신경 쓰지 않습니다. 이 때문에 오버헤드가 매우 적어 빠릅니다. DNS, VoIP, 실시간 영상 스트리밍 등에서 사용됩니다. 63. TCP의 3-way-handshake 과정에 대해 설명해주세요. (출제 확률: 매우 높음) 정답: 3-way-handshake는 TCP 통신을 시작하기 전에 클라이언트와 서버가 서로 통신 준비가 되었음을 확인하는 과정입니다. SYN: 클라이언트가 서버에게 접속을 요청하는 SYN 패킷을 보냅니다. SYN+ACK: 서버는 요청을 수락한다는 의미로 ACK와 함께, 클라이언트에게도 포트를 열어달라는 SYN을 담아 응답합니다. ACK: 클라이언트는 서버의 요청을 수락한다는 ACK를 보내고, 이 패킷을 받은 서버는 연결이 성립되었음을 확인하고 통신을 시작합니다. 정답 해설: 이 과정은 양쪽 모두 데이터를 보내고 받을 준비가 되었다는 것을 상호 확인하기 위해 필수적입니다. 1단계 (Client -> Server: SYN): "안녕, 나랑 통신할래? 내 시퀀스 번호는 x야." 2단계 (Server -> Client: SYN+ACK): "그래 좋아. 네 요청(x) 잘 받았어(ACK=x+1). 나도 너랑 통신하고 싶어. 내 시퀀스 번호는 y야." 3단계 (Client -> Server: ACK): "알았어. 너의 요청(y)도 잘 받았어(ACK=y+1)." 이 3단계를 거쳐야만 양쪽이 서로의 초기 시퀀스 번호를 알게 되고, 이후 데이터 전송 시 순서를 보장할 수 있게 됩니다. 만약 2-way-handshake만 한다면, 서버는 클라이언트가 자신의 응답을 받았는지 확인할 방법이 없어 불안정한 연결이 될 수 있습니다. 64. DNS(Domain Name System)는 어떻게 동작하나요? (출제 확률: 높음) 정답: DNS는 www.google.com과 (opens in a new tab) 같은 도메인 이름을 컴퓨터가 이해하는 IP 주소로 변환해주는 시스템입니다. 사용자가 도메인을 요청하면, 로컬 DNS 서버(ISP 제공)가 먼저 캐시를 확인합니다. 캐시에 없으면, 전 세계에 13개가 있는 Root DNS 서버부터 시작하여 TLD(Top-Level Domain) DNS 서버(.com), Authoritative DNS 서버(실제 도메인 정보 저장) 순으로 재귀적으로 질의하여 최종 IP 주소를 찾아 사용자에게 반환합니다. 정답 해설: DNS의 동작 방식은 계층적인 구조와 재귀적인 질의로 요약할 수 있습니다. 브라우저 캐시 확인 -> OS 캐시 확인 -> 로컬 DNS 서버 캐시 확인 로컬 DNS 서버가 Root DNS 서버에 질의: ".com을 관리하는 서버 주소 알려줘." Root DNS 서버가 응답: ".com은 TLD 서버가 관리해. 주소는 xxx야." 로컬 DNS 서버가 TLD DNS 서버에 질의: "google.com을 관리하는 서버 주소 알려줘." TLD DNS 서버가 응답: "google.com은 Authoritative 서버가 관리해. 주소는 yyy야." 로컬 DNS 서버가 Authoritative DNS 서버에 질의: "www.google.com의 (opens in a new tab) IP 주소 뭐야?" Authoritative DNS 서버가 응답: "IP 주소는 142.250.204.14야." 로컬 DNS 서버가 사용자에게 IP 주소를 전달하고, 해당 정보를 캐싱합니다. 이처럼 전 세계에 분산된 서버들이 협력하여 동작하기 때문에, 인터넷의 모든 도메인 이름을 IP 주소로 변환할 수 있습니다. 65. CAP 이론이란 무엇이며, 분산 시스템 설계에서 어떤 의미를 가지나요? (출제 확률: 높음) 정답: CAP 이론은 분산 데이터 저장소가 일관성(Consistency), 가용성(Availability), 분할 허용성(Partition Tolerance) 세 가지 중 최대 두 가지만을 동시에 보장할 수 있다는 이론입니다. 현대 분산 시스템은 네트워크 단절(Partition)이 언제든 발생할 수 있으므로, 분할 허용성(P)은 반드시 선택해야 합니다. 따라서 실질적으로는 일관성(C)과 가용성(A) 사이의 트레이드오프를 선택하는 문제가 됩니다. 정답 해설: Consistency (일관성): 모든 노드가 항상 동일한 데이터를 보여주는 것을 보장합니다. 즉, 쓰기 작업이 완료되면 이후의 모든 읽기 요청은 그 최신 데이터를 반환해야 합니다. Availability (가용성): 일부 노드에 장애가 발생하더라도, 모든 요청에 대해 항상 응답을 받을 수 있음을 보장합니다. (오래된 데이터일지라도) Partition Tolerance (분할 허용성): 노드 간 네트워크가 단절되어 메시지를 주고받지 못하는 상황에서도 시스템이 계속 동작해야 함을 의미합니다. 네트워크 단절(P)이 발생했을 때, 시스템은 두 가지 선택을 해야 합니다. CP (Consistency/Partition Tolerance): 데이터 일관성을 위해, 네트워크가 단절된 노드로부터의 요청을 거부하거나 에러를 반환합니다. 즉, 가용성을 희생합니다. (예: RDBMS, MongoDB) AP (Availability/Partition Tolerance): 모든 요청에 응답하기 위해, 네트워크가 단절된 노드는 일단 자신이 가진 데이터를 반환합니다. 이 데이터는 최신이 아닐 수 있으므로, 일관성을 희생합니다. (예: Cassandra, DynamoDB) 분산 시스템을 설계할 때는 서비스의 특성에 맞춰 CP와 AP 중 어떤 특성을 더 중요하게 생각할지 결정하는 것이 매우 중요합니다. 66. 프로세스 동기화를 위한 상호 배제(Mutual Exclusion) 기법에는 어떤 것들이 있나요? (출제 확률: 높음) 정답: 상호 배제는 임계 구역(Critical Section)에 하나의 프로세스(또는 스레드)만 진입하도록 보장하는 기법입니다. 대표적으로 **뮤텍스(Mutex)**와 **세마포어(Semaphore)**가 있습니다. 뮤텍스는 하나의 스레드만 락을 획득할 수 있는 '열쇠'와 같은 방식으로, 락을 가진 스레드만 임계 구역에 들어갈 수 있습니다. 세마포어는 정해진 개수(N)의 스레드만 공유 자원에 접근할 수 있도록 허용하는 '카운터' 방식으로, N개의 가용 자원을 관리하는 데 사용됩니다. 정답 해설: 뮤텍스(Mutex, MUTual EXclusion): 락을 획득한 스레드만이 락을 해제할 수 있습니다. 오직 하나의 스레드만 임계 구역에 들어갈 수 있으므로, 이진(binary) 세마포어와 유사하게 동작할 수 있습니다. 세마포어(Semaphore): 정수 값을 가지는 카운터입니다. 스레드가 임계 구역에 진입할 때 카운터를 감소시키고, 나올 때 증가시킵니다. 카운터가 0이면 다른 스레드는 대기해야 합니다. 카운팅 세마포어: 카운터가 1 이상일 수 있으며, 동시에 여러 스레드가 임계 구역에 접근하는 것을 허용할 때 사용됩니다. (예: 동시에 5개만 허용되는 DB 커넥션 풀) 이진 세마포어: 카운터가 0 또는 1의 값만 가지며, 뮤텍스와 유사하게 상호 배제를 위해 사용될 수 있습니다. 하지만 뮤텍스와 달리, 락을 획득한 스레드가 아니더라도 락을 해제할 수 있다는 차이점이 있습니다. 67. REST API와 gRPC의 차이점은 무엇이며, 어떤 상황에서 gRPC가 더 유리할까요? (출제 확률: 높음) 정답: REST API는 HTTP/1.1 기반으로 JSON을 주로 사용하며, 텍스트 기반이라 사람이 읽기 쉽고 범용성이 높습니다. 반면, gRPC는 HTTP/2 기반으로 **프로토콜 버퍼(Protocol Buffers)**를 사용하여 데이터를 직렬화합니다. 이진(binary) 포맷이라 성능이 뛰어나고, 양방향 스트리밍을 지원합니다. 따라서 마이크로서비스 간의 내부 통신처럼 높은 성능과 낮은 지연 시간이 요구되는 경우에 gRPC가 더 유리합니다. 정답 해설: 성능: gRPC는 이진 포맷인 프로토콜 버퍼를 사용하므로, 텍스트 기반인 JSON보다 직렬화/역직렬화 속도가 빠르고 메시지 크기가 작습니다. 또한 HTTP/2의 멀티플렉싱, 헤더 압축 등의 기능을 활용하여 네트워크 효율이 뛰어납니다. API 명세: gRPC는 .proto 파일을 통해 API 명세를 엄격하게 정의합니다. 이를 통해 서버와 클라이언트 코드를 자동으로 생성할 수 있어 타입 안전성을 보장하고 개발 생산성을 높입니다. 통신 방식: REST는 요청/응답 모델만 지원하지만, gRPC는 서버 스트리밍, 클라이언트 스트리밍, 양방향 스트리밍 등 다양한 통신 방식을 지원하여 더 복잡한 상호작용을 구현할 수 있습니다. 이러한 특징 때문에, 외부에 공개되어 범용적인 호환성이 중요한 API는 REST를, 내부 서비스 간의 고성능 통신이 중요한 MSA 환경에서는 gRPC를 선택하는 것이 일반적인 전략입니다. 68. Big-O 표기법이란 무엇이며, O(1), O(log n), O(n), O(n^2)의 의미를 간단한 예시와 함께 설명해주세요. (출제 확률: 높음) 정답: Big-O 표기법은 입력 데이터의 크기(n)가 증가할 때, 알고리즘의 **실행 시간 또는 공간 사용량이 증가하는 비율(성장률)**을 나타내는 점근적 표기법입니다. O(1) (상수 시간): 입력 크기와 상관없이 항상 일정한 시간이 걸립니다. (예: 배열의 특정 인덱스 접근) O(log n) (로그 시간): 입력 크기가 두 배로 늘어날 때, 단계가 하나씩 늘어납니다. (예: 이진 탐색) O(n) (선형 시간): 입력 크기에 비례하여 시간이 걸립니다. (예: 배열의 모든 요소를 한 번씩 순회) O(n^2) (제곱 시간): 입력 크기의 제곱에 비례하여 시간이 걸립니다. (예: 이중 for문을 사용한 버블 정렬) 정답 해설: Big-O는 알고리즘의 절대적인 실행 시간이 아닌, 효율성의 척도를 나타냅니다. O(1)은 가장 이상적인 시간 복잡도입니다. O(log n)은 매우 효율적인 알고리즘으로, 데이터가 아무리 많아져도 실행 시간은 크게 늘어나지 않습니다. O(n)은 합리적인 시간 복잡도입니다. O(n^2)부터는 입력 데이터가 커질수록 실행 시간이 기하급수적으로 늘어나므로, 대용량 데이터를 처리할 때는 피해야 하는 알고리즘입니다. 개발자는 자신이 작성한 코드의 시간 복잡도를 분석하여, 대규모 데이터 환경에서도 성능 저하 없이 동작할 수 있는지 예측하고 개선하는 능력을 갖추어야 합니다. 69. Consistent Hashing은 어떤 문제를 해결하기 위해 사용되며, 어떻게 동작하나요? (출제 확률: 높음) 정답: Consistent Hashing은 분산 캐시나 분산 데이터베이스에서 서버(노드)가 추가되거나 삭제될 때, 데이터 재배치(remapping)를 최소화하기 위해 사용됩니다. 일반적인 해시 방식은 서버 수가 변하면 대부분의 키가 재배치되어야 하지만, Consistent Hashing은 해시 공간을 원형으로 구성하고, 키와 서버를 이 원 위의 한 점으로 매핑합니다. 키는 자신으로부터 시계 방향으로 가장 가까운 서버에 저장되므로, 서버가 추가되거나 삭제되어도 영향을 받는 키가 인접한 일부로 국한됩니다. 정답 해설: 기존의 모듈러(modular) 해싱(hash(key) % N, N은 서버 수)의 문제는 N이 변할 때 발생합니다. 서버가 4대에서 5대로 늘어나면, 거의 모든 키에 대해 % 5의 결과값이 % 4의 결과값과 달라져 대규모 데이터 이동(캐시의 경우 대규모 캐시 미스)이 발생합니다. Consistent Hashing은 이 문제를 해결합니다. 해시 함수를 사용하여 0부터 2^32-1까지의 숫자로 이루어진 **해시 링(Hash Ring)**을 만듭니다. 각 서버의 IP나 이름 등을 해싱하여 링 위의 한 위치에 배치합니다. 저장할 데이터의 키를 해싱하여 링 위의 한 위치에 배치합니다. 데이터는 자신의 위치에서 시계 방향으로 이동하며 만나는 첫 번째 서버에 저장됩니다. 이 구조에서는, 서버 하나가 추가되거나 삭제되어도 해당 서버와 그 다음 서버 사이의 키들만 재배치됩니다. 나머지 대부분의 키는 영향을 받지 않으므로, 시스템의 부하를 최소화하며 노드를 유연하게 확장하거나 축소할 수 있습니다. 70. 심볼릭 링크(Symbolic Link)와 하드 링크(Hard Link)의 차이점은 무엇인가요? (출제 확률: 높음) 정답: 하드 링크는 원본 파일과 동일한 inode를 공유하는 또 다른 파일 이름입니다. 원본 파일과 하드 링크는 사실상 동일하며, 모든 링크가 삭제되어야 파일이 실제로 삭제됩니다. 반면, 심볼릭 링크는 원본 파일을 가리키는 경로 정보가 담긴 별도의 파일로, 윈도우의 '바로 가기'와 유사합니다. 원본 파일이 삭제되면 링크는 깨지게 됩니다. 정답 해설: 파일 시스템에서 모든 파일은 inode라는 고유한 데이터 구조를 가집니다. inode는 파일의 메타데이터(권한, 소유자, 크기 등)와 실제 데이터가 저장된 디스크 블록의 위치 정보를 담고 있습니다. 파일 이름은 단지 이 inode를 가리키는 포인터일 뿐입니다. 하드 링크: ln original.txt hard.txt 명령은 original.txt가 가리키는 inode를 hard.txt도 함께 가리키도록 만듭니다. inode의 링크 카운트가 1 증가합니다. 따라서 original.txt를 지워도 링크 카운트가 1 감소할 뿐, 카운트가 0이 되기 전까지는 파일 데이터가 삭제되지 않습니다. 이러한 특성 때문에 하드 링크는 다른 파일 시스템(파티션)을 가로질러 생성할 수 없으며, 디렉터리에도 링크를 걸 수 없습니다. 심볼릭 링크: ln -s original.txt symbolic.txt 명령은 symbolic.txt라는 새로운 파일과 새로운 inode를 생성합니다. 이 새로운 파일의 내용은 단지 "original.txt"라는 텍스트 경로입니다. 운영체제는 심볼릭 링크에 접근할 때, 이 경로를 읽어 원본 파일로 리다이렉션 시켜줍니다. 경로 기반이므로 다른 파일 시스템을 가로지를 수 있고, 디렉터리에도 링크를 걸 수 있습니다. 71. 운영체제의 페이징(Paging)과 세그멘테이션(Segmentation) 기법을 비교 설명해주세요. (출제 확률: 보통) 정답: 페이징과 세그멘테이션은 모두 메모리 관리를 위한 기법입니다. 페이징은 프로세스의 주소 공간을 **고정된 크기의 '페이지'**로 나누고, 물리 메모리도 같은 크기의 '프레임'으로 나누어 불연속적으로 할당하는 방식입니다. 세그멘테이션은 주소 공간을 **논리적인 의미 단위(코드, 데이터, 스택 등)의 가변 크기 '세그먼트'**로 나누어 관리하는 방식입니다. 페이징은 외부 단편화를 해결하는 데 효과적이고, 세그멘테이션은 메모리 보호나 공유에 더 유리합니다. 정답 해설: 페이징(Paging): 장점: 고정된 크기로 나누므로 메모리 할당 및 관리가 단순하고, 외부 단편화 문제가 발생하지 않습니다. 단점: 내부 단편화(페이지의 마지막 부분에 남는 공간)가 발생할 수 있습니다. 논리적인 단위가 아니므로 코드나 데이터 공유가 복잡합니다. 세그멘테이션(Segmentation): 장점: 코드, 데이터, 스택 등 의미 있는 단위로 나누므로, 각 세그먼트에 읽기/쓰기/실행 권한을 부여하는 등 메모리 보호와 공유가 용이합니다. 단점: 가변 크기의 세그먼트들이 메모리에 할당되고 해제되기를 반복하면, 중간중간에 작은 빈 공간들이 생기는 외부 단편화 문제가 발생할 수 있습니다. 현대의 운영체제(Linux, Windows 등)는 이 두 기법의 장점을 결합한 페이징된 세그멘테이션(Paged Segmentation) 또는 세그멘테이션을 가미한 페이징 방식을 사용하여 메모리를 효율적으로 관리합니다.

☁️ Cloud (10 Questions)

72. 클라우드 컴퓨팅이란 무엇이며, IaaS, PaaS, SaaS의 차이점을 설명해주세요. (출제 확률: 매우 높음)

정답: 클라우드 컴퓨팅은 인터넷을 통해 서버, 스토리지, 데이터베이스 같은 IT 리소스를 필요할 때마다 빌려 쓰고 사용한 만큼 비용을 지불하는 서비스입니다. **IaaS(Infrastructure as a Service)**는 가상 서버, 스토리지 같은 인프라만 제공하고, **PaaS(Platform as a Service)**는 인프라 위에 애플리케이션 개발 및 실행 환경까지 제공하며, **SaaS(Software as a Service)**는 완전히 만들어진 소프트웨어 자체를 서비스 형태로 제공합니다. 정답 해설: 세 가지 모델은 **'관리의 책임 범위'**로 구분할 수 있습니다. 피자에 비유하자면: On-Premise (전통 방식): 집에서 밀가루부터 모든 재료를 사서 직접 피자를 만드는 것. (모든 것을 직접 관리) IaaS (예: AWS EC2, GCP Compute Engine): 마트에서 피자 도우, 소스, 토핑을 사와서 집 오븐으로 직접 굽는 것. (인프라만 제공받고, OS, 미들웨어, 런타임 등은 직접 설치 및 관리) PaaS (예: Heroku, AWS Elastic Beanstalk): 배달 전문점에서 피자를 주문해서 먹는 것. (개발자는 코드만 올리면, 플랫폼이 알아서 배포하고 관리) SaaS (예: Google Workspace, Slack): 피자 가게에 가서 완성된 피자를 사 먹는 것. (사용자는 소프트웨어를 그대로 사용하기만 함) 개발자 입장에서는 IaaS가 가장 자유도가 높지만 관리 부담이 크고, PaaS와 SaaS로 갈수록 관리 부담은 줄어들지만 자유도는 낮아지는 트레이드오프가 있습니다. 73. 로드 밸런서(Load Balancer)란 무엇이며, 어떤 종류가 있고 왜 사용하나요? (출제 확률: 매우 높음) 정답: 로드 밸런서는 여러 대의 서버로 들어오는 네트워크 트래픽을 균등하게 분산시켜주는 장치입니다. 이를 통해 특정 서버에 부하가 집중되는 것을 막고, 전체 시스템의 가용성과 확장성을 높입니다. 대표적으로 OSI 4계층에서 동작하는 L4 로드 밸런서와 7계층에서 동작하는 L7 로드 밸런서가 있습니다. 정답 해설: 사용 이유: 부하 분산(Load Balancing): 트래픽을 여러 서버로 나누어 서버 과부하를 방지합니다. 고가용성(High Availability): 특정 서버에 장애가 발생하면, 로드 밸런서는 이를 감지하고 해당 서버로는 트래픽을 보내지 않아 서비스 중단을 막습니다. (Health Check 기능) 수평 확장(Horizontal Scaling): 트래픽이 증가할 때 서버를 추가하기만 하면 로드 밸런서가 자동으로 트래픽을 분산시켜주므로, 손쉽게 시스템을 확장할 수 있습니다. 종류: L4 로드 밸런서: 전송 계층(Transport Layer)에서 동작하며, IP 주소와 포트 번호를 기반으로 트래픽을 분산합니다. 패킷 내용을 보지 않으므로 속도가 빠릅니다. L7 로드 밸런서: 응용 계층(Application Layer)에서 동작하며, HTTP 헤더나 URL 같은 애플리케이션 레벨의 정보를 보고 트래픽을 분산합니다. /images 경로는 이미지 서버로, /api 경로는 API 서버로 보내는 등 더 정교한 라우팅이 가능합니다. (예: AWS Application Load Balancer) 74. 오토 스케일링(Auto Scaling)이란 무엇이며, 어떤 장점이 있나요? (출제 확률: 매우 높음) 정답: 오토 스케일링은 애플리케이션의 트래픽이나 부하에 따라 컴퓨팅 리소스(서버 인스턴스 등)의 수를 자동으로 조절하는 기능입니다. 트래픽이 많아지면 인스턴스를 늘려(Scale-out) 안정적인 서비스를 유지하고, 트래픽이 줄어들면 인스턴스를 줄여(Scale-in) 불필요한 비용을 절감하는 장점이 있습니다. 정답 해설: 오토 스케일링은 클라우드의 **탄력성(Elasticity)**을 가장 잘 보여주는 기능입니다. 비용 효율성: 사용량이 적은 새벽 시간에는 최소한의 인스턴스만 유지하고, 사용량이 급증하는 피크 타임에는 자동으로 인스턴스 수를 늘려 대응합니다. 이를 통해 항상 최대 트래픽에 맞춰 서버를 과도하게 준비해 둘 필요가 없어 비용을 크게 절감할 수 있습니다. 가용성 및 안정성: 갑작스러운 트래픽 증가에도 자동으로 서버가 증설되어 서비스가 느려지거나 중단되는 것을 방지합니다. 또한, 특정 인스턴스에 장애가 발생하면 오토 스케일링 그룹이 이를 감지하고 자동으로 새로운 정상 인스턴스로 교체하여 서비스의 가용성을 유지합니다. 오토 스케일링은 보통 로드 밸런서와 함께 사용되며, CPU 사용률이나 네트워크 트래픽 같은 지표를 기준으로 확장/축소 정책을 설정합니다. 75. CDN(Content Delivery Network)은 어떤 원리로 동작하며, 왜 사용하나요? (출제 확률: 매우 높음) 정답: CDN은 이미지, 동영상, CSS, JS 파일과 같은 정적 콘텐츠를 사용자와 지리적으로 가까운 여러 곳의 캐시 서버(엣지 서버)에 미리 복제해두고, 사용자가 요청할 때 가장 가까운 서버에서 콘텐츠를 전송하는 네트워크입니다. 이를 통해 원본 서버(Origin Server)의 부하를 줄이고, 네트워크 지연 시간을 최소화하여 사용자에게 더 빠른 콘텐츠 로딩 속도를 제공하기 위해 사용합니다. 정답 해설: CDN의 동작 원리는 '물리적 거리 단축'과 '캐싱' 입니다. 사용자가 웹사이트에 접속하면, DNS는 사용자와 가장 가까운 CDN **엣지 서버(Edge Server)**의 IP 주소를 알려줍니다. 사용자의 브라우저는 해당 엣지 서버에 콘텐츠를 요청합니다. 만약 엣지 서버에 해당 콘텐츠가 캐싱되어 있다면, 즉시 사용자에게 전송합니다. 만약 캐싱되어 있지 않다면(Cache Miss), 엣지 서버가 **원본 서버(Origin Server)**에 콘텐츠를 요청하여 받아온 뒤, 사용자에게 전송하고 자신에게 캐싱합니다. 이러한 방식으로, 한국에 있는 사용자는 한국에 있는 엣지 서버에서, 미국에 있는 사용자는 미국에 있는 엣지 서버에서 콘텐츠를 받게 되므로, 원본 서버가 어디에 있든 상관없이 빠른 속도를 경험할 수 있습니다. 76. 서버리스 컴퓨팅(Serverless Computing)이란 무엇이며, 장단점은 무엇인가요? (출제 확률: 높음) 정답: 서버리스는 개발자가 서버를 직접 프로비저닝하거나 관리할 필요 없이, 코드를 실행할 수 있게 해주는 클라우드 컴퓨팅 모델입니다. 코드는 특정 이벤트가 발생했을 때만 실행되며, 클라우드 제공업체가 인프라 관리를 모두 책임집니다. (예: AWS Lambda, Google Cloud Functions) 장점: 인프라 관리 부담 제로, 사용한 만큼만 비용 지불(비용 효율성), 자동 확장성. 단점: Cold Start로 인한 초기 지연 시간 발생 가능, 실행 시간 및 리소스 제약, 특정 클라우드 제공업체에 대한 종속성(Vendor Lock-in). 정답 해설: 서버리스는 '서버가 없다'는 뜻이 아니라, **'서버 관리에 대해 신경 쓸 필요가 없다'**는 의미입니다. Cold Start: 함수가 오랫동안 호출되지 않으면, 클라우드 제공업체는 자원 효율을 위해 해당 함수의 컨테이너를 종료시킵니다. 이후 다시 함수가 호출되면, 컨테이너를 새로 시작하고 코드를 로드하는 과정이 필요하여 첫 응답이 느려질 수 있습니다. 이것이 Cold Start 문제입니다. 비용 모델: 유휴 상태일 때는 비용이 전혀 발생하지 않고, 코드가 실행된 시간(밀리초 단위)과 횟수에 대해서만 비용을 지불하므로, 트래픽이 불규칙하거나 간헐적인 워크로드에 매우 비용 효율적입니다. 적합한 사용 사례: 간단한 API 백엔드, 이미지 리사이징 같은 데이터 처리, 특정 이벤트에 대한 응답(예: 파일 업로드 시 썸네일 생성) 등 이벤트 기반의 짧은 작업에 매우 적합합니다. 77. 컨테이너 오케스트레이션이란 무엇이며, 쿠버네티스(Kubernetes)와 같은 도구가 왜 필요한가요? (출제 확률: 높음) 정답: 컨테이너 오케스트레이션은 수많은 컨테이너의 배포, 관리, 확장, 네트워킹을 자동화하는 기술입니다. 프로덕션 환경에서는 수백, 수천 개의 컨테이너가 동작하는데, 이를 수동으로 관리하는 것은 불가능합니다. 쿠버네티스와 같은 도구는 컨테이너의 자동 배치(스케줄링), 장애 발생 시 자동 복구(Self-healing), 트래픽에 따른 자동 확장(Auto-scaling) 등의 기능을 제공하여, 대규모 컨테이너 환경을 안정적이고 효율적으로 운영할 수 있게 해줍니다. 정답 해설: Docker만 사용하면 컨테이너를 '실행'할 수는 있지만, '운영'하기는 어렵습니다. 스케줄링: "이 컨테이너를 어떤 서버에 띄워야 리소스가 가장 효율적일까?" Self-healing: "컨테이너가 죽거나 서버에 장애가 발생하면 어떻게 자동으로 다시 띄울까?" 서비스 디스커버리 및 로드 밸런싱: "수시로 생성되고 사라지는 컨테이너들의 IP 주소를 어떻게 추적하고, 어떻게 트래픽을 분산시킬까?" 배포 및 롤백: "어떻게 무중단으로 새로운 버전의 컨테이너로 교체하고, 문제가 생기면 어떻게 이전 버전으로 쉽게 돌아갈까?" 쿠버네티스는 이러한 복잡한 운영 문제들을 해결하기 위한 사실상의 표준(De facto standard) 도구입니다. 개발자는 "나는 이 애플리케이션을 3개의 복제본으로 실행하고 싶다"고 선언하기만 하면, 쿠버네티스가 나머지 모든 것을 알아서 처리해주는 강력한 자동화 플랫폼입니다. 78. IaaS, PaaS, SaaS의 차이점을 설명해주세요. (출제 확률: 높음) 정답: 세 가지 모델은 클라우드 제공업체가 관리해주는 책임의 범위에 따라 구분됩니다. IaaS(Infrastructure as a Service): 가상 서버, 스토리지, 네트워크 같은 인프라만 제공합니다. OS 설치부터 모든 소프트웨어 관리는 사용자의 책임입니다. (예: AWS EC2) PaaS(Platform as a Service): 인프라 위에 OS, 미들웨어, 런타임 등 애플리케이션 실행 환경까지 제공합니다. 개발자는 코드만 배포하면 됩니다. (예: Heroku) SaaS(Software as a Service): 완전히 만들어진 소프트웨어 자체를 서비스 형태로 제공합니다. 사용자는 별도의 설치나 설정 없이 바로 사용합니다. (예: Google Workspace) 정답 해설: 피자에 비유하자면: On-Premise (전통 방식): 집에서 밀가루부터 모든 재료를 사서 직접 피자를 만드는 것. (모든 것을 직접 관리) IaaS (예: AWS EC2, GCP Compute Engine): 마트에서 피자 도우, 소스, 토핑을 사와서 집 오븐으로 직접 굽는 것. (인프라만 제공받고, OS, 미들웨어, 런타임 등은 직접 설치 및 관리) PaaS (예: Heroku, AWS Elastic Beanstalk): 배달 전문점에서 피자를 주문해서 먹는 것. (개발자는 코드만 올리면, 플랫폼이 알아서 배포하고 관리) SaaS (예: Google Workspace, Slack): 피자 가게에 가서 완성된 피자를 사 먹는 것. (사용자는 소프트웨어를 그대로 사용하기만 함) 개발자 입장에서는 IaaS가 가장 자유도가 높지만 관리 부담이 크고, PaaS와 SaaS로 갈수록 관리 부담은 줄어들지만 자유도는 낮아지는 트레이드오프가 있습니다. 79. VPC(Virtual Private Cloud)란 무엇이며, Subnet과 Security Group은 어떤 역할을 하나요? (출제 확률: 높음) 정답: VPC는 클라우드 환경 내에 사용자가 직접 정의하는 논리적으로 격리된 가상 네트워크입니다. Subnet: VPC 내부의 IP 주소 범위를 더 작게 나눈 네트워크 영역입니다. 외부 인터넷과 통신이 가능한 Public Subnet과, 외부와 차단된 Private Subnet으로 나누어 리소스를 배치함으로써 보안을 강화합니다. Security Group: 인스턴스 수준에서 트래픽을 제어하는 가상 방화벽 역할을 합니다. 특정 IP나 포트에 대한 인바운드/아웃바운드 트래픽을 허용하거나 차단하는 규칙을 설정할 수 있습니다. 정답 해설: VPC는 클라우드 환경에서 네트워크 보안과 구성을 위한 가장 기본적인 단위입니다. Public/Private Subnet 구성: 일반적인 웹 애플리케이션 아키텍처에서는, 외부 사용자의 접근이 필요한 웹 서버나 로드 밸런서는 Public Subnet에 배치하고, 민감한 데이터를 다루는 데이터베이스나 내부 API 서버는 Private Subnet에 배치합니다. Private Subnet의 리소스는 외부에서 직접 접근할 수 없으며, 보통 Public Subnet의 Bastion Host나 NAT Gateway를 통해서만 제한적으로 외부와 통신합니다. 이를 통해 보안을 크게 강화할 수 있습니다. Security Group의 상태 기반(Stateful) 특징: Security Group은 상태를 기억합니다. 인바운드 규칙에 따라 허용된 요청이 들어오면, 그에 대한 응답 트래픽은 아웃바운드 규칙과 상관없이 자동으로 허용됩니다. 이는 일반적인 방화벽과 구분되는 중요한 특징입니다. 80. IaC(Infrastructure as Code)란 무엇이며, 어떤 장점이 있나요? (출제 확률: 높음) 정답: IaC는 서버, 로드 밸런서, 데이터베이스 등 인프라 구성을 코드를 통해 관리하고 프로비저닝하는 것입니다. (예: Terraform, AWS CloudFormation) 장점: 자동화 및 속도: 코드를 실행하여 인프라를 빠르고 반복적으로 생성할 수 있습니다. 일관성 및 재현성: 동일한 코드는 항상 동일한 환경을 생성하므로, '내 컴퓨터에선 됐는데...' 하는 문제를 방지하고 개발/스테이징/운영 환경의 일관성을 유지할 수 있습니다. 버전 관리 및 협업: 인프라 구성이 코드로 관리되므로, Git을 통해 변경 이력을 추적하고 코드 리뷰를 통해 협업할 수 있습니다. 정답 해설: IaC가 없다면, 새로운 서버가 필요할 때마다 클라우드 콘솔에 접속하여 수많은 항목을 마우스로 클릭하며 설정해야 합니다. 이 과정은 시간이 오래 걸리고, 실수가 발생하기 쉬우며, 똑같은 환경을 다시 만드는 것이 매우 어렵습니다. IaC는 이러한 수동 작업을 코드로 대체합니다. 예를 들어, Terraform 코드로 "t2.micro 사양의 EC2 인스턴스 3개를 특정 VPC의 서브넷에 생성하고, 80번 포트를 여는 보안 그룹을 적용하라"고 선언적으로 정의해두면, terraform apply 명령어 한 번으로 모든 인프라가 자동으로 생성됩니다. 이는 인프라 관리를 소프트웨어 개발 프로세스처럼 만들어, 더 빠르고 안정적이며 예측 가능한 시스템 운영을 가능하게 합니다. 81. 오브젝트 스토리지(Object Storage)는 파일 스토리지나 블록 스토리지와 어떻게 다른가요? (출제 확률: 보통) 정답: 오브젝트 스토리지(예: AWS S3)는 데이터를 '객체(Object)'라는 단위로 저장하며, 각 객체는 데이터 본체, 메타데이터, 고유 ID로 구성됩니다. 계층적인 디렉토리 구조가 없는 평평한(flat) 구조를 가지며, HTTP API를 통해 접근합니다. 이는 대용량의 비정형 데이터를 저장하고 웹을 통해 제공하는 데 최적화되어 있습니다. 반면, 파일 스토리지는 폴더와 파일의 계층 구조를, 블록 스토리지는 데이터를 고정된 크기의 블록으로 나누어 저장하며 OS에 디스크처럼 마운트하여 사용합니다. 정답 해설: 파일 스토리지 (예: NAS, EFS): 우리가 컴퓨터에서 사용하는 폴더/파일 시스템과 동일합니다. 여러 사용자가 파일을 공유하기에 용이하지만, 확장성에 한계가 있습니다. 블록 스토리지 (예: SAN, EBS): 하드디스크처럼 동작하며, OS가 파일 시스템을 직접 포맷하고 관리합니다. 데이터베이스나 고성능 컴퓨팅처럼 빠른 I/O와 낮은 지연 시간이 요구되는 작업에 적합합니다. 오브젝트 스토리지 (예: S3): 무한한 확장성: 평평한 주소 공간을 가지므로 사실상 무한대로 확장할 수 있습니다. 풍부한 메타데이터: 각 객체에 다양한 메타데이터를 저장할 수 있어 데이터 관리가 용이합니다. 웹 접근성: HTTP/HTTPS를 통해 어디서든 데이터에 접근할 수 있습니다. 이러한 특징 때문에, 이미지/동영상 같은 정적 콘텐츠 호스팅, 데이터 백업 및 아카이빙, 빅데이터 분석을 위한 데이터 레이크(Data Lake) 등 대용량의 비정형 데이터를 저장하고 관리하는 데 가장 널리 사용됩니다.