<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>leo.dev</title><description>개발자 김태영(leo)</description><link>https://leo-1178-blog.vercel.app/</link><item><title>데이터로 굴리는 운영, 출시 이후의 다섯 단계</title><link>https://leo-1178-blog.vercel.app/posts/service-operation-growth-cycle/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/service-operation-growth-cycle/</guid><description>출시는 더 이상 경쟁력이 아니다. 운영은 유지보수가 아니라 분석·발견·도출·결정·개선의 사이클이고, AARRR 퍼널과 RICE로 무엇을 먼저 할지 정한다.</description><pubDate>Thu, 11 Jun 2026 00:00:00 GMT</pubDate><category>til</category><category>growth</category><category>aarrr</category><category>rice</category><category>prioritization</category><category>product</category></item><item><title>브로커 없이 만든 트랜잭셔널 아웃박스</title><link>https://leo-1178-blog.vercel.app/posts/transactional-outbox-without-broker/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/transactional-outbox-without-broker/</guid><description>상태를 바꾼 트랜잭션은 커밋됐는데 거기 딸린 메일은 fire-and-forget로 날아가 조용히 실패한다. Kafka·SQS 없이 MySQL 테이블 하나로 외부 발송을 트랜잭션에 묶고, 재시도·멱등·DB 장애의 역설까지.</description><pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate><category>backend</category><category>backend</category><category>outbox</category><category>transaction</category><category>idempotency</category><category>nestjs</category></item><item><title>무음 실패와 opt-in 관측가능성 3단계</title><link>https://leo-1178-blog.vercel.app/posts/opt-in-observability-three-stages/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/opt-in-observability-three-stages/</guid><description>운영에서 무서운 건 빨간 에러가 아니라 아무 데도 안 남는 실패다. 조용히 죽는 배치, 진짜 에러를 묻는 401 스팸. 로그 게이팅·분산추적·에러 트래킹을 환경변수로 켜고 끄게 붙였다.</description><pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate><category>backend</category><category>observability</category><category>opentelemetry</category><category>sentry</category><category>nestjs</category><category>logging</category></item><item><title>parent-only soft-delete의 접근제어 구멍</title><link>https://leo-1178-blog.vercel.app/posts/parent-only-soft-delete-access-guard/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/parent-only-soft-delete-access-guard/</guid><description>부모(회사)만 soft-delete하고 자식(직원) 테이블을 보존했더니, 삭제된 회사의 ID를 아는 사용자가 권한 검사를 그대로 통과했다. 접근제어가 자식을 직접 조회했기 때문이다.</description><pubDate>Thu, 04 Jun 2026 00:00:00 GMT</pubDate><category>backend</category><category>backend</category><category>soft-delete</category><category>access-control</category><category>multitenant</category><category>typeorm</category></item><item><title>락 없이 안 겹치는 인보이스 채번</title><link>https://leo-1178-blog.vercel.app/posts/mysql-atomic-counter-invoice-number/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/mysql-atomic-counter-invoice-number/</guid><description>INV-2026-00001 같은 채번은 유일·연속이어야 한다. 동시 발급 둘이 같은 번호를 받으면 회계 사고다. SELECT MAX+1도 비관적 락도 아닌, 단일 upsert로 직렬화하는 LAST_INSERT_ID 원자 카운터.</description><pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate><category>backend</category><category>backend</category><category>mysql</category><category>concurrency</category><category>typeorm</category></item><item><title>공유 패키지와 enum→as const 전환</title><link>https://leo-1178-blog.vercel.app/posts/monorepo-shared-package-enum-as-const/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/monorepo-shared-package-enum-as-const/</guid><description>같은 연차 일수를 프론트와 백엔드가 다르게 계산하던 버그. 원인은 같은 로직을 양쪽에 따로 짠 것이었다. 공유 패키지로 단일 출처를 만들면서 enum을 as const로 갈아엎은 이유.</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><category>backend</category><category>typescript</category><category>monorepo</category><category>pnpm</category><category>turborepo</category><category>tree-shaking</category></item><item><title>LLM 구조화 추출과 출력 신뢰 방어</title><link>https://leo-1178-blog.vercel.app/posts/llm-structured-extraction-trust/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/llm-structured-extraction-trust/</guid><description>50~100페이지 공고 PDF를 LLM에 던져 등록 폼을 자동으로 채웠다. 핵심은 추출이 아니라, 모델이 뱉은 JSON을 검증 없이 DB에 넣지 않는 설계였다. responseSchema 강제·타입 화이트리스트·confidence 등급.</description><pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate><category>backend</category><category>ai</category><category>llm</category><category>gemini</category><category>vertex-ai</category><category>nestjs</category></item><item><title>배포 길목에 박은 CI 품질 게이트</title><link>https://leo-1178-blog.vercel.app/posts/deploy-pipeline-5-ci-quality-gate/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/deploy-pipeline-5-ci-quality-gate/</guid><description>배포 파이프라인에 타입체크·린트·테스트 게이트가 0개였다. 작업이 dev 직접 push로 들어가는 플로우라, PR이 아니라 모든 배포가 지나는 길목(deploy.yml 맨 앞 verify 잡)에 단일 게이트를 박았다.</description><pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate><category>infra</category><category>ci-cd</category><category>github-actions</category><category>turborepo</category><category>typescript</category></item><item><title>죽은 검증 훅과 AI 하네스 드리프트</title><link>https://leo-1178-blog.vercel.app/posts/ai-harness-fragmentation-drift/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/ai-harness-fragmentation-drift/</guid><description>AI 에이전트로 개발하려면 규칙·스킬·검증 훅을 두른 하네스가 필요하다. 5개월에 걸쳐 쌓인 하네스를 점검하니, 좋은 의도의 정리 한 번이 자동 검증 훅을 두 달째 죽여놓고 있었다.</description><pubDate>Sat, 23 May 2026 00:00:00 GMT</pubDate><category>infra</category><category>ai</category><category>tooling</category><category>ci-cd</category><category>engineering-discipline</category></item><item><title>실 DB에서만 드러나는 버그들</title><link>https://leo-1178-blog.vercel.app/posts/real-db-integration-test-concurrency/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/real-db-integration-test-concurrency/</guid><description>mock 유닛 테스트가 전부 초록인데 운영에서 동시 요청에 잔액이 음수가 됐다. 락도 트랜잭션 격리도 원자성도 mock엔 없다. 돈과 무결성이 걸린 코드는 mock green을 통과로 인정하지 않기로 했다.</description><pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate><category>backend</category><category>backend</category><category>testing</category><category>concurrency</category><category>integration-test</category><category>mysql</category></item><item><title>비관적 락이 못 막은 write skew</title><link>https://leo-1178-blog.vercel.app/posts/write-skew-pessimistic-lock/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/write-skew-pessimistic-lock/</guid><description>pessimistic_write 락을 걸어둔 연차 차감에서 동시 신청 2건이 모두 승인돼 잔액이 음수가 됐다. 검증이 락 밖이면, 락은 lost update를 막아도 write skew를 막지 못한다.</description><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate><category>backend</category><category>concurrency</category><category>database</category><category>pessimistic-lock</category><category>write-skew</category><category>testing</category></item><item><title>연차 잔액을 컬럼 하나로 들지 않은 이유</title><link>https://leo-1178-blog.vercel.app/posts/leave-balance-ledger/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/leave-balance-ledger/</guid><description>연차 잔액을 balance 컬럼 하나로 들면 &apos;며칠 남았나&apos;는 알아도 &apos;왜 그 숫자인가&apos;는 모른다. 노무 분쟁과 감사가 걸린 숫자라, 회계 장부처럼 append-only 원장으로 모델링했다.</description><pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate><category>backend</category><category>database</category><category>typeorm</category><category>ledger</category><category>audit</category><category>concurrency</category></item><item><title>관계 모양마다 다른 N+1 처방</title><link>https://leo-1178-blog.vercel.app/posts/finding-fixing-n-plus-one/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/finding-fixing-n-plus-one/</guid><description>N+1이 보이면 반사적으로 JOIN을 붙인다. 하지만 JOIN이 맞는 건 다대일일 때뿐, 일대다에 붙이면 N+1을 행 증식으로 바꿀 뿐이다. 관계의 모양에 따라 배치 IN과 GROUP BY로 갈린다.</description><pubDate>Wed, 13 May 2026 00:00:00 GMT</pubDate><category>backend</category><category>typeorm</category><category>nestjs</category><category>n-plus-one</category><category>performance</category></item><item><title>로거 64곳을 고치지 않고 묶은 Correlation ID</title><link>https://leo-1178-blog.vercel.app/posts/async-local-storage-correlation-id/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/async-local-storage-correlation-id/</guid><description>요청 하나가 수십 줄 로그를 남기는데 서로 연결고리가 없으면 동시 요청에 뒤섞여 추적 불가다. 외부 라이브러리 없이 Node 내장 AsyncLocalStorage로 Correlation ID를 깔고, 로거 64곳을 무수정으로 묶었다.</description><pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate><category>backend</category><category>backend</category><category>observability</category><category>nestjs</category><category>async-local-storage</category><category>logging</category></item><item><title>의존성 배열의 기준: 무엇이 아니라 언제</title><link>https://leo-1178-blog.vercel.app/posts/useeffect-mutation-429/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/useeffect-mutation-429/</guid><description>exhaustive-deps는 effect가 쓰는 모든 값을 deps에 넣으라 한다. 대개 맞지만, 참조가 불안정한 값과 렌더를 유발하는 effect가 만나는 지점에서 그 규칙은 effect를 자기 자신을 먹는 뱀으로 만든다.</description><pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate><category>frontend</category><category>react</category><category>react-query</category><category>useeffect</category></item><item><title>localStorage와 React 인증 상태의 단절</title><link>https://leo-1178-blog.vercel.app/posts/login-infinite-loading/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/login-infinite-loading/</guid><description>토큰을 localStorage에 쓰고 바로 navigate했더니 앱이 로딩에서 멈췄다. 인증 상태가 localStorage(출처)와 Context(화면이 보는 파생) 두 곳에 사는데, 출처만 바꾸고 파생을 안 깨운 탓이었다.</description><pubDate>Wed, 06 May 2026 00:00:00 GMT</pubDate><category>frontend</category><category>react</category><category>context-api</category><category>authentication</category><category>localStorage</category></item><item><title>AI가 한 작업을 스스로 기록하게 만들기</title><link>https://leo-1178-blog.vercel.app/posts/ai-worklog-system/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/ai-worklog-system/</guid><description>AI로 개발하면 세션이 끝날 때마다 무엇을 했고 왜 그렇게 정했는지가 휘발된다. 이력서·회고의 1차 자료가 매번 사라진다. 자동 기록을 붙이며 배운 건, 트리거는 훅이고 내용 생성은 LLM이라는 분리였다.</description><pubDate>Sun, 03 May 2026 00:00:00 GMT</pubDate><category>infra</category><category>ai</category><category>tooling</category><category>claude-code</category><category>engineering-discipline</category></item><item><title>AI가 짠 코드를 믿을 수 있게 만드는 하네스</title><link>https://leo-1178-blog.vercel.app/posts/ai-dev-harness-design/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/ai-dev-harness-design/</guid><description>AI로 빠르게 짜면 &apos;동작한다&apos;는 코드에 타입 오류·N+1·인가 누락이 섞여 들어온다. 사람이 매번 정독하지 않고도 막으려면, AI 출력을 검증 전 초안으로 다루고 검증을 시스템화해야 한다.</description><pubDate>Fri, 01 May 2026 00:00:00 GMT</pubDate><category>infra</category><category>ai</category><category>tooling</category><category>claude-code</category><category>engineering-discipline</category></item><item><title>파일 특성에 맞춘 CloudFront 캐시 전략</title><link>https://leo-1178-blog.vercel.app/posts/deploy-pipeline-4-cloudfront-cache/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/deploy-pipeline-4-cloudfront-cache/</guid><description>전체 무효화로 최대 15분 버전이 혼재하던 문제를, 해시 파일은 영구 캐시·index.html은 no-cache로 나눠 배포 즉시 전 세계에 반영되게 했다.</description><pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate><category>infra</category><category>ci-cd</category><category>aws</category><category>cloudfront</category><category>vite</category></item><item><title>ECS 배포 실패 시 자동 롤백, outcome과 conclusion의 함정</title><link>https://leo-1178-blog.vercel.app/posts/deploy-pipeline-3-ecs-rollback/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/deploy-pipeline-3-ecs-rollback/</guid><description>마이그레이션은 성공했는데 ECS 배포가 실패하는 경우, 직전 Task Definition으로 자동 복구한다. continue-on-error의 outcome/conclusion 차이가 관건이었다.</description><pubDate>Sun, 26 Apr 2026 00:00:00 GMT</pubDate><category>infra</category><category>ci-cd</category><category>github-actions</category><category>aws</category><category>ecs</category></item><item><title>워크플로우 통합과 배포 순서 강제</title><link>https://leo-1178-blog.vercel.app/posts/deploy-pipeline-2-unified-workflow/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/deploy-pipeline-2-unified-workflow/</guid><description>FE가 BE보다 먼저 배포되던 레이스 컨디션을, 3개 워크플로우를 하나로 합치고 job 의존성으로 순서를 강제해 막았다.</description><pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate><category>infra</category><category>ci-cd</category><category>github-actions</category><category>deployment</category></item><item><title>배포 파이프라인은 어디서 깨지는가</title><link>https://leo-1178-blog.vercel.app/posts/deploy-pipeline-1-problems/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/deploy-pipeline-1-problems/</guid><description>FE·BE·배치 3개 워크플로우가 서로를 모른 채 동시에 도는 구조. 거기서 생기는 7가지 문제를 위험도별로 분석했다.</description><pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate><category>infra</category><category>ci-cd</category><category>github-actions</category><category>aws</category><category>deployment</category></item><item><title>여러 증권사를 묶는 일임계약 시스템 설계</title><link>https://leo-1178-blog.vercel.app/posts/securities-contract-system-design/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/securities-contract-system-design/</guid><description>증권사 API에 종속된 일임계약을, 증권사마다 다른 상태와 규격과 업무 중단을 견디게 설계해야 했다. 증권사별 상태기계, DB 락 대신 Redis 동시성, 큐 기반 중단 대응으로 갈랐다.</description><pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate><category>backend</category><category>backend</category><category>system-design</category><category>nestjs</category><category>redis</category><category>queue</category><category>state-machine</category></item><item><title>전체 공개였던 S3 버킷 막기</title><link>https://leo-1178-blog.vercel.app/posts/s3-public-bucket-hardening/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/s3-public-bucket-hardening/</guid><description>Principal:* + s3:* 상태로 방치된 구버전 버킷을 발견하고 차단했다. Block Public Access, HTTPS-only 정책, IAM 최소권한 적용 과정.</description><pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate><category>infra</category><category>aws</category><category>s3</category><category>iam</category><category>security</category></item><item><title>쉬운 분리, 어려운 경계</title><link>https://leo-1178-blog.vercel.app/posts/service-subsystem-split/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/service-subsystem-split/</guid><description>874줄 NotificationsService를 facade + 서브서비스로 나눴다. 분리 자체보다 어려운 건 경계를 어디에 그을지, 그리고 나눈 뒤 그 경계를 어떻게 지킬지였다.</description><pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate><category>backend</category><category>nestjs</category><category>architecture</category><category>refactoring</category><category>service-split</category></item><item><title>React Query 훅을 Query / Mutation으로 분리하는 패턴</title><link>https://leo-1178-blog.vercel.app/posts/react-query-hook-split/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/react-query-hook-split/</guid><description>200~600줄짜리 단일 훅 파일을 Query/Mutation으로 쪼개는 구조. query key 소유권, 배럴 하위 호환, 순환 참조 방지까지 다룬다.</description><pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate><category>frontend</category><category>react-query</category><category>tanstack-query</category><category>hooks</category><category>refactoring</category><category>frontend</category></item><item><title>유령 의존성, 그리고 pnpm이 그걸 막는 법</title><link>https://leo-1178-blog.vercel.app/posts/pnpm-phantom-dependencies/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/pnpm-phantom-dependencies/</guid><description>package.json에 없는 패키지를 import했는데 동작한다. 이게 왜 위험한지, pnpm은 어떻게 구조로 막는지, 막다가 만나는 현실 타협(hoist-pattern)까지.</description><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><category>infra</category><category>pnpm</category><category>node_modules</category><category>phantom-dependencies</category><category>monorepo</category></item><item><title>상태를 갖지 않는 레이아웃 컴포넌트</title><link>https://leo-1178-blog.vercel.app/posts/compound-component-layout/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/compound-component-layout/</guid><description>PageHeader·SideDetailPanel·PageTableCard 세 컴포넌트를 통해 Controlled 패널, CSS transform 슬라이드, Compound Component 패턴, 디자인 토큰 분리를 정리한다.</description><pubDate>Tue, 07 Apr 2026 00:00:00 GMT</pubDate><category>frontend</category><category>react</category><category>compound-component</category><category>layout</category><category>tailwind</category><category>design-system</category></item><item><title>구독 결제에서 믿을 수 없는 것들</title><link>https://leo-1178-blog.vercel.app/posts/subscription-payment-trust/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/subscription-payment-trust/</guid><description>PortOne 빌링키로 구독 결제를 직접 붙이며 배운 건, 외부 PG도 webhook도 카드도 믿을 수 없다는 것. 멱등성·PAST_DUE 복구·상태 이력으로 그 불신을 설계에 녹였다.</description><pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate><category>backend</category><category>payment</category><category>portone</category><category>webhook</category><category>idempotency</category><category>subscription</category></item><item><title>로그인 한 번으로 끝나지 않는 인증 검사</title><link>https://leo-1178-blog.vercel.app/posts/auth-status-recheck-token-hardening/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/auth-status-recheck-token-hardening/</guid><description>인증을 전수 점검하다 같은 구멍이 반복해서 나왔다. 상태 검사는 로그인에만 있고 토큰은 그 검사보다 오래 산다. 정지된 계정이 refresh로 새 토큰을 받고, 검증 토큰은 평문으로 DB에 누웠다.</description><pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate><category>backend</category><category>backend</category><category>security</category><category>authentication</category><category>jwt</category><category>nestjs</category></item><item><title>SWING에 버그 리포트 기능을 직접 만든 이유</title><link>https://leo-1178-blog.vercel.app/posts/building-bug-report-feature/</link><guid isPermaLink="true">https://leo-1178-blog.vercel.app/posts/building-bug-report-feature/</guid><description>Linear·Jira 대신 SWING 안에 이슈 트래커를 직접 만든 결정. 컨텍스트·워크플로우·MTTR을 우리 것으로 두고 확장 가능하게 설계했더니, 팀장이 제품으로 키워보자고 제안했다.</description><pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate><category>backend</category><category>nestjs</category><category>postgresql</category><category>issue-tracker</category><category>qa</category><category>workflow</category></item></channel></rss>