JVM 동시성 모델 이해하기 (5) – Kotlin Coroutines

동기 코드의 가독성으로 논블로킹을 — suspend 하나로 바뀌는 세상 Part 3에서 Reactor의 Mono/Flux를, Part 4에서 WebFlux를 다뤘습니다. Reactor는 논블로킹 비동기 처리를 가능하게 해주지만, 코드가 복잡해지면 flatMap 체이닝이 깊어지고 가독성이 떨어진다는 문제가 있었습니다. 관계가 깊어질수록 flatMap 안에 flatMap이 중첩되면서 코드의 흐름을 따라가기 어려워집니다. 같은 로직을 Kotlin Coroutines로 작성하면 이렇게 됩니다. 두 코드 모두 논블로킹입니다. I/O 대기 … 더 읽기

JVM 동시성 모델 이해하기 (4) – Spring WebFlux

Reactor가 웹을 만났을 때 — 적은 스레드로 많은 연결을 처리하는 법 Part 3에서 Reactive Streams 스펙과 Project Reactor의 Mono/Flux, 연산자, 스케줄러를 다뤘습니다. Reactor는 비동기 데이터 스트림을 처리하는 강력한 라이브러리이지만, 그 자체로는 HTTP 요청을 받거나 응답을 보내는 기능이 없습니다. 이 글에서는 Reactor 위에 Spring이 구축한 WebFlux 프레임워크를 다룹니다. Spring MVC의 어떤 한계를 해결하려고 만들어졌는지, 내부에서 Netty의 … 더 읽기

JVM 동시성 모델 이해하기 (3) – Reactive Streams와 Project Reactor

CompletableFuture 너머의 세계 — 스트림을 제어하는 새로운 방법 Part 2에서 Java 동시성 API의 발전을 따라갔습니다. Thread → ExecutorService → CompletableFuture로 추상화 수준이 올라가면서 비동기 프로그래밍이 점점 편해졌지만, CompletableFuture에는 두 가지 근본적인 한계가 남아 있었습니다. 첫째, 단건 처리에 특화되어 있습니다. CompletableFuture는 “하나의 비동기 작업이 완료되면 결과를 처리한다”는 모델입니다. 그런데 실시간으로 계속 들어오는 데이터 — 예를 들어 … 더 읽기

JVM 동시성 모델 이해하기 (2) – Java의 전통적인 동시성 모델

Thread에서 CompletableFuture까지 — 추상화의 발전사 Part 1에서 동시성과 병렬성, 동기/비동기, 블로킹/논블로킹의 개념을 정리했습니다. 이번 글에서는 그 개념들이 Java 코드에서 어떤 모습으로 나타나는지 살펴봅니다. Java의 동시성 API는 한 번에 완성된 것이 아닙니다. Thread로 시작해서, 그 한계를 극복하기 위해 ExecutorService가 등장하고, 결과를 받기 위해 Future가 추가되고, 블로킹 없이 결과를 처리하기 위해 CompletableFuture가 나왔습니다. 각 도구는 이전 도구의 … 더 읽기

JVM 동시성 모델 이해하기 (1) – 동시성과 병렬성의 기초

Spring 개발자가 꼭 정리해야 할 기초 체력 Spring MVC 으로 잘 개발하고 있다가도 성능에 대한 고민을 하게 되면 WebFlux, Coroutine, Virtual Thread라는 키워드를 자연스레 접하게 됩니다. 문서를 읽어보면 “비동기 논블로킹”, “리액티브”, “경량 스레드” 같은 표현이 난무하는데, 막상 동시성과 병렬성의 차이가 뭔지, 비동기면 논블로킹이랑 같은 건지 물어보면 명확히 답하기 어렵습니다. 이 글은 JVM 동시성 모델 이해하기 … 더 읽기

Tracing 이해하기 (5) – Java Agent vs Library Instrumentation

들어가며 3편에서 잠깐 언급했던 문제가 있습니다. 라이브러리 내부 로그에는 traceId가 없다는 것이었죠. Reactive Mongo Client, R2DBC 드라이버, Netty 같은 라이브러리의 DEBUG 로그에서 이런 현상이 발생합니다. 왜 이런 일이 발생할까요? 우리가 4편까지 설정한 Library Instrumentation 방식은 우리 애플리케이션 코드와 Spring이 지원하는 컴포넌트만 계측(instrument)합니다. 라이브러리가 자체적으로 Micrometer를 지원하지 않으면, 그 내부 코드는 계측되지 않습니다. 이 문제를 해결하는 … 더 읽기

Tracing 이해하기 (4) – Kotlin Coroutine과 Context Propagation

들어가며 이전 글에서 Reactor Context가 WebFlux의 Event Loop 환경에서 어떻게 traceId를 유지하는지 살펴봤습니다. Subscriber 체인에 Context를 바인딩하는 방식으로 스레드 전환 문제를 해결했죠. 그런데 Kotlin을 사용한다면 상황이 조금 더 복잡해집니다. Kotlin Coroutine은 CoroutineContext라는 자체 Context 시스템을 가지고 있기 때문입니다. 이제 우리가 다뤄야 할 Context가 세 가지가 되었습니다. Spring WebFlux에서 Kotlin Coroutine을 사용하면, 이 세 가지 Context가 … 더 읽기

Tracing 이해하기 (3) – Reactor Context와 비동기 환경

서론 이전 글에서 ThreadLocal과 MDC가 어떻게 Tracing Context를 저장하고 전파하는지 살펴봤습니다. 동기 환경에서는 “한 요청 = 한 스레드”라는 단순한 모델 덕분에 ThreadLocal만으로도 충분했습니다. 하지만 Spring WebFlux로 넘어오면 상황이 완전히 달라집니다. WebFlux는 소수의 스레드가 수천 개의 요청을 동시에 처리하는 Event Loop 모델을 사용합니다. 하나의 요청이 처리되는 동안 스레드가 수시로 바뀔 수 있죠. 이 환경에서 ThreadLocal은 더 … 더 읽기

Tracing 이해하기 (2) – ThreadLocal과 MDC의 이해

왜 ThreadLocal을 알아야 할까? 1편에서 Distributed Tracing의 핵심이 Trace Context 전파라는 것을 살펴봤습니다. 서비스 간에는 HTTP 헤더(W3C Trace Context, B3)로 전파하면 되지만, 한 가지 의문이 남습니다. “서비스 내부에서는 Trace Context가 어디에 저장되어 있을까?” Spring MVC 애플리케이션에서 요청이 들어오면 Controller → Service → Repository를 거치는 동안 traceId와 spanId는 어딘가에 보관되어야 합니다. 매번 파라미터로 넘기는 건 비현실적이니까요. … 더 읽기

Tracing 이해하기 (1) – Observability의 역사부터 Spring 생태계까지 (feat. OTel)

서론 “이 API 왜 이렇게 느려요?” MSA 환경에서 이 질문에 답하려면, 요청이 어떤 서비스를 거쳐 어디서 시간을 소비했는지 추적해야 합니다. 서비스가 3개일 때는 로그를 뒤져가며 찾을 수 있지만, 수십 개의 서비스가 얽혀있다면? Distributed Tracing 없이는 사실상 불가능합니다. 이 글에서는 Distributed Tracing이 왜 필요한지, OpenTelemetry가 어떻게 업계 표준이 되었는지, 그리고 Spring Boot 생태계에서는 어떤 선택지가 있는지 … 더 읽기