전체 글 109

Virtual Thread I/O 작업 5배 빠르게 만들기: 외부 API 동시 호출

회사에서 데이터 마이그레이션 위한 Update 문을 추출하는 작업이 있었습니다. 수십만 건의 데이터를 외부 API를 호출하는 로직이 포함되어 있었습니다. 가장 단순한 방법은 for 루프를 돌며 데이터를 하나씩 순차적으로 처리하는 것입니다. 하지만 이 방식은 I/O 작업이 대부분인 경우 엄청나게 비효율적이며, 하염없이 기다려야 하는 고통의 시간이 시작됩니다. 외부 API를 호출할때 Thread를 여러개 만들어서 병렬로 호출해서 시간을 단축하려고 했으나, Java 21의 새로운 기능인 가상 스레드(Virtual Threads)를 사용해보고 싶어서 적용해보았습니다. 이 글에서는 단순한 for 루프를 사용한 순차 처리 방식이 왜 느린지 알아보고, 이를 Java 21의 가상 스레드와 Semaphore를 이용해 어..

카테고리 없음 2025.07.14

[QueryDsl] querydsl-core 모듈에 기여한 이야기

Querydsl에서 이전에 발견했던 버그를 해결하고 PR이 병합된 후에, 드디어 Querydsl 6.11 버전에 제가 기여한 코드가 포함되었습니다.올해 초만 해도 오픈소스에 기여하는 개발자들을 보며 막연히 멋있다고만 생각했었는데, 제 이름이 실제 릴리스 노트에 포함된 것을 보니 정말 x10000 기뻤습니다.그래서 목표를 '올해 안에 오픈소스 기여 딱 1개만 하자!'에서 '이왕 하나 성공한 거, 3개까지 도전해보자!'라고 과감하게 바꾸었습니다.사실 3개라는 목표는 이루었습니다. Querydsl 6.12 버전에 제가 올린 PR 2개가 추가로 병합되었습니다.https://github.com/OpenFeign/querydsl/releases/tag/6.12 Release QueryDSL 6.12 · OpenFe..

카테고리 없음 2025.06.15

QueryDsl 6.11 의 컨트리뷰터가 되다. (Hibernate 6 + Querydsl CASE문 Enum 호환성 이슈 해결한 썰)

Querydsl에서 select문 서브쿼리를 사용하기 위해 CASE WHEN 구문을 사용하고자 했는데, 파라미터로 ENUM을 전달하니 오류가 발생했다. 결국 .name()으로 문자열 리터럴을 전달하는 방식으로 우회해서 문제를 해결하기는 했지만, 뭔가 찜찜했다. 구글링해보니, querydsl 공식 레포에 누군가 이슈를 등록해놓았고 오픈소스 기여에 평소 관심이 있던 차라서 도전해보았다. 운이 좋게도 PR이 병합되었고, querydsl 6.11 release에 포함되었다. 문제 배경: Hibernate 6에서 깨지는 Enum CASE 쿼리Querydsl은 TypeSafety한 JPQL 쿼리를 생성할 때, 상수 값 처리를 위해 바인드 파라미터(?) 또는 enum의 클래스 경로를 출력하는 방식을 사용해왔다. 그..

카테고리 없음 2025.05.31

JPQL 서브쿼리 성능을 개선해보자. COUNT에서 HAVING, 그리고 NOT EXISTS까지

개발을 하다 보면 특정 조건을 만족하는 “유일한” 레코드만 조회해야 할 때가 있습니다. 최근 JPQL로 특정 VIN(vin_no) 차대번호에 대해 상태코드가 ‘60’인 계약이 딱 한 건만 존재하는 경우만 조회하는 쿼리를 작성하면서 이러한 상황을 겪었습니다. 초기 구현은 동작했지만 성능 병목이 있었고, 이를 해결하기 위해 점진적으로 쿼리를 개선해 나갔습니다. 이 글에서는 제가 COUNT 상관 서브쿼리 -> GROUP BY + HAVING -> NOT EXISTS 순으로 쿼리를 개선해 간 과정을 공유하고, 각각의 방식의 장단점을 살펴보겠습니다.1단계: 상관 서브쿼리와 COUNT(*) = 1를 사용한 초기 접근초기에는 상관 서브쿼리를 사용하여 해당 VIN에 상태코드 ‘60’인 계약 건수가 정확히 1개인지 검사..

카테고리 없음 2025.05.20

Spring Batch 입문 : 출고일 누락 보정 배치 구축기

1. 문제 정의: 출고일 누락 이슈회사에서 운영중인 서비스에서분명 상품을 구매해서 어플에 등록까지 했는데, 앱 내의 특정 서비스에서 구매한 상품이 없다는 화면이 표출된다는 VoC가 접수되었습니다.외부 서비스에서 회원이 소유한 상품 목록을 조회하기 위해 API를 호출하는데, API 응답 중 출고일자 필드가 null로 반환되는 문제가 발생했습니다.
클라이언트 애플리케이션은 이 출고일자를 기반으로 비즈니스 로직을 수행하는데, null 값으로 인해 오류 메시지를 뱉거나 잘못된 동작이 이어졌습니다. 정상흐름이라면 아래와 같이 수행되어야 합니다.상품이 출고 되면 외부 판매 시스템이 contract 테이블의 해당 상품 레코드를 SHIPPED 상태로 업데이트하고, 출고일자를 기록사용자가 앱에서 상품을 등록하면 prod..

카테고리 없음 2025.05.10

@Where를 대신 @Filter를 사용해야 하는 이유

보틀노트 개발 중 엔티티에 조건을 걸어 조회해야 하는 상황이 생겼다.유저에 대해서 Soft Delete를 사용중인데, Active한 유저만 조회해야 하는 상황과, 삭제 된 회원까지 모두 조회해야 하는 상황이 있었다.이번 포스팅에서는 @Where을 사용하면 안되는 이유와 @Filter를 적용했던 이유, 그리고 마주친 난관들에 대해서 서술했다.@Where을 적용했던 배경, 그 단점을 깨달았던 순간, 그리고 @Filter를 통해 유연성을 확보하고 FilterManager로 동적 제어를 구현했던 과정을 중심으로 기록했다.1. @Where을 적용했던 배경프로젝트에서 사용자 엔티티(User)를 다루면서 기본적으로 활성 상태(status = 'ACTIVE')인 유저만 조회하려고 했다.탈퇴한 유저(status = 'D..

카테고리 없음 2025.03.08

Pagination의 사실과 오해 (Offset vs No Offset)

보틀노트 개발 초기 위스키 조회 API를 개발할 당시에, 커서 기반 페이지네이션을 사용해서 구현했었다.그런데 조회 API를 테스트해보니, 점점 뒷 페이지로 갈 수록 쿼리속도가 느려지는 듯한 느낌을 받아서 쿼리를 살펴보았다.QueryDsl로 작성한 코드에 OFFSET, LIMIT절이 존재했다. 구현이 잘못되어 있었다.lastId만 메타정보에 제공한다고 커서 기반 페이지네이션이 아니었다... 그냥 무한스크롤을 구현할 수 있는 Offset 방식일 뿐이었다.같은 실수를 반복하지 않기 위해  두 방식의 차이를 정리하고 성능상의 차이도 정리해보았다. Offset 기반의 PaginationOffset 방식은 쿼리 조건에 맞는 전체 결과 집합을 생성한 후, 지정된 OFFSET 값만큼의 행을 건너뛰고 나머지 결과를 반환..

카테고리 없음 2025.03.06

API Key 기반의 Spring Security 인증 로직 구현하기

회사에서 API Key 기반의 Spring Security 인증 로직을 구현했는데, 이 참에 Spring Security 전체적인 구조에 대해서 학습했다.불과 작년만해도 정말 헤맸었는데, Grok3, Claude 등 여러 AI 모델 선생님들과 함께하며 모르는 부분을 정확히 짚으니, 어떤 클래스를 필수로 구현해야 하고, 어떤 클래스가 인증 프로세스에서 어떤 역할을 하는지 등.. 애매했던 부분을 확실히 이해할 수 있었다.1. API 키 기반 인증이란?API 키 기반 인증은 클라이언트(예: 다른 서버나 애플리케이션)가 서버에 요청을 보낼 때, 고유한 키(API 키)를 포함시켜 자신의 신원을 증명하는 인증 방식이다.핵심 아이디어: API 키는 클라이언트와 서버 간의 신뢰를 나타내는 토큰이다. 서버는 이 키를 검..

카테고리 없음 2025.03.04

QueryDSL에서 Enum 처리 오류와 해결 방법: CaseBuilder vs String 변환

QueryDSL에서 SELECT 절 서브쿼리와 Enum을 함께 다룰 때 예기치 않은 문제가 발생했다.이번 포스팅에서는 팔로워 목록을 조회하는 쿼리에서 발생한 오류와 이를 해결한 두 가지 방법을 비교 분석해 보았다.나를 팔로우 하고 있는 회원을 조회하는 API에서 구현이 잘못된 부분이 있었다.나의 팔로워 조회 API 여도,  그 중 status 필드는 나 기준으로 상대방에 대한 팔로우 여부를 나타내야해서 서브쿼리를 작성해야 했다.문제 상황 다음은 팔로워 목록을 조회하며, 특정 조건에 따라 FollowStatus Enum 값을 반환하려는 QueryDSL 쿼리다.private List getFollowerDetails(Long userId, Long cursor, Long pageSize) { QFoll..

카테고리 없음 2025.03.03

[개취뽀] 개발자 오프라인 미팅에 참여하고 나서

개취뽀(개발자 취업 뽀개기) 오프라인 미팅토스 출신 7년차 백엔드 개발자이자 유튜버 '딩코딩코'로 활동하고 계신 박현준님께서 주최하신 오프라인 미팅에 참여했다. 개인적으로 생각하는 가장 이상적인 조직문화를 갖고 있는 토스에서 7년을 일하신 분이라 모든 영상을 챙겨보던 구독자였는데, 오프라인 미팅을 주최하셔서 무조건 참여해야겠다 싶었다. 유튜브에서 오프라인 미팅까지 만들어주신 딩코딩코님이 정말 대단하다고 느껴졌다. 장소는 신촌이었고, 대부분이 BE 개발자분들었지만 FE 분들도 꽤 참여하신 것 같았다. 실제로 보면 키도 크시고 매번 영상으로만 봤었는데, 실물을 보니 연예인을 보는 듯한 기분이었다(ㅋㅋ)개취뽀(개발자 취업 뽀개기)라는 이름에서 알 수 있듯주로 취준생 분들이나, 신입 ~ 3년차 분들이 많을 것이..

카테고리 없음 2024.12.01