개발 블로그
전은성 Dev
글 78편 · 최근 30편
-
초당 85줄 로그가 그날의 매도 체결 기록을 지웠다
문제: 로그가 너무 많아서 로그가 사라졌다 호출 빈도 — 자동매매 전략의 on_candle 함수는 종목마다, 매 틱마다 호출 문제 코드 — MDD(최대 낙폭) 차단이 걸릴 때마다 logger.info로 무조건 기록 실질 — “차단 중”이라는 상태 하나를 매 틱마다 재알림 2026년...
-
ddl-auto: update의 빈틈을 보완 DDL 자동화로 메우기
Flyway를 걷어낸 뒤 남은 숙제 이전 글: Flyway를 도입하고 3주 만에 걷어낸 이야기 걷어낸 뒤 스키마 관리 방식 JPA ddl-auto 사람이 손으로 적용하는 SQL 파일 묶음(db/manual/*.sql) 구멍 두 가지 stg 환경은 ddl-auto: validate ...
-
종목마다 따로 놀던 최고점이 멀쩡한 계좌를 손절 모드로 밀어넣었다
문제: 손실이 없는데 손절 모드였다 equity_peak의 정의: 계좌 자산이 지금까지 찍은 최고점 기록 여기서 얼마나 떨어졌는지(MDD, Max Drawdown) 계산 일정 이상 하락 시 신규 매수 차단 그런데 실제 저장 상태는 달랐다. 저장 단위: 종목별 전략 인스턴스마다 따로...
-
체결 47건을 역산해서야 드러난 4.1배짜리 수수료 오차
시작은 작은 지적이었다 최초 지적: 손익 화면에서 수수료와 세금이 하나의 값으로 합산 표시 첫 판단: “두 값을 나눠 보여주면 끝” — 넘어갈 뻔함 확인 결과: 합산 구조만의 문제가 아님. 요율 자체가 틀려 있었다. 가정 대신 실측한 내역 데이터: 실제 체결 47건 출처: KIS ...
-
설정 버그를 고쳤더니, 그다음엔 'ML을 켜는 게 맞나'라는 질문이 남았다
발단: 단순한 설정 누락 docker-compose.yml의 environment 항목 — 어떤 환경변수를 컨테이너에 넘길지 명시적으로 나열하는 방식 누락된 항목 — HYBRID_ENABLE_ML 결과 — .env 파일에 값을 뭐라고 적어두든 컨테이너까지 미전달 그래서 enable...
-
죽은 파일이 살아있는 파일보다 최신이었다
감사의 목적은 번들 축소가 아니었다 흔한 동기(번들 크기, 파일 수)가 아님 목적: “죽은 코드를 살아있다고 착각하고 거기에 작업하는 사고” 방지 계기: 그 사고가 이미 여러 번 발생 조사 결과: 2주 동안 같은 패턴 5회 반복 원인: git log가 거짓말을 한다 가장 인상적인 ...
-
같은 버그를 두 번 고친 날, V1/V2 이중 스택을 정리하기로 했다
계기: 같은 버그를 양쪽에서 따로 고쳤다 한 화면에 V1·V2 두 벌의 구현 공존 신규 작성 화면 — V2 사용 월 업데이트 화면 — V1 사용 카테고리 상태 desync 버그 발생 시의 대응: V1 쪽 수정 → 며칠 뒤 V2 쪽에서 재수정 같은 원인·같은 증상·같은 수정의 2회 ...
-
매수 신호는 나오는데 체결이 0건 — 감으로 안 고치고 필터를 채점했다
문제: 신호는 나오는데 거래가 없다 기간: 2026년 7월 20~21일 라이브 로그 전략 상태: BUY 신호 계속 발생 실제 매수 체결: 하루종일 0건 신호 자체는 살아있는데 그게 전부 걸러지고 있다는 뜻이었다. 감으로 고치지 않고, 필터를 채점했다 방식: 짐작으로 필터를 하나씩 ...
-
엑셀 라이브러리가 두 개였다 — xlsx에서 exceljs로 통일하기
문제 한 프로젝트 안에서 엑셀을 내보내는 경로가 두 개였고, 각각 다른 라이브러리를 사용 중. 경로 라이브러리 헤더 스타일 AG Grid export xlsx 불가 리포트 export exceljs 적용됨 xlsx(SheetJS) 커뮤니티 버전의 제약 — 셀 스타일 미지원 결과 —...
-
AI 코드리뷰 봇 2종을 파이프라인에 넣고, 봇이 못 잡는 걸 배웠다
배경 대상: 프론트엔드 저장소 1곳 구성: AI 코드리뷰 두 겹 PR에 자동으로 달리는 Gemini 리뷰 봇 작업 중간에 명시적으로 태우는 code-review 패스 목표: “지적을 받는 것”이 아니라 지적사항을 실제 커밋으로 환류시키기 기간: 약 두 달 두 달 운영 결과 — 봇이...
-
소프트 삭제 전환과 @Filter가 새는 곳
엔티티 14개를 하드 삭제에서 소프트 삭제로 배경 — 보고서에서 참조하는 데이터가 삭제되면 과거 보고서가 파손 작업 — 삭제를 is_delete 플래그로 전환 (PR #792) 대상 — 엔티티 14개 개념은 단순한데 실제로 걸린 곳은 둘 — 유니크 제약, @Filter 미적용 경로...
-
동기 30초 타임아웃을 webhook 비동기로
30초로는 부족했다 기능: 문서 OCR 파싱 기존 구조: 내부 OCR 서비스에 HTTP 요청 → 결과 수신 → 응답 (동기) 30초 타임아웃에 계속 걸린 원인 두 가지: PaddleOCR 콜드스타트 — 모델 로딩 지연. 유휴 후 첫 요청이 특히 느림 페이지당 처리 지연 — 문서 길...
-
PDF 다운로드가 7초 걸렸는데, 범인은 한글 폰트였다
문제 대상 — 제품 배출량을 산정하는 B2B 웹앱의 결과 리포트 PDF 다운로드 소요 시간 — 버튼 클릭 후 약 7초 그동안 화면 — 로딩만 회전, 아무 변화 없음 사용자 반응 — “먹통이 됐나” 하고 재클릭하기 충분한 시간 구현 스택 — @react-pdf/renderer 4.x...
-
AI 에이전트 72개로 메모리 누수를 감사하고, 그 결과를 다시 검증했다
문제 오래 켜두는 SPA의 메모리 누수 — 재현부터 어려움 조건: 특정 화면 열고 닫기를 수십 번 반복해야 드러남 그 시점엔 이미 어느 컴포넌트 탓인지 특정하기 어려움 수백 개 컴포넌트를 사람이 하나씩 훑는 건 비현실적 그래서 접근 전환 병렬 에이전트 72개를 띄워 코드베이스를 영...
-
RTK Query 워터폴을 평탄화하고, 캐시를 켜자 stale이 드러났다
문제 대시보드 탭 하나를 여는 데 요청이 직렬로 연결. summary 조회 → 첫 제품 자동 선택 → 해당 제품의 LCA 선택 → 상세 데이터 호출 각 단계가 앞 단계의 응답을 기다린 뒤에야 시작 — 전형적인 워터폴 추가로 이 API 그룹은 keepUnusedDataFor: 0으로...
-
N+1 제거 4종 세트 — fetch join이 답이 아닐 때
N+1은 하나의 문제가 아니다 기간: 한 달 반 작업: 대시보드 조회 성능 개선 N+1 조우 횟수: 여러 번, 매번 처방이 상이 “N+1이면 fetch join” 반사가 통하지 않는 경우: 절반 이상 (a) fetch join을 못 쓸 때 — @BatchSize 첫 케이스 — f6...
-
뷰를 물질화 테이블로 — 읽기 비용을 쓰기 시점으로 옮기기
읽을 때마다 5개 CTE를 풀스캔하고 있었다 대상 — 대시보드 조회에 쓰이는 엔티티 하나 매핑 — @Subselect로 매핑된 뷰 구성 — CTE 5개를 엮은 집계 뷰 MySQL의 동작 — 이 뷰를 실체화해두지 않음. 조회할 때마다 전체 재계산 즉 화면을 열 때마다 풀스캔 판단 기...
-
AG Grid의 autoHeight는 행 가상화를 끈다
문제 입출력 데이터를 입력하는 그리드에서 행이 수백 개로 늘어나자 셀 하나를 편집할 때마다 눈에 띄게 버벅였다. 타이핑 속도를 화면이 못 따라오는 정도였다. 원인 1: 편집할 때마다 columnDefs가 통째로 재생성됐다 columnDefs는 useMemo로 감싸져 있었음 의존성 ...
-
readOnly 트랜잭션에서 write를 잡아내는 테스트 가드레일
조회 API가 매번 write 트랜잭션을 만들고 있었다 발단 — 대시보드 조회 API 응답 지연 확인 결과(f6fd21ef) — eco-view의 배출원 조회 경로가 lcaStepService.updateStepOnly(write)를 부수효과로 호출 즉 대시보드를 열 때마다 wri...
-
AI가 쓴 커밋 메시지가 거짓말을 했다
배경: 커밋의 절반이 AI 협업이다 대상: 같은 백엔드를 공유하는 쌍둥이 Next.js 프론트엔드 기간: 한 달 남짓 집중 작업 커밋 접두사: 압도적으로 fix 국면: 통합테스트 + QA 대응 그 기간의 AI 협업 표기 집계 관리자 포털 사용자 포털 전체 커밋 205 193 C...
-
Flyway를 도입하고 3주 만에 걷어낸 이야기
도입과 철수 사이 22일 도입 — 2026년 5월 13일, b832f20d 철수 — 6월 4일, 9e3a7aaa 기간 — 22일 그 사이 FlywayConfig — 221줄까지 성장 후 통째로 삭제 성공담이 아니라 철수 회고다. 왜 도입했나 프로젝트 구조: 빌드 — Gradle 멀...
-
JWT를 걷어내고 Spring Session으로 — 그 뒤에 온 것들
전환 자체는 하루였다 커밋 a1837d49(PR #557) 내용: JWT 기반 인증 → Redis 기반 Spring Session 삭제: JwtFilter, JwtProvider 등 클래스 6개, 374줄 커밋 하나로 종료 정작 내용이 있는 구간: 그다음 3주 이 글의 주제: 세션...
-
kubectl exec은 왜 멈추는가 — 셸 스크립트에 타임아웃과 재시도 넣기
문제: 스크립트가 멈춘다 Fabric 네트워크 기동 = bash 스크립트가 kubectl을 순서대로 호출 증상 — 스크립트가 간헐적으로 멈춤. 에러가 아니라 그냥 정지 환경 — 폐쇄망 + kind 다중 클러스터. 네트워크 계층이 순탄하지 않음 원인 구조: kubectl은 기본적으로...
-
TanStack Query에서 로그아웃은 토큰만 지우는 게 아니다
문제: 로그아웃했는데 이전 사용자 데이터가 보인다 구성: 같은 백엔드를 쓰는 쌍둥이 Next.js 프론트엔드 — 관리자 포털, 사용자 포털 인증 상태: Zustand 서버 상태: TanStack Query 이 분리 자체는 흔하고 합리적인 구성 문제 지점: 로그아웃 경로 useAut...
-
비동기 API가 무조건 200을 반환하고 있었다
문제: “삭제 성공”이라는 거짓말 블록체인 네트워크 생성에 걸리는 시간 — 몇 분 수행 작업 — Kubernetes에 리소스 등록, pod 기동 대기, 채널 생성, peer 조인 그래서 채택한 방식 — 생성도 삭제도 비동기 처리 컨트롤러의 역할 — 작업 등록만 하고 즉시 응답 문제...
-
TOCTOU 한 건을 고치는 대신 버그 클래스를 없앴다
문제: SFTP 업로드가 동시에 들어오면 터졌다 프로젝트: 웹 버튼 클릭 → Hyperledger Fabric·Besu 네트워크를 Kubernetes 클러스터에 배포하는 플랫폼 인프라 계층: JSch로 원격 서버에 SSH/SFTP 접속 → bash 스크립트 전송 후 실행 해당 업로...
-
localStorage에 남은 isAuthenticated: true가 로그인을 막았다
문제: 로그인 페이지가 “로딩 중…“에서 안 넘어간다 사용자 포털 버그 리포트 한동안 안 쓰다가 다시 진입하면 로그인 화면이 “로딩 중…“만 표시한 채 정지 폼 미표시 → 로그인 경로 없음 localStorage 비우면 정상 복귀 localStorage를 비워야 풀린다 → 원인은 ...
-
오픈소스 오퍼레이터 버그를 CRD 스키마 패치로 우회하기
문제: 체인코드 상태가 FAILED에서 안 나온다 구조: 체인코드 배포 시 Kubernetes에 FabricChaincode 커스텀 리소스 생성 hlf-operator가 이 리소스를 보고 실제 체인코드 pod 기동 증상: 배포는 실제로 성공 kubectl get fabricchai...
-
invalidateQueries를 refetchQueries로 바꿨더니
문제: mutation 후 목록이 즉시 안 바뀐다 관리자 포털과 사용자 포털 양쪽에서 반복된 QA 피드백. 증상 — 항목을 삭제하거나 생성했는데 목록이 그대로 반영 시점 — 탭을 옮겼다 오거나 새로고침한 뒤 전제 — mutation 성공 후 invalidateQueries 호출 중...
-
인증 필터가 매 요청 DB를 네 번 때리고 있었다
문제: TPS가 안 나온다 부하 테스트에서 목표 TPS 미달 애플리케이션 로직 자체는 가벼움 조사 방향: 비즈니스 코드가 아니라 모든 요청이 반드시 지나가는 경로 구성: Spring Security + JWT 필터 체인 추적 결과: DB 접근이 겹겹이 누적 지점 하는 일 JwtFi...