기본 콘텐츠로 건너뛰기

타입 힌트를 도입했는데 디버깅이 빨라졌다: 파이썬에서 “조사 속도”를 올리는 고정점들

타입 힌트를 도입했는데 디버깅이 빨라졌다: 파이썬에서 “조사 속도”를 올리는 고정점들 그날은 로그가 있었다. 경고도 있었고, 에러도 있었다. 그런데 원인은 없었다. 재현이 안 되는 버그는 아니었다. 오히려 잘 재현됐다. 문제는 “어디서부터 잘못됐는지”를 못 찾는 거였다. 요청은 API에서 들어오고, DB에서 데이터를 꺼내고, 중간에 여러 함수가 거친 뒤에야 예외가 났다. 마지막 스택트레이스는 친절했지만, 팀이 궁금한 건 늘 그거다. 그래서 이 값은 언제부터 이상했지? 타입 힌트를 ‘정확성’의 이야기로만 들으면 도입이 늘 늦어진다. “동적 언어인데 굳이?” “mypy 설정부터 해야 하나?” 같은 얘기만 남는다. 나는 타입 힌트를 조사 속도 의 도구로 받아들이기 시작하면서부터 도입이 쉬워졌다. 코드가 덜 틀리게 만드는 도구가 아니라, 사고가 났을 때 조사를 빨리 끝내는 도구. 오늘은 그 관점에서 타입 힌트를 도입할 때 고정하면 좋은 것들을 적는다. 체크리스트로 늘어놓기보다, 한 번 터진 장애를 기준으로 어디를 고정하면 조사 시간이 줄어드는지의 이야기다. 본문에는 링크를 넣지 않고, 읽을거리는 맨 아래 참고자료로만 모았다. 타입은 ‘정답’이 아니라 조사 비용을 줄이는 장치다 Talk Python의 Typing Council 에피소드(참고자료)를 들으면, 타입은 단순히 문법 논쟁이 아니라 “파이썬 생태계가 어떻게 합의를 쌓아가는지”에 대한 이야기로 들린다. 그 합의는 성능보다도, 협업과 유지보수의 현실에서 나온다. 내가 실무에서 타입 힌트를 좋아하게 된 이유도 비슷하다. 타입이 있으면 코드가 100% 맞아지진 않는다. 대신 이런 질문이 빨라진다. 이 함수는 뭘 받아야 하지? 이 값은 None 이 될 수 있나? 여기서 문자열이 들어오는 게 맞나? 중요한 건 질문이 “리뷰 타이밍”에만 나오지 않는다는 거다. 장애 조사에서 이 질문들이 늦게 나오면, 팀은 로그를 뒤지고 DB를 뒤지고 결국 사람의 기억을 ...
최근 글

OpenRouter를 붙였는데 더 불안해졌다: 파이썬에서 LLM 호출 동선을 한 군데로 모으는 법

OpenRouter를 붙였는데 더 불안해졌다: 파이썬에서 LLM 호출 동선을 한 군데로 모으는 법 그날 장애는 모델이 아니라 “재시도”에서 시작했다. 응답이 늦어지자 타임아웃이 났고, 타임아웃이 나자 재시도가 돌았다. 재시도가 도니 요청이 더 밀렸다. 로그에는 같은 에러가 쌓였고, 비용 대시보드는 새로고침할 때마다 숫자가 튀었다. 누군가가 “모델만 바꾸면 되는 거 아니었어?”라고 물었는데, 그 질문이 제일 아팠다. OpenRouter 같은 라우터 API를 붙이는 이유는 보통 “여러 모델을 쉽게 바꿔 쓰려고”다. 그런데 실무에서 더 큰 이점은 따로 있다. 호출 동선을 한 군데로 강제할 수 있다는 것 이다. 키, 타임아웃, 리트라이, 로그, 비용. 이 다섯 가지가 프로젝트 여기저기에 흩어져 있으면, 모델이 아무리 좋아도 운영이 먼저 망가진다. 오늘은 그걸 한 파일로 모으는 이야기다. 본문에는 링크를 넣지 않고, 읽을거리는 맨 아래 참고자료로만 모았다. 라우터 API의 장점은 ‘모델 선택’이 아니라 ‘동선 통일’이다 Real Python의 OpenRouter 글(참고자료)을 보면, 접근은 단순하다. 라우터를 붙이면 여러 모델을 한 API로 호출할 수 있다. 여기까지는 누구나 한다. 하지만 팀이 진짜로 얻고 싶은 건 “모델 바꾸기”가 아니라 “규칙을 바꾸기”다. 타임아웃은 몇 초로 둘지 재시도는 몇 번까지 허용할지 실패하면 어떤 예외로 올릴지 로그는 어떤 태그로 남길지 비용은 어디에서 상한을 걸지 이 다섯 가지는 기능 개발보다 운영에 가깝다. 그리고 운영 규칙은 문서로 써놓으면 대개 깨진다. 코드로 박아야 한다. Armin Ronacher가 AI를 ‘테세우스의 배’로 비유한 글(참고자료)을 읽고 떠오른 것도 비슷한 감각이었다. 모델이 바뀌고, 프롬프트가 바뀌고, 정책이 바뀌어도 “이 시스템이 같은 배인가”를 유지하려면 결국 형태(구조)가 아니라 동선(운영)이 남아 있어야 한다. 그래서 우리는 라우...

호환성 작업을 안전하게 굴리는 법: scikit-learn array API adoption을 내 프로젝트로 번역하기

호환성 작업을 안전하게 굴리는 법: scikit-learn array API adoption을 내 프로젝트로 번역하기 실험 백엔드를 하나 붙였다. main에는 영향이 없을 줄 알았다. 그런데 어느 날부터 CI가 일주일에 한두 번씩, 같은 테스트가 다른 이유로 깨지기 시작했다. 더 골치 아픈 건, 로그/메트릭까지 흐려져서 평소에 잡히던 신호가 안 잡히는 날이 생겼다는 거다. 그때 깨달았다. 새 백엔드가 문제라기보다, 실험이 프로덕션 동선에 섞인 게 문제 였다. 본문에는 링크를 넣지 않고, 읽을거리는 맨 아래 참고자료로만 모았다. array API adoption을 내 프로젝트로 가져오면, 먼저 인터페이스가 흔들린다 scikit-learn의 array API adoption 업데이트 글을 읽으면(참고자료), “새 배열 표준을 지원한다”는 문장보다 더 크게 보이는 게 있다. 호환성 작업을 ‘한 번의 마이그레이션’으로 보지 않고, 사용자의 입력/출력 경로를 조금씩 넓혀가는 작업으로 다룬다는 점이다. 이걸 내 프로젝트로 번역하면 질문이 바뀐다. 우리 코드가 받는 입력은 진짜로 “numpy 배열”뿐인가? 내부에서 너무 빨리 타입을 고정해버리는 습관(예: 특정 라이브러리로의 캐스팅)은 없는가? 실패했을 때 어디서 결정할 건가? (조용히 기존 경로로 돌아갈지, 에러로 멈출지) 실무에서 array API 호환성은 보통 성능 문제가 아니라 경계 문제 로 터진다. 타입/백엔드 결정이 여기저기 흩어져 있으면, 어느 경로는 새 백엔드, 어느 경로는 기존 백엔드가 된다. 그리고 그 섞임은 테스트가 잡기 전에 프로덕션 신호부터 망친다. 그래서 첫 단계는 “지원한다”가 아니라 “결정하는 곳을 한 군데로 모은다”다. 호환 레이어(backend 선택 래퍼): ‘결정’은 한 군데에서만 한다 호환성 작업에서 제일 위험한 코드가 뭔지 아나. 새 백엔드 코드를 ‘조심스럽게’ 여러 파일에 조금씩 넣는 거다. 그 순간부터 main은 혼...

pip 업데이트를 ‘천천히’ 굴리는 법: dependency cooling과 검증 루프

pip 업데이트를 ‘천천히’ 굴리는 법: dependency cooling과 검증 루프 릴리즈 전날 밤, 그 메시지가 뜬다. “requirements.txt가 또 흔들렸어요.” 누가 뭘 잘못한 것도 아닌데, 팀 전체가 동시에 긴장한다. 지금 올리면 내일 배포가 미끄러질 수 있다. 안 올리면 이번 릴리즈에 알려진 버그/취약점을 끌고 간다. 올리면, 어디가 깨질지 모른다. 그날도 그랬다. CI가 원래보다 8분 늦게 돌아가고(러너가 밀렸다), 그 사이에 누군가가 “그럼 이참에 패키지 몇 개 더 올리죠”를 꺼냈다. 딱 그 순간부터, 이 PR은 의존성 업데이트가 아니라 ‘프로젝트 토론’이 된다. 토론은 나쁘지 않은데, 릴리즈 전날의 토론은 대개 사고로 끝난다. 의존성 업데이트가 무서운 이유는 ‘한 번의 변경’이 아니라, 변경이 들어오는 속도가 통제되지 않는 상태 로 쌓이기 때문이다. 평소에는 미루다가, 급한 날 한 번에 바꾸고, 그 날의 실패 경험이 다음 미루기를 정당화한다. 그래서 이 글의 핵심은 “업데이트를 더 자주 하자”가 아니다. 오히려 업데이트를 늦추는 운영 을 이야기한다. Seth Michael Larson이 소개한 pip v26.0의 상대적 dependency cooling을 출발점으로 삼되(참고자료), 기능 소개가 아니라 팀 운영 규칙으로 번역해보자. 목표는 단순하다. “릴리즈 전날 공포”를 “평일의 반복 작업”으로 내리는 것. 본문에는 링크를 넣지 않고, 읽을거리는 맨 아래 참고자료로만 모았다. dependency cooling: 업데이트를 막는 게 아니라 ‘속도’를 제한하는 것 dependency cooling을 한 문장으로 요약하면 이거다. 업데이트를 금지하지 말고, 변경의 속도를 감당 가능한 수준으로 늦춘다. 여기서 “늦춘다”는 건 방치하자는 말이 아니다. 리듬을 설계하자는 말이다. 실무에서 의존성 업데이트가 팀을 흔드는 순간은 대부분 비슷하다. 미루다가 한꺼번에 바꾸게 되...

JetBrains IDE에 AI 에이전트를 붙였더니, 제일 먼저 무너진 건 ‘코드’가 아니었다

JetBrains IDE에 AI 에이전트를 붙였더니, 제일 먼저 무너진 건 ‘코드’가 아니었다 IDE에 에이전트를 붙이는 건, 생각보다 감정적인 작업이다. 처음엔 다들 들뜬다. “이제 리뷰 시간 줄겠네.” “테스트도 자동으로 붙여주겠지.” “레거시도 금방 정리하겠네.” 그런데 실제로 팀에 도입해 보면, 처음으로 생기는 질문은 성능이 아니다. “얘가 우리 레포를 어디까지 볼 수 있는 거죠?” “이 변경은 내 로컬에서만 되는 거 아닌가요?” “왜 이 PR은 파일을 30개나 만졌지?” JetBrains IDE에서 ACP 레지스트리로 외부 에이전트를 붙일 수 있다는 소식은, 편의 기능이 늘었다는 정도로 보면 감각을 놓친다. 이건 ‘채팅창’이 하나 더 생긴 게 아니라, IDE가 프로젝트 전체를 읽고 바꾸는 손이 하나 더 생긴 사건이다. (참고자료) 나는 팀에 이런 걸 붙일 때, 모델 고르는 회의보다 먼저 운영 규칙을 적는다. 이게 없으면 “똑똑한 도구”가 아니라 “똑똑하게 사고 내는 도구”가 된다. 오늘 글은 기능 설명이 아니다. 내가 실제로 팀에 붙였던 가드레일(권한/재현/실험 분리)을 정리한 기록이다. 권한: 에이전트에게 “무엇을 보게/바꾸게” 할지부터 정한다 IDE 에이전트의 가장 큰 힘은 코드 생성이 아니다. 프로젝트 전체 접근 이다. 이게 편한 순간은 많다. 그런데 위험한 순간은 더 분명하다. 설정 파일을 “좋은 의도”로 바꿔버린다 배포 스크립트를 건드려서 CI가 깨진다 보안 키/내부 URL/운영 지침 같은 게 입력에 섞일 수 있다는 전제로 운영한다(외부 전송 여부와 별개로). 그래서 나는 도입 첫날에 ‘원칙 3개’를 만들기보다, 실제로 팀이 싸우는 질문부터 적는다. 어차피 첫 달에 계속 고치게 되니까, 문장은 짧고 구체적인 게 낫다. 어디까지 고쳐도 되나? 에이전트가 수정해도 되는 디렉터리와 “절대 건드리지 말아야 할” 디렉터리를 먼저 나눈다. 예를 들어 src/는 허용하되,...

__init__.py로 공개 API를 고정하는 법: 파이썬 코드리뷰에서 제일 먼저 합의할 한 가지

__init__.py로 공개 API를 고정하는 법: 파이썬 코드리뷰에서 제일 먼저 합의할 한 가지 패키지가 커지면, 실력 좋은 팀도 똑같은 데서 늦어진다. 기능이 복잡해져서가 아니라, import가 길어지고, import가 길어지면 코드리뷰가 길어진다. 리뷰 화면에서 from app.domain.user.handlers.signup import do_the_thing 같은 줄을 볼 때마다, 나는 기능 구현보다 “이 이름이 밖에서 보이는 이름이 되어도 괜찮나?”를 먼저 떠올린다. 밖에서 보이는 이름이 늘면, 유지보수는 늘어난다. 심지어 좋은 리팩터링도 “다 깨질까 봐” 못 하게 된다. 그래서 이 글은 여러 규칙을 이야기하지 않는다. 한 가지만 이야기한다. 패키지의 공개 API를 __init__.py 에서 고정하자. 그걸로 끝이다. 나머지는 팀 취향이다. 왜 하필 __init__.py인가: import 동선을 “짧고 일정하게” 만들기 __init__.py 는 파이썬 패키지의 얼굴이다. 어떤 팀은 여기를 비워두고, 어떤 팀은 여기서 이것저것 다 한다. 둘 다 가능하다. 문제는 “가능하다”와 “지속 가능하다”가 다르다는 점이다. 패키지가 작을 때는 누가 어디서 무엇을 import하든 큰 문제가 없다. 파일 하나 옮기면 IDE가 알아서 고쳐주기도 하고, 깨져도 금방 고친다. 그런데 패키지가 커지고, 기능이 늘고, 사람이 바뀌면 import 동선이 팀의 습관이 된다. 동네 팀에서 자주 보이는 패턴이 이거다. 처음엔 “그냥 여기에서 가져오면 되겠지”로 깊은 경로를 import한다. 다음엔 비슷한 코드를 다른 디렉터리에 또 만든다. 어느 날 파일을 정리하려고 옮기면, 생각보다 많은 곳이 깨진다. 깨진 걸 고치면서 “그냥 옮기지 말자”가 팀의 결론이 된다. 이때부터 코드리뷰는 “좋은 구조로 정리하는 시간”이 아니라 “깨질까 봐 확인하는 시간”이 된다. __init__.py 로 공개 API를 고정하면, ...

Django 보안 릴리스가 뜬 날의 운영자 플레이북: 파이썬 팀이 덜 망가지는 업데이트 처리

Django 보안 릴리스가 뜬 날의 운영자 플레이북: 파이썬 팀이 덜 망가지는 업데이트 처리 그날은 알림이 먼저 울렸다. “Django security releases issued…” 아침에 커피 내리는 손으로 슬랙을 열었다가, 나는 바로 닫았다. 슬랙을 열면 사람의 말이 먼저 들어온다. “지금 배포 가능해요?” “오늘 일정 빡센데요.” “이거 진짜 위험한 건가요?” 보안 릴리스 날에는 감정이 먼저 움직인다. 그리고 감정이 먼저 움직이면, 결정은 흔들린다. 나는 운영자 모드로 스스로를 잠깐 고정한다. 이건 루틴 글을 쓰겠다는 얘기가 아니다. 그날은 루틴이 아니라 사건처럼 굴러간다. 보안 릴리스는 늘 “오늘의 변경”이 되어야 하고, 오늘의 변경은 누군가가 책임을 가져야 한다. 이 글은 그 하루를 어떻게 지나가는지, 내가 실제로 하는 순서(그리고 일부러 하지 않는 것들)를 기록한 플레이북이다. FastAPI나 uv, 새로운 런타임 실험 같은 이야기들은 그날의 배경으로만 짧게 스친다. 오늘 할 일과 섞이지 않게, 일부러 바깥에 둔다. 제일 먼저 하는 건 ‘패닉 방지’가 아니라 범위 고정 보안 릴리스가 뜨면 팀은 두 종류로 갈린다. “보안이면 무조건 당장 올리자” “그거 올렸다가 또 깨지면 누가 책임져요” 이 둘은 싸우는 게 아니라 서로 다른 위험을 보고 있는 거다. 전자는 ‘미적용 위험’을, 후자는 ‘적용 위험’을 본다. 그래서 제일 먼저 해야 할 일은 “우리가 오늘 무엇을 바꾸는지”를 아주 작게 고정하는 것이다. 그날의 범위는 이렇게 정한다. 애플리케이션 로직은 손대지 않는다. 의존성은 보안 릴리스가 요구하는 프레임워크 패치만 올린다. 테스트는 ‘핵심 사용 경로’만 확실히 돈다. 모든 걸 다 돌리는 날이 아니라, 오늘 살아남는 날이다. 이런 결정을 멋있게 포장하면 안 된다. 현실은 이렇다. 그날도 CI 러너가 묘하게 밀려서, “전체 테스트 한 번 돌리고 마음 편해지자”를 눌렀다가 10분을...