2026년 8월 3일 | 개발 깨알 상식_Tips이미지 전처리 함수에 파일 객체 대신 경로를 넘겨야 해서 NamedTemporaryFile의 tmp.name을 전달했다. WSL과 Linux CI에서는 잘 읽혔는데 Windows runner에서만 PermissionError 또는 WinError 32가 났다. 파일 기록 뒤 flush()까지 호출했으니 처음에는 백신이나 느린 디스크를 의심했다. 실제 원인은 내용이 아니라 아직 열린 임시 파일 핸들과 Windows의 삭제 공유 조건이었다.Figure 1. 같은 임시 파일 경로라도 POSIX의 재열기와 Windows의 삭제 공유 조건은 다르다. flush가 아니라 핸들 수명과 삭제 시점을 바꿔야 한다.파일 내용이 아니라 열린 핸들이 충돌한다NamedTempor..
2026년 8월 1일 | 개발 깨알 상식_Tips영문 JSON만 돌던 작은 HTTP 서버에 {"message":"완료"}를 넣자 브라우저가 응답을 중간에서 끊었다. 서버 로그는 200이었고 JSON 직렬화도 성공했지만, 클라이언트에는 ERR_CONTENT_LENGTH_MISMATCH나 불완전한 응답이 남았다. 원인은 헤더를 만들 때 len(text)를 쓴 것이었다. Content-Length는 실제로 전송하는 바이트 수를 적는 헤더다.문자열에서 바로 길이를 세지 않고, 실제 전송할 UTF-8 바이트를 만든 뒤 Content-Length를 계산한다.Content-Length가 세는 단위는 octet이다HTTP 명세에서 Content-Length는 전송 내용의 크기를 10진수 octet 수로 나타낸다. oc..
시리즈: 바이브코딩 Tips #55이전: 54편 | 목록 | 다음 없음2026년 7월 30일 | 바이브코딩 Tipssync_tree 함수는 source와 destination을 모두 Path로 받는다. 코딩 에이전트가 이 함수를 정리하면서 인자 순서를 바꿨고, 호출부 하나는 예전 순서를 그대로 유지했다. 타입 오류도 없고 함수 호출 횟수를 보는 mock 테스트도 통과했다. 실제 실행에서만 보관 폴더의 파일이 작업 폴더 쪽으로 복사됐다. 나는 이 장면을 겪고 나서 같은 타입의 위치 인자가 두 개 이상인 리팩터링은 호출 성공보다 의미 매핑을 먼저 확인한다.같은 두 Path 값이라도 위치 인자 순서가 바뀌면 복사 방향이 반대로 갈린다. 호출 여부가 아니라 각 이름에 들어간 값을 확인해야 한다.오류 없이 반대 ..
2026년 7월 30일 | 개발 깨알 상식_Tipssubprocess.run(..., timeout=30)에서 TimeoutExpired가 났고 호출한 명령도 끝났는데, 그 명령이 띄운 worker는 계속 CPU를 쓰고 있었다. 다음 테스트가 같은 포트를 열지 못해 처음에는 종료 확인이 늦게 반영된 줄 알았다. 실제로는 Python이 기다리던 직접 자식과 그 자식이 다시 만든 프로세스를 같은 종료 범위로 봤던 것이 문제였다.Python 문서대로 run()의 timeout이 만료되면 내부 자식 프로세스는 kill되고 wait까지 끝난 뒤 예외가 다시 올라온다. 하지만 이 설명의 child process는 내가 시작한 Popen 대상이다. 그 대상이 shell, 테스트 러너, 빌드 도구처럼 손자 프로세스를 ..
2026년 7월 27일 | 개발 깨알 상식_Tips같은 app.db를 쓰는데 API 테스트에서는 고아 레코드가 거절되고, 배치 worker에서는 그대로 들어갔다. 두 경로의 CREATE TABLE 문은 같았고 자식 테이블에도 REFERENCES parent(id)가 있었다. 차이는 스키마가 아니라 새 연결에서 PRAGMA foreign_keys가 0이었다는 점이었다.SQLite 외래 키 정의가 테이블에 기록돼 있다는 사실과, 그 제약을 현재 쓰기에서 실제로 집행한다는 상태는 별개다. 나는 처음에 테스트 DB를 한 번 열며 옵션을 켰으니 파일 전체에 저장된 줄 알았다. 실제로는 연결마다 명시적으로 켜야 하는 실행 상태라서, 다른 worker나 connection pool이 만든 연결에는 자동으로 이어지지 ..
2026년 7월 21일 | 개발 깨알 상식_Tipsorders.csv는 터미널에서 4줄로 보였지만 Python의 csv.reader로 읽으면 헤더를 포함해 3개 레코드였다. 메모 컬럼 안의 줄바꿈이 물리 줄을 하나 늘렸기 때문이다. 파일이 깨진 것도 아니고 파서가 한 행을 버린 것도 아니었다. 내가 서로 다른 단위를 같은 ‘행 수’라고 부른 것이 문제였다.Figure 1. 같은 입력도 줄바꿈 문자를 세면 4줄이고, CSV 문법으로 읽으면 헤더 1개와 데이터 2개다.물리 줄과 레코드가 갈리는 지점CSV에서 레코드는 무조건 줄바꿈 하나로 끝나지 않는다. 필드가 따옴표로 감싸져 있으면 그 안에 쉼표와 줄바꿈이 들어갈 수 있다. 배송 메모, 상담 기록, 사용자 서술, 주소처럼 사람이 여러 줄을 입력하는 컬럼에서..
2026년 7월 17일 | 개발 깨알 상식_Tipsjoined.gz를 풀었더니 Python의 gzip 경로에서는 두 레코드가 나왔고, zlib.decompress(..., wbits=31) 경로에서는 첫 레코드만 나왔다. 예외도 없었고 종료 상태도 정상이었다. 파일 손상이 아니라 gzip member 경계를 하나만 읽은 것이 원인이었다.gzip member는 헤더·압축 데이터·트레일러를 가진 독립 압축 단위다. RFC 1952의 gzip 파일은 이런 member가 하나 이상 차례로 붙는 구조를 허용한다. 날짜별 로그 조각이나 분산 작업 산출물을 바이트 단위로 이어 붙이면, 겉으로는 파일 하나지만 안에는 여러 member가 남을 수 있다.Figure 1. 같은 joined.gz에서 왼쪽 경로는 두 memb..
2026년 7월 16일 | 개발 깨알 상식_TipsmacOS에서 받은 보고서_최종.txt를 Linux 배치가 찾지 못했는데, 화면에 찍힌 이름은 분명 같았다. 원인은 NFC(Normalization Form C, 정규화 형식 C)와 NFD(Normalization Form D, 정규화 형식 D)가 섞인 파일명이었다. NFC는 한글 음절을 결합된 형태로 두고, NFD는 같은 글자를 초성·중성·종성 코드포인트로 분해한다. 눈으로 읽는 문자열은 같아도 프로그램이 비교하는 코드포인트 배열은 달랐다.이 문제는 파일을 어느 운영체제에서 만들었는지만 보고 단정하기 어렵다. 압축, 동기화 클라이언트, 네트워크 공유, Git, 업로드 폼처럼 이름이 여러 경계를 지나면서 표현이 달라질 수 있다. 나는 이제 “맥에서 왔으니..
2026년 7월 15일 | 개발 깨알 상식_TipsPython에서 API 응답의 만료 시각과 로컬에서 만든 현재 시각을 비교하다가 TypeError: can't compare offset-naive and offset-aware datetimes를 만났다. 문자열 모양은 둘 다 날짜와 시간이었지만, 한쪽에는 UTC 오프셋이 있고 다른 쪽에는 시간대 정보가 없었다. 값의 포맷보다 먼저 datetime이 어느 기준의 시각을 표현하는지가 갈린 상태였다.이 오류를 급히 없애려고 tzinfo를 지우거나 아무 값에 UTC를 붙이면 코드는 다시 돌 수 있다. 문제는 그 순간 비교 대상이 같은 시각이 아닐 수 있다는 점이다. 나는 예외를 타입 문제로만 보지 않고, 시스템 안에서 wall clock과 실제 시점을 섞었다..
2026년 7월 13일 | 개발 깨알 상식_Tips설정 파일에는 "timeout": 5가 분명히 있었는데, 프로그램이 읽은 값은 30초였다. 파일 끝쪽에 같은 키가 한 번 더 들어간 것이 원인이었다. JSON 문법은 정상이라 파서는 예외를 내지 않았고, Python의 json.loads는 뒤에 나온 값을 남겼다. 나는 처음에 환경변수나 기본값 병합 순서를 의심했지만, 실제 문제는 같은 객체 안의 중복 키가 파싱 단계에서 조용히 사라진 것이었다.Figure 1. 일반 파싱은 마지막 값만 남기지만, 키-값 쌍 단계에서는 중복을 잡을 수 있다.중복 키는 보기보다 위험하다. timeout처럼 뒤 값이 이기는 설정이면 실행이 느려지는 정도로 끝날 수 있지만, 권한 플래그나 배포 대상, 저장 경로가 겹치면 사람이 ..
시리즈: GraphRAG 구축기 #23이전: 22편 | 목록 | 다음: 24편2026년 6월 30일 | 개인 프로젝트 · GraphRAGGraphRAG profile registry에서 가장 애매한 순간은 후보를 바로 버리기에도, 바로 살리기에도 근거가 조금 모자랄 때다. 지난 회차에서는 exit policy가 어떤 조건을 만족했는지 summary에서 보이게 만들었다. 다만 거기서 한 칸 더 남아 있었다. `needs-manual-confirmation`이라는 상태는 보이는데, 실제로 사람이 다음에 어떤 버튼을 눌러야 하는지는 여전히 JSON을 열어 해석해야 했다.조건 충족 상태를 registry 변경이 아니라, 먼저 검토 가능한 action preview로 접는 흐름.이번에 바꾼 지점이번 회차에서는 e..
2026년 6월 25일 | 개발 깨알 상식_TipsAPI 클라이언트에서 timeout 하나만 두면 실패 이유가 자주 뭉개진다. 호출이 늦었다는 사실은 알겠는데, 실제로는 DNS나 TCP 연결에서 막힌 건지, 서버가 처리를 오래 한 건지, 본문을 읽는 중에 멈춘 건지 로그만 봐서는 갈라지지 않는다. 나도 작은 수집기를 만들 때 처음에는 timeout=30 하나로 끝냈다가, 같은 에러를 네트워크 문제와 서버 지연 문제로 번갈아 오해한 적이 있다.연결 준비 시간과 서버 응답 시간을 나눠 적으면 같은 timeout도 다음 행동이 달라진다.timeout 하나가 숨기는 것가장 흔한 실수는 timeout을 “전체 호출 제한 시간”처럼만 생각하는 것이다. 물론 어떤 라이브러리는 전체 예산을 지원하지만, 실무에서 내가 ..
2026년 6월 22일 | 개발 공부Vargha-Delaney A12는 두 독립 표본에서 값을 하나씩 뽑았을 때 첫 번째 표본이 두 번째 표본보다 클 확률을 0부터 1 사이로 적는 효과 크기다. 이름은 조금 낯설지만 질문은 단순하다. 평균이 몇 점 차이냐보다 먼저 무작위로 한 쌍을 맞붙이면 A가 B를 얼마나 자주 이기나를 묻는다.나는 이 값을 Brunner-Munzel 검정이나 Mann-Whitney U 검정 옆에 붙일 보조 숫자로 본다. 검정은 “우연이라고 보기 어려운가”를 묻고, A12는 “차이가 어느 정도 방향으로 기울었나”를 묻는다. 두 질문을 섞어 쓰면 p-value가 효과 크기처럼 읽히고, 효과 크기가 유의성 판정처럼 읽힌다. 그래서 표를 만들 때 둘을 일부러 다른 칸에 둔다.A12는 평균 차..
2026년 6월 15일 | 개발 깨알 상식_Tipspip install에 --dry-run --report를 붙이면, 패키지를 실제로 깔기 전에 resolver가 고른 설치 계획을 JSON으로 남길 수 있다. 나는 오래된 실험 환경이나 공유 서버에서 작은 라이브러리 하나를 올리려다가 생각보다 많은 의존성이 같이 움직이는 장면을 몇 번 봤다. 설치가 끝난 뒤에야 “왜 이 버전이 바뀌었지”를 찾으면 이미 상태가 흐려진다. 그래서 요즘은 애매한 설치일수록 먼저 무엇이 바뀔 예정인지 파일로 남기는 단계를 하나 둔다.Figure 1. 실제 설치 전에 resolver 결과를 plan.json으로 남겨 변경 범위를 먼저 읽는 흐름.설치 명령을 계획 명령으로 바꾸기가장 단순한 형태는 아래처럼 쓴다. 핵심은 instal..
2026년 6월 13일 | 개발 깨알 상식_Tips모듈을 실제로 import하기 전에 문법 오류를 먼저 걸러내는 사전 점검 흐름.Python 스크립트 묶음을 한 번에 고칠 때 제일 피곤한 실패는, 한참 뒤 실행 경로에 들어가서야 문법 오류가 터지는 경우다. 테스트가 잘 짜여 있으면 결국 잡히지만, 작은 CLI나 배치 스크립트는 모든 파일을 import하지 않을 때가 많다. 나는 이런 폴더에서는 무거운 테스트를 돌리기 전에 python -m compileall -q를 한 번 먼저 넣는다. 코드를 실행하지는 않지만, 파서가 읽을 수 없는 파일을 빠르게 걷어내는 얇은 체에 가깝다.실행 검증이 아니라 파싱 검증으로 보기compileall은 디렉터리 아래의 .py 파일을 바이트코드로 컴파일한다. 여기서 중요한 점..