JVM Lock Coarsening

Lock Coarsening이란? 같은 모니터(객체)에 대해 락을 연속적으로 잡았다 풀었다 하는 동작을 더 큰 단위로 묶어서 한 번에 잡고 푸는 형태로 바꿈으로써, 락을 획득하고 해제하는 데 따르는 오버헤드를 최소화하는 JIT 최적화 기법입니다. -XX:+EliminateLocks 옵션이 활성화되어 있으면 적용됩니다. 핵심 아이디어 자바의 synchronized는 바이트코드로 monitorenter/monitorexit으로 표현됩니다. 반복문이나 인접한 코드 영역에서 같은 객체에 대해 monitorenter/monitorexit가 짧은 간격으로 반복되면, JIT 컴파일러는 이를 한 번만 처리하도록 변경합니다. 이를 통해 CAS(Compare-and-Set) 연산, 메모리 배리어, 스택 및 헤더 변경과 같은 락 비용을 최소화합니다. 예시 Lock Coarsening 전 for (int i = 0; i < n; i++) { synchronized (lock) { doWork(i); } } Lock Coarsening 후 synchronized (lock) { for (int i = 0; i < n; i++) { doWork(i); } } 검증 아래와 같이 테스트 코드를 설정했습니다. @Fork(..., jvmArgsPrepend = {"-XX:-UseBiasedLocking"}) @State(Scope.Benchmark) public class LockRoach { int x; @Benchmark @CompilerControl(CompilerControl.Mode.DONT_INLINE) public void test() { for (int c = 0; c < 1000; c++) { synchronized (this) { x += 0x42; } } } } -prof perfasm을 사용하여 디스어셈블리를 분석했습니다. 분석 결과, JIT 컴파일러가 Loop Unrolling을 적용하여 락 획득과 해제 빈도가 감소한 것을 확인했습니다. 다만, 루프 전체에 대해 lock coarsening이 적용되지는 않았습니다. JVM은 과도한 코어스닝이 한 스레드가 락을 오래 독점하게 만들 수 있어 위험하다고 판단했습니다. ↗ 0x00007f455cc708c1: lea 0x20(%rsp),%rbx │ < blah-blah-blah, monitor enter > ; <--- coarsened! │ 0x00007f455cc70918: mov (%rsp),%r10 ; load $this │ 0x00007f455cc7091c: mov 0xc(%r10),%r11d ; load $this.x │ 0x00007f455cc70920: mov %r11d,%r10d ; ...hm... │ 0x00007f455cc70923: add $0x42,%r10d ; ...hmmm... │ 0x00007f455cc70927: mov (%rsp),%r8 ; ...hmmmmm!... │ 0x00007f455cc7092b: mov %r10d,0xc(%r8) ; LOL Hotspot, redundant store, killed two lines below │ 0x00007f455cc7092f: add $0x108,%r11d ; add 0x108 = 0x42 * 4 <-- unrolled by 4 │ 0x00007f455cc70936: mov %r11d,0xc(%r8) ; store $this.x back │ < blah-blah-blah, monitor exit > ; <--- coarsened! │ 0x00007f455cc709c6: add $0x4,%ebp ; c += 4 <--- unrolled by 4 │ 0x00007f455cc709c9: cmp $0x3e5,%ebp ; c < 1000? ╰ 0x00007f455cc709cf: jl 0x00007f455cc708c1 - Loop Unrolling이란? - 루프 본문을 복제하여 반복 횟수와 분기 비용을 줄이는 컴파일러의 루프 최적화 기법입니다. - Loop Unrolling이 적용된 코드의 예시는 다음과 같습니다. @Fork(..., jvmArgsPrepend = {"-XX:-UseBiasedLocking"}) @State(Scope.Benchmark) public class LockRoach { int x; @Benchmark @CompilerControl(CompilerControl.Mode.DONT_INLINE) public void test() { for (int c = 0; c < 1000; c += 4) { synchronized (this) { // 동일한 본문을 4번 복제 x += 0x42; x += 0x42; x += 0x42; x += 0x42; } } } } Loop Unrolling을 통해 락 획득과 해제 빈도를 1/4 수준으로 줄일 수 있었습니다. Loop Unrolling 기능을 제한하면 성능이 4배 저하되는 것을 확인했습니다. Benchmark Mode Cnt Score Error Units # Default LockRoach.test avgt 5 5331.617 ± 19.051 ns/op # -XX:LoopUnrollLimit=1 LockRoach.test avgt 5 20679.043 ± 3.133 ns/op // 4배 느려짐 레퍼런스 https://shipilev.net/jvm/anatomy-quarks/1-lock-coarsening-for-loops/

2025년 9월 16일 · 3 분 · Donghyung Ko
Kafka JoinGroupRequest를 통한 컨슈머 그룹 리더 선출 과정

Kafka Client-Side Partition Assignment

카프카 클라이언트 라이브러리 코드를 살펴보던 와중에, 컨슈머 그룹 리밸런싱이 발생한 시점에 컨슈머가 구독할 파티션을 결정하는 ConsumerPartitionAssignor 인터페이스와 몇 가지 구현체 (RangePartitionAssignor, CooperativeStickyAssignor)가 있는 것을 확인할 수 있습니다. 연결된 컨슈머 정보를 기반으로 파티션을 분배하는 작업은 서버(예: 브로커)가 처리해야 할 작업처럼 보였는데, 이러한 로직이 클라이언트 라이브러리 코드에 포함되어 있다는 점이 다소 생소하게 느껴졌습니다. 이에 대해 조금 더 살펴보던 중, 카프카에서 이러한 구조를 채택하게 된 배경을 설명하는 문서를 찾아 공유해 드립니다. client-side 파티션 분배 정책의 필요성 파티션 분배 작업은 반드시 서버가 처리해야 하는 것은 아닙니다. 예를 들어, 파티션 분배의 책임이 브로커에 있을 경우, 새로운 파티션 분배 정책을 적용하기 위해서는 브로커를 재시작해야 하는 부담이 있습니다. 파티션 분배 정책은 매우 다양할 수 있어서 이에 대한 획일화된 검증 규칙을 적용하기 어렵습니다. 예를 들어, 하나의 파티션을 여러 컨슈머에게 동시에 배정해야 할 수도 있습니다. 커스텀한 파티션 분배 정책이 필요한 대표적인 사례는 다음과 같습니다. ...

2024년 4월 14일 · 3 분 · Donghyung Ko

Kotlin Coroutine의 동작 원리

이번 글은 Kotlin Coroutine의 디자인을 제안한 Kotlin Proposals - Coroutines를 참고하여 coroutine의 동작 원리에 대해 다루어 보도록 하겠습니다. Coroutine 원문에서는 coroutine을 한 문장으로 an instance of suspendable computation이라고 설명하고 있습니다. 이처럼, coroutine의 가장 핵심적인 특징은 중단 가능하다는 것입니다. 그렇다면, 중단 가능하다는 것은 정확히 어떤 의미일까요? Suspension 원문에 따르면, 중단 가능하다는 것은 다른 coroutine의 코드를 실행할 수 있도록 coroutine이 현재 thread에서 코드 실행을 잠시 중단하고 thread를 양보할 수 있다는 것을 의미합니다. 뿐만 아니라, 중단된 coroutine이 다른 thread에서 다시 재개될 수 있음을 뜻합니다. ...

2023년 6월 7일 · 8 분 · Donghyung Ko

Kafka Improvement Proposals 101 - Leader Epoch

안녕하세요. 이번 글에서는 leader epoch이 처음으로 제안된 (KIP-101) Alter Replication Protocol to use Leader Epoch rather than High Watermark for Truncation에 대한 번역과 개인적인 후기를 작성해보았습니다. Log Replication Protocol leader epoch에 대해 다루기 전에, 먼저 카프카에서 파티션 로그가 리더 브로커에서 팔로워 브로커로 복제되는 과정에 대해서 살펴보도록 하겠습니다. 로그 복제 과정은 producer의 ack 설정이나 min.isr 설정과 같은 요소에 따라 일부 차이가 존재할 수 있습니다만, 이번에는 핵심 용어를 중심으로 전반적인 과정을 다루는 것을 목표로 하였습니다. ...

2023년 4월 16일 · 6 분 · Donghyung Ko

[NestJS 파헤치기] 03. InstanceLoader and Injector

Intro 안녕하세요. 이전 포스팅에서는 NestJS에서 모듈과 의존성 객체의 메타데이터가 어떤 과정을 거쳐 등록되는지 살펴보았습니다. 이번 포스팅에서는 모듈에 등록된 의존성 객체 인스턴스의 라이프사이클(생성, 주입, 제거)을 관리하는 InstanceLoader와 Injector에 대해 알아보도록 하겠습니다. Dependency Injection in NestJS Dependency Injection(이하 의존성 주입)은 인스턴스들의 의존 관계를 선언적으로 표현하고, 의존 관계의 파싱과 인스턴스 생성은 일반적으로 프레임워크에서 관리하는 IoC 컨테이너에 위임하는 프로그래밍 방법론입니다. 의존성 주입과 관련된 자세한 내용은 이번 포스팅의 범위를 벗어나므로 생략하도록 하겠습니다. 의존성 주입을 구현할 때에는 객체들의 의존 관계를 정확히 파싱하고 이를 순서대로 조율해주는 과정이 매우 중요합니다. NestJS에서는 크게 생성자 기반(constructor-based) 방식과 프로퍼티 기반(property-based) 방식이 있습니다. 아래 예시에서 CatController는 CatService에 의존성을 가지고 있습니다. 따라서 CatController의 인스턴스를 생성하려면 먼저 CatService의 인스턴스를 생성해야 하고, 이 인스턴스를 CatController를 생성할 때 주입해주어야 합니다. NestJS에서는 InstanceLoader와 Injector가 이 과정을 실질적으로 주도하고 조율하는 역할을 한다고 볼 수 있습니다. ...

2022년 11월 28일 · 8 분 · Donghyung Ko

[NestJS 파헤치기] 02. @Module and DynamicModule

안녕하세요. 이전 포스팅에서는 NestFactory가 NestApplication을 생성하는 과정을 다루었습니다. 이번 포스팅에서는 NestJS를 구성하는 핵심 요소 중 하나인 Module이 어떻게 애플리케이션에 등록되는지 알아보겠습니다. @Module NestJS에서는 모듈을 선언할 때 @Module 데코레이터를 사용합니다. NestJS 공식 문서에서는 @Module이 Nest가 애플리케이션 구조를 관리하는 데 필요한 메타데이터를 다루는 용도로 사용된다고 설명합니다. (다만, 여기서 말하는 모듈은 NestJS가 내부적으로 사용하는 Module과 구별되는 개념입니다.) A module is a class annotated with a @Module() decorator. The @Module() decorator provides metadata that Nest makes use of to organize the application structure. ...

2022년 11월 18일 · 5 분 · Donghyung Ko

[NestJS 파헤치기] 01. NestFactory

안녕하세요. 최근 여러 가지 일로 바쁜 나날을 보내다 보니 오랜만에 포스팅을 하게 되었습니다. 이번에는 NestJS 파헤치기라는 주제로 글을 작성해보려 합니다. 최근 업무상 NestJS를 새롭게 사용하게 되었는데, 그 과정에서 NestJS 코어와 관련한 지식이 부족하여 기능을 구현하는 데 어려움을 느끼는 일이 잦아졌습니다. 그래서 개인적인 공부를 위해 NestJS 프레임워크를 개괄적으로 살펴보는 시간을 가져보고자 마음먹었습니다. 이번에는 그 첫 시간으로 모든 NestJS 어플리케이션의 진입점에 해당하는 NestFactory 클래스에 대해 살펴보도록 하겠습니다. NestFactory NestJS 공식문서를 살펴보면, 처음 마주하는 튜토리얼에서 다음과 같은 코드를 찾아볼 수 있습니다. ...

2022년 11월 15일 · 8 분 · Donghyung Ko
iptables mangle 테이블의 패킷 처리 흐름

A Deep Dive into iptables and Netfilter

이 글은 원문 A Deep Dive into Iptables and Netfilter Architecture를 번역하여 작성한 글입니다. iptables란? iptables는 netfilter 프레임워크를 제어하는 데 사용되는, 리눅스 커널에 내장된 네트워크 스택입니다. NIC를 거쳐 유입되는 모든 네트워크 패킷은 netfilter에 등록된 룰을 거쳐 제어됩니다. iptables는 netfilter가 패킷 조작을 위해 제공하는 훅에서 룰을 관리하는 역할을 합니다. iptables를 활용하면 특정 조건에 부합하는 패킷을 드롭하거나, 패킷의 출발지(source) 또는 목적지(destination)를 수정하는 것 등이 가능합니다. Netfilter Hook netfilter는 패킷 제어를 위한 다섯 가지 훅을 제공합니다. 모든 패킷에는 전송 방향(incoming 혹은 outgoing)에 따라 각 훅에 등록된 패킷 제어 규칙이 적용됩니다. netfilter에서 지원하는 훅은 다음과 같습니다. ...

2022년 3월 7일 · 5 분 · Donghyung Ko
상위 객체에 대한 내재 락(Intent Lock)의 필요성을 보여주는 도식

데이터베이스 락(Lock)의 종류와 역할

락(Lock)이란? 데이터베이스는 여러 사용자들이 같은 데이터에 동시에 접근하는 상황에서, 데이터의 무결성과 일관성을 지키기 위해 락을 사용합니다. Lock의 종류 데이터베이스의 락은 크게 다음과 같은 종류로 분류할 수 있습니다. 공유 락(Shared Lock) 공유 락은 데이터를 변경하지 않는 읽기 명령에 부여되는 락으로, Read Lock이라고도 불리며 Shared의 앞 글자를 따서 주로 S로 표기합니다. 여러 사용자가 동시에 데이터를 읽어도 데이터의 일관성에는 아무런 영향을 주지 않기 때문에, 공유 락끼리는 동시에 접근할 수 있습니다. 베타 락(Exclusive Lock) 베타 락은 데이터를 변경하는 쓰기 명령에 부여되는 락으로, Write Lock이라고도 불리며 X로 표기합니다. 베타 락은 이름처럼 다른 세션이 해당 자원에 접근(예: SELECT, INSERT 등)하는 것을 막습니다. 이러한 점에서 베타 락은 멀티 스레딩 환경에서 임계 영역을 안전하게 관리하기 위해 활용되는 뮤텍스와 유사하다고 볼 수 있습니다. 베타 락은 트랜잭션이 진행되는 동안 유지됩니다. ...

2022년 1월 21일 · 3 분 · Donghyung Ko
Linux Bridge 네트워크 구성도

가상 네트워크 인터페이스 (Linux Virtual Networking Interface)

이 글은 아래의 원문을 읽고 내용의 일부를 한글로 정리한 글입니다. https://developers.redhat.com/blog/2018/10/22/introduction-to-linux-interfaces-for-virtual-networking/ 리눅스는 컨테이너 기술의 기반이 되는 가상 네트워킹(virtual networking) 관련 기능을 다양하게 제공한다. 이 글에서는 리눅스 가상 네트워킹에서 자주 사용되는 대표적인 네트워크 인터페이스를 살펴본다. Bridge Linux Bridge는 일반적인 네트워크 스위치와 유사하게 동작한다. Bridge는 주로 라우터, 게이트웨이, VM 등에서 패킷을 목적지로 전달(forwarding)하는 역할을 수행한다. Bridge는 STP, VLAN filter, multicast snooping 등의 기능도 추가로 지원한다. 아래 소스 코드는 리눅스에서 Bridge를 생성하고 서로 다른 네트워크 인터페이스와 연결하는 과정을 보여주는 예시이다. 이 과정을 거치면 브릿지를 통해 VM 1, VM 2와 network namespace 1은 서로 통신할 수 있게 된다. ...

2021년 6월 24일 · 3 분 · Donghyung Ko