<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel><title>노갬성</title><link>https://nogamsung.github.io/</link><description>백엔드 개발자 안검성(nogamsung)의 기술 블로그. Spring Boot, JPA, Querydsl, Redis, Kong API Gateway, Airflow 등 실무에서 겪은 문제와 해결 과정을 기록합니다.</description> <language>ko-kr</language> <lastBuildDate>Thu, 05 Nov 2026 12:55:54 +0900</lastBuildDate> <generator>Jekyll 4.4.1</generator> <atom:link href="https://nogamsung.github.io/rss.xml" rel="self" type="application/rss+xml" /> <item><title>온프레미스 서버에 Kamal로 무중단 배포 도입하기</title><link>https://nogamsung.github.io/posts/kamal-zero-downtime/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/kamal-zero-downtime/</guid><pubDate>Wed, 04 Nov 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>devops</category> <category>deployment</category> <description>Watchtower로 컨테이너를 다시 만들던 배포가 재배포 한 번에 서비스 대부분을 503으로 만든 일을 계기로, 직접 블루그린·Kamal·Coolify·Docker Swarm을 비교하고 Kamal을 고른 과정과 Tailscale SSH·서비스 간 호출·한 서버의 dev/prd·SMTP·예약 작업처럼 옮기면서 만난 문제를 정리합니다.</description> </item> <item><title>권한을 바꿨는데 30분 동안 옛 권한으로 동작한다, JWT에 권한 버전 심기</title><link>https://nogamsung.github.io/posts/jwt-permission-version/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/jwt-permission-version/</guid><pubDate>Tue, 03 Nov 2026 18:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>backend</category> <category>spring</category> <description>stateless JWT의 roles 클레임은 발급 시점 값이라 관리자가 권한을 바꿔도 토큰 수명(30분) 동안 옛 권한으로 메뉴와 API가 동작했습니다. 블랙리스트 대신 토큰에 권한 계산 시각(pv)을 넣고 Redis의 변경 시각과 비교해 401로 끊는 설계, 커밋 전 캐시 재적재 레이스, fail-open 선택까지 정리합니다.</description> </item> <item><title>무중단 배포에서 두 번 도는 예약 작업, ShedLock으로 막기</title><link>https://nogamsung.github.io/posts/shedlock-scheduled-lock/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/shedlock-scheduled-lock/</guid><pubDate>Tue, 03 Nov 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>backend</category> <category>spring</category> <description>무중단 배포로 Spring Boot 배치 서버가 잠깐 두 개 뜨는 동안 @Scheduled 작업이 두 번 실행되는 문제를, 분산 락·Spring Batch·Quartz와 비교한 뒤 ShedLock(JDBC)과 lockAtLeastFor로 해결하고 graceful shutdown·테스트까지 갖춘 과정을 정리합니다.</description> </item> <item><title>내부 서비스 호출에 서킷브레이커를 넣으며 권한 조회를 fail-open에서 fail-closed로 바꾼 이야기</title><link>https://nogamsung.github.io/posts/circuit-breaker-fail-closed/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/circuit-breaker-fail-closed/</guid><pubDate>Mon, 02 Nov 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>backend</category> <category>spring</category> <description>서비스 API 9개가 account 서비스에 권한을 묻는 구조에서, 조회 실패 시 &quot;허용&quot;하던 fail-open 설계는 장애가 곧 권한 우회였습니다. Resilience4j로 호출부 단위 회로 4개를 두고, 무엇을 실패로 셀지(4xx 제외), 회로 OPEN을 liveness에 연결하지 않는 이유, 권한 조회를 503 fail-closed로 바꾼 결정과 그 직후 드러난 배포 공백 문제까지 정리합니다.</description> </item> <item><title>Claude Code와 Codex를 같은 하네스로, CLAUDE.md에서 AGENTS.md로</title><link>https://nogamsung.github.io/posts/claude-codex-shared-harness/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/claude-codex-shared-harness/</guid><pubDate>Tue, 27 Oct 2026 18:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>ai</category> <category>agent-workflow</category> <description>한쪽 구독이 끊겨도 다른 쪽 하나로 전부 돌아가게, 그리고 손으로 두 벌 고치는 설정 파일 없이. 지침 파일을 AGENTS.md 하나로 바꾸고, 빌드·커버리지 게이트를 에이전트 훅에서 git 훅으로 옮기고, 스킬 32개를 한 폴더에 모으고, PR마다 반대편 모델이 리뷰하게 만든 하루의 PR 다섯 개를 정리합니다. 그 과정에서 발견한 &quot;한 번도 막지 못하고 있던 훅&quot;과 &quot;한 번도 돌지 않고 있던 git 훅&quot; 이야기도 함께.</description> </item> <item><title>Spring Boot 4 업그레이드, 그리고 final 엔티티 때문에 LAZY가 사실 EAGER였다</title><link>https://nogamsung.github.io/posts/spring-boot-4-kotlin-lazy-loading/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/spring-boot-4-kotlin-lazy-loading/</guid><pubDate>Tue, 27 Oct 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>backend</category> <category>spring</category> <description>Spring Boot 3.4에서 4.1로, Kotlin 1.9에서 2.3(K2)으로 18개 모듈을 하루에 올리며 알게 된 것들. Kotlin 2.x의 plugin.jpa가 all-open을 켜면서 final이던 엔티티가 open이 되고, 그제서야 @ManyToOne(LAZY) 67곳이 지금까지 EAGER로 돌고 있었다는 사실과 가짜 fetch join 179곳이 드러났습니다. Jackson 3 때문에 전 요청이 39ms 만에 408이 난 사고도 함께 정리합니다.</description> </item> <item><title>next start는 next.config를 어디서 읽는가, Docker 런타임 이미지에서 세 번 데인 기록</title><link>https://nogamsung.github.io/posts/next-start-runtime-config-docker/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/next-start-runtime-config-docker/</guid><pubDate>Tue, 20 Oct 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>frontend</category> <category>nextjs</category> <description>빌드 스테이지에서 .next만 복사한 Next.js 런타임 이미지가 두 달 사이 세 번 발목을 잡았습니다. images.remotePatterns가 무시돼 이미지가 400, basePath가 사라져 전 라우트 404, 비루트 사용자로 바꾸자 corepack이 pnpm을 다시 받으려다 멈추고 로거의 mkdir이 EACCES로 모든 BFF가 HTML 500. Next 소스에서 어떤 설정이 빌드 산출물에 들어가고 어떤 설정을 next start가 런타임에 읽는지 확인하고, 비루트 컨테이너 점검 목록을 정리합니다.</description> </item> <item><title>제품 안의 AI, 채팅 어시스턴트를 5일 만에 뺀 이유와 4개월 뒤 문서 판정기로 다시 넣은 이유</title><link>https://nogamsung.github.io/posts/ai-in-product-chat-to-judge/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/ai-in-product-chat-to-judge/</guid><pubDate>Thu, 08 Oct 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>ai</category> <category>product</category> <description>5월에 Claude 채팅 위젯을 ERP에 넣었다가 5일 만에 통째로 걷어냈고, 10월에 같은 Claude API를 배치 안의 &quot;문서 판정기&quot;로 다시 들였습니다. 바뀐 것은 모델이 아니라 문제 정의, 비용 단위, 실패 처리였습니다. 건당 $0.23, 하루 $37이라는 실측을 보고 100배 싼 판정 모델을 게이트로 앞세워 하루 $10을 목표로 재설계한 과정을 숫자와 함께 정리합니다.</description> </item> <item><title>서브도메인 6개를 한 호스트의 basePath로 옮기기, 무중단 4단계와 Next가 접두를 안 붙여 주는 곳</title><link>https://nogamsung.github.io/posts/nextjs-basepath-migration/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/nextjs-basepath-migration/</guid><pubDate>Sun, 04 Oct 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>frontend</category> <category>nextjs</category> <description>각자 서브도메인에 살던 Next.js 앱 6개를 포털 호스트 아래 경로 접두(example.com/svc)로 옮겼습니다. 앱 배포와 프록시 반영이 같은 순간일 필요가 없게 만든 4단계 순서, Next가 basePath를 붙여 주지 않는 fetch·문자열 src·window.location을 전역 패치로 처리한 방법, 누락을 찾아내는 &quot;접두 밖은 404&quot; 프록시, 쿠키를 host-only로 바꾼 이유, 그리고 서비스워커 4개가 FCM 토큰을 서로 지우던 문제까지 정리합니다.</description> </item> <item><title>Refresh 토큰 회전을 넣었다가 하루 만에 뺀 이유</title><link>https://nogamsung.github.io/posts/refresh-token-rotation-rollback/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/refresh-token-rotation-rollback/</guid><pubDate>Fri, 04 Sep 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>backend</category> <category>spring</category> <description>보안 리뷰 권고대로 refresh 토큰 회전과 재사용 탐지를 넣었더니 30분마다 전 사용자가 모든 서비스에서 동시에 로그아웃됐습니다. 데스크톱 셸이 한 refresh 토큰을 웹앱 5개 쿠키와 자기 저장소에 복제하는 구조에서 &quot;재사용 = 탈취&quot; 판정이 어떻게 오판으로 바뀌는지, 그리고 회전을 걷어내고 무엇을 남겼는지 정리합니다.</description> </item> <item><title>LiveKit 통화 종료 정산 경합, REST leave와 webhook이 같은 상태를 바꿀 때</title><link>https://nogamsung.github.io/posts/livekit-call-settlement-race/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/livekit-call-settlement-race/</guid><pubDate>Thu, 03 Sep 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>backend</category> <category>spring</category> <description>사내 메신저에 LiveKit SFU로 음성 통화를 붙인 뒤 사흘 동안 겪은 세 가지 문제를 정리합니다. 인증 필터가 LiveKit 서명 JWT를 유저 JWT로 검증해 webhook이 전량 401이 되던 것, 동기 REST leave와 비동기 webhook이 같은 통화 상태를 바꾸며 생긴 정산 경합을 멱등 정산으로 푼 것, 유저 단위 STOMP 토픽으로는 응답자의 다른 기기 벨을 끌 수 없던 것입니다.</description> </item> <item><title>안읽음 배지는 왜 세 번이나 틀렸나, 클라이언트 누적 카운터에서 서버 집계로</title><link>https://nogamsung.github.io/posts/unread-badge-server-count/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/unread-badge-server-count/</guid><pubDate>Tue, 28 Jul 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>frontend</category> <category>realtime</category> <description>&quot;받은 프레임마다 +1&quot;로 만든 안읽음 배지가 6주 동안 세 번 틀렸습니다. 새로고침하면 0이 되는 설계 한계, 서버가 두 토픽으로 팬아웃한 DM을 두 번 세는 문제, 그리고 메일 1통이 배지 4로 보인 사건까지. StrictMode를 의심했다가 소스로 결백을 확인하고, 좀비 소켓을 찾아낸 뒤, 결국 &quot;숫자는 서버가 세고 이벤트는 무효화만 한다&quot;로 바꾼 과정을 정리합니다.</description> </item> <item><title>백그라운드 탭에서 WebSocket이 조용히 죽는 이유, 같은 배너 뒤의 네 가지 원인</title><link>https://nogamsung.github.io/posts/websocket-background-tab-reconnect/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/websocket-background-tab-reconnect/</guid><pubDate>Fri, 24 Jul 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>frontend</category> <category>realtime</category> <description>웹 메신저의 &quot;연결이 끊겼습니다. 재연결 중...&quot; 배너가 6주 동안 네 번 다른 이유로 떴습니다. deactivate()가 재연결을 다시 부르는 플래핑, Spring SimpleBroker가 0,0으로 협상해 버린 하트비트, 숨긴 탭의 타이머 스로틀링, 그리고 직결 WebSocket이 BFF 경유 REST보다 먼저 도착해 상태가 유실되는 첫 입장 레이스까지. stompjs와 Spring 소스를 따라가며 정리합니다.</description> </item> <item><title>서브에이전트가 내 이름으로 PR을 머지했다, 에이전트에게 주면 안 되는 권한</title><link>https://nogamsung.github.io/posts/subagent-merged-pr-as-me/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/subagent-merged-pr-as-me/</guid><pubDate>Tue, 23 Jun 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>ai</category> <category>agent-workflow</category> <description>수십 개 파일의 기계적 편집을 서브에이전트에 맡겼더니, 그 에이전트가 스스로 푸시하고 PR을 만들고 7초 만에 공유 브랜치에 머지했습니다. 제 계정으로요. 테스트 12개가 깨진 채였고 dev는 50분 동안 빨간불이었습니다. 왜 가능했는지를 당시 하네스 상태로 되짚고(도구 상속, exit 1 훅, 보호 없는 브랜치), 그날 세운 규칙과 그 규칙이 강제가 아니었다는 사실까지 적습니다.</description> </item> <item><title>로그인은 200인데 쿠키가 사라진다, NextResponse.cookies는 이름으로만 키잉된다</title><link>https://nogamsung.github.io/posts/nextresponse-cookies-keyed-by-name/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/nextresponse-cookies-keyed-by-name/</guid><pubDate>Tue, 16 Jun 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>frontend</category> <category>nextjs</category> <description>같은 이름의 쿠키가 Domain만 다르게 두 개 있을 때 Next.js BFF에서 벌어진 세 가지 문제를 하루 안에 따라갑니다. 401 무한 루프, 발급 직후 쿠키가 사라지는 자기 삭제, 그리고 진짜 원인인 ResponseCookies의 &quot;이름만으로 키잉되는 Map&quot;까지. 모킹한 테스트가 왜 버그를 숨겼는지도 함께 적습니다.</description> </item> <item><title>에이전트에게 저장소를 가르치기, CLAUDE.md와 memory/와 훅과 스킬</title><link>https://nogamsung.github.io/posts/teaching-the-repo-to-agents/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/teaching-the-repo-to-agents/</guid><pubDate>Thu, 07 May 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>ai</category> <category>agent-workflow</category> <description>백엔드와 프론트엔드 저장소를 하나로 합친 날, 각자 갖고 있던 하네스 두 벌이 충돌했습니다. 하위 폴더의 .claude/와 .github/는 둘 다 죽은 설정이었고, 양쪽 타입은 이미 어긋나 있었습니다. 지침은 세 겹의 CLAUDE.md로, 설정은 stacks.json 하나로, 강제는 훅과 CI로, 기억은 memory/로, 그리고 API 계약은 OpenAPI 한 소스로. 에이전트가 읽는 것과 에이전트를 막는 것을 어떻게 나눴는지 정리합니다.</description> </item> <item><title>자동완성에서 에이전트로, 혼자 짜던 백엔드에 코딩 에이전트를 들이기까지</title><link>https://nogamsung.github.io/posts/from-autocomplete-to-agents/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/from-autocomplete-to-agents/</guid><pubDate>Thu, 16 Apr 2026 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>ai</category> <category>agent-workflow</category> <description>Copilot의 자동완성에서 ChatGPT 창에 붙여 넣기, 그리고 터미널에서 돌아가는 코딩 에이전트까지. 도구가 바뀐 4년을 짧게 훑고, 혼자 1년 가까이 짜던 Kotlin 백엔드에 오늘 처음으로 에이전트를 들인 날의 기록을 남깁니다. 첫 커밋은 기능이 아니라 깨져 있던 테스트 인프라 수정이었고, 같은 날 PR 흐름과 PR CI가 시작됐습니다. 3월의 손으로 쓴 설정이 왜 실패했는지도 함께.</description> </item> <item><title>엔티티를 웹 앱에서 꺼내기, 도메인 모듈과 API 모듈 분리</title><link>https://nogamsung.github.io/posts/gradle-domain-api-module-split/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/gradle-domain-api-module-split/</guid><pubDate>Mon, 08 Dec 2025 18:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>backend</category> <category>spring</category> <description>예약 배치 작업이 웹 애플리케이션 모듈 안에 살게 되자 구조가 잘못됐다는 게 드러났습니다. 엔티티·리포지토리·QueryDSL Q클래스를 API 모듈에서 도메인 모듈로 꺼내 Gradle 모듈 4개를 6개로 나눈 313개 파일 변경을, 왜 이 시점이었는지, scanBasePackages와 kapt·테스트 유틸은 어떻게 처리했는지, 그리고 아직 못 한 것은 무엇인지 정리합니다.</description> </item> <item><title>아무도 true로 바꾼 적 없는 locked_yn, 마감을 플래그 대신 날짜로 계산하기</title><link>https://nogamsung.github.io/posts/date-based-period-lock/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/date-based-period-lock/</guid><pubDate>Mon, 08 Dec 2025 10:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>backend</category> <category>spring</category> <description>수주·판매·입금·재고 보고의 월 마감을 위해 첫 커밋부터 있던 locked_yn 컬럼을 191일 만에 지웠습니다. 운영 코드 어디에서도 true로 바꾼 적이 없는 플래그 옆에서 날짜 규칙이 세 번 바뀌며 실제 마감을 맡고 있었기 때문입니다. &quot;상태를 저장하는 플래그&quot;와 &quot;날짜에서 계산하는 규칙&quot; 사이의 선택과 그 비용을 정리합니다.</description> </item> <item><title>PDFBox로 거래명세서 PDF 만들기, 좌표는 왼쪽 아래에서 시작한다</title><link>https://nogamsung.github.io/posts/pdfbox-transaction-statement/</link><guid isPermaLink="true">https://nogamsung.github.io/posts/pdfbox-transaction-statement/</guid><pubDate>Fri, 05 Dec 2025 00:00:00 +0900</pubDate> <dc:creator>Geomseong Ahn</dc:creator> <category>backend</category> <category>spring</category> <description>판매 건마다 종이 양식과 같은 모양의 거래명세서 PDF가 필요했습니다. Apache PDFBox 3으로 한글 폰트를 임베딩하고, 셀 병합이 있는 표를 직접 그리고, 첫 페이지 18행·이후 25행으로 미리 쪼개는 페이지네이션과 도장 이미지 합성까지 구현한 과정과, 도장 하나 추가하다 깨진 테스트 이야기를 정리합니다.</description> </item> </channel> </rss>
