본문 바로가기

분류 전체보기92

[Note] MySQL utf8mb4_unicode_ci 들어가기 전공급사로부터 받은 이미지 caption을 내부 카테고리로 매핑하는 배치가 있다. 구조는 단순하다. 미리 적재해 둔 매핑 테이블을 부트스트랩 시점에 캐시로 올려두고, 런타임에 caption을 정규화(canonicalize)한 뒤 캐시에서 조회하는 것이다. 그런데 어느 날, 분명히 매핑 데이터를 넣어 뒀는데도 특정 caption이 계속 UNMATCHED로 처리됐다. SQL 파일에도 있고, 미매핑 추적용 CSV에도 있었다. DB 부트스트랩은 항상 실행되는 always 모드였다. 로그만 보면 완벽하게 정상이었다. 처음엔 배치 코드 쪽을 의심했다. SQL 파일이 제대로 실행됐는지, 캐시 로딩에 타이밍 문제가 있는 건 아닌지, canonical key 생성 과정에서 뭔가 빠지는 건 아닌지. 코드를 훑어봐.. 2026. 4. 26.
[Spring] Kotlin DSL로 Spring Batch 설정하기 들어가기 전이전에 Kotlin DSL에 대해 글을 썼을 때, 마지막을 이렇게 맺었다. "Internal DSL은 기반 언어의 생태계를 그대로 사용할 수 있다는 것이 강점이다." 그 강점을 실제로 써먹어 보고 싶다는 생각은 있었는데, 마침 Spring Batch 설정 코드를 정리할 일이 생겼다. 계기는 단순했다. 배치 Job을 하나 새로 만들 때마다 반복적으로 작성해야 하는 코드가 있었다. JobBuilder, StepBuilder를 직접 생성하고, JobRepository를 주입하고, transactionManager를 연결하고. 설정의 의도보다 설정의 절차가 코드를 더 많이 차지하고 있었다. 이걸 줄이고 싶었고, Kotlin DSL이 적합하다고 판단했다. 무엇을 감추고 싶었나Spring Batch 5... 2026. 4. 21.
[Note] ItemReader를 어떻게 구현해야할까 Spring Batch로 대용량 배치를 작성하다 보면 ItemReader를 직접 구현해야 하는 상황이 생긴다. 특히 cursor 기반 페이징을 직접 제어해야 할 때, 자연스럽게 두 가지 구현 방향 중 하나를 선택하게 된다.이 글은 그 두 방향을 비교하고, 왜 구조적 명확성 측면에서 하나의 방식이 더 나은지 정리한 내용이다.배경: cursor 기반 페이징이 필요한 이유대용량 배치에서 offset 기반 페이징은 잘 알려진 함정이 있다.SELECT * FROM property ORDER BY id LIMIT 500 OFFSET 100000-- offset이 커질수록 앞의 100000건을 매번 스캔 → 후반부로 갈수록 느려짐이를 피하기 위해 lastId를 커서로 사용하는 방식을 쓴다.SELECT * FROM p.. 2026. 4. 19.
[Note] Elasticsearch ID 필드 타입 설계 사내에서 제공하는 검색 SDK를 통해 Elasticsearch 기반 숙소 검색 시스템을 구성하고 있었다. regionId, poiId, categoryId, roomViewIds 같은 필드들은 SDK가 제공하는 NumberField 타입으로 정의되어 있었고, 나는 이 이름을 보고 별다른 의심 없이 사용했다. 숫자 값을 저장하는 필드이니 NumberField가 당연히 맞는 선택이겠거니 했다. 이 필드들이 어떤 ES 타입으로 실제 매핑되는지, 그 타입이 내부적으로 어떤 자료구조를 쓰는지는 생각하지 않았다.의문이 생긴 건 외부 기술 블로그를 읽다가였다. Elasticsearch에서 숫자 타입과 keyword 타입이 내부적으로 전혀 다른 자료구조를 사용하며, 쿼리 패턴에 따라 그 선택이 성능에 의미 있는 차이를.. 2026. 4. 15.
[Note] MySQL MATERIALIZED 전략 이해하기 들어가기 전개발 환경의 이미지 테이블에 약 1억 건이 적재되어 있는 상황에서 배치 애플리케이션을 테스트하던 중, 실수로 중복 프로퍼티가 약 10만 건 적재됐다. 인덱스는 충분히 구성되어 있었고, 삭제 대상만 별도로 관리하기 위해 임시 테이블(tmp_delete_provider_property_ids)도 미리 준비해 둔 상태였다.처음 실행한 쿼리는 다중 테이블 DELETE였다. delete afrom tb_provider_property_image ajoin tmp_delete_provider_property_ids t on t.provider_property_id = a.provider_property_idwhere a.provider_id = 2; JOIN 방식의 DELETE는 실행 계획상 N.. 2026. 3. 30.
[Note] 혼합 워크로드 환경에서의 처리 전략 들어가기 전배치 애플리케이션을 설계할 때 흔히 저지르는 첫 번째 오류는, 모든 작업을 하나의 성격으로 간주하는 것이다. 실제 운영 환경에서의 배치는 대개 순수한 I/O 바운드도 아니고 순수한 CPU 바운드도 아니다. 두 특성이 단계적으로, 혹은 동시에 교차한다. 문제는 이 혼합 상태를 단일 실행 모델로 처리하려 할 때 발생한다. 예를 들어 Reader는 DB에서 이미지 메타데이터를 조회하고, Processor는 외부 HTTP API를 호출하여 이미지를 분석한 뒤, 추가적으로 로컬에서 feature 계산을 수행하며, Writer는 결과를 upsert한다. 이 구조는 전형적인 혼합 워크로드다. HTTP 호출은 I/O Burst이고, feature 계산은 CPU Burst다. Writer는 다시 JDBC ba.. 2026. 2. 24.
[Kotlin] KAPT와 KSP 들어가기 전 KAPT와 KSP를 이해하려면, 결국 자바 어노테이션 프로세서부터 봐야 한다. 코틀린에서 KAPT와 KSP를 논하기 전에 반드시 짚고 넘어가야 할 전제가 하나 있다. KAPT와 KSP는 “코틀린의 기능”이 아니라, 자바 어노테이션 프로세싱이라는 오래된 JVM 생태계의 설계를 어떻게 수용하거나, 혹은 거부할 것인가에 대한 선택지다. 자바의 어노테이션 프로세서는 컴파일러 확장 포인트로서 설계되었다. 소스 코드에 선언된 어노테이션을 읽고, 그 정보를 기반으로 새로운 소스 코드를 생성하는 메커니즘이다. 대표적으로 Lombok, MapStruct, Dagger, QueryDSL 같은 라이브러리들이 이 메커니즘 위에서 동작한다. 중요한 점은 이 시스템이 자바 컴파일러(javac)의 타.. 2026. 2. 13.
[Note] 성능 개선 전략: 인덱스, 역정규화 들어가기 전성능 개선을 이야기할 때 가장 흔하게 등장하는 단어는 인덱스다.쿼리가 느리면 인덱스를 추가하고 그래도 느리면 인덱스를 하나 더 추가한다.하지만 실무에서는 인덱스를 아무리 추가해도 성능이 거의 개선되지 않는 경우가 분명히 있을 뿐더러 오히려 무분별한 인덱스 추가는 쓰기 성능 저하, 저장 공간 증가, 인덱스 유지·관리 비용 상승 같은 부작용을 낳을 수 있다.이 글은 그런 상황에서 출발한다.호텔을 검색할 때 전형적인 읽기 중심 도메인을 기준으로 정규화된 모델이 왜 느려지는지 그 문제를 해결하기 위해 왜 역정규화를 먼저 선택하게 되는지 그 이후 인덱스와 비트맵 설계가 어떤 역할을 맡게 되는지를 순서대로 정리한다.핵심은 "어떤 기법이 빠르다"가 아니라 "어떤 문제에 어떤 순서로 접근해야 하는가" 다.1.. 2026. 2. 2.
[Spring] TransactionManager 들어가기 전Spring의 트랜잭션 매니저는 PlatformTransactionManager라는 추상화를 통해 다양한 데이터 접근 기술에 대해 일관된 트랜잭션 처리를 제공한다. 그러나 실전에서는 단일 트랜잭션 경계만으로 설명되지 않는 복잡한 상황이 자주 등장한다. 특히 멀티 데이터소스, 이벤트 기반 처리, 비동기 작업 등이 혼재하는 환경에서는 Spring의 추상화 이면을 명확히 이해하고 있어야 한다. 본 글은 이러한 맥락에서 트랜잭션 매니저의 역할, 동기화 메커니즘, 컨테이너 내부 동작 방식, 이벤트 연계와 경계의 모호성까지 작성하고자 한다.TransactionManager의 추상화Spring의 트랜잭션 매니저는 단순히 데이터베이스 트랜잭션을 제어하는 기술적 역할을 넘어 본질적인 애플리케이션의 상태 전이와.. 2026. 1. 26.