기본 콘텐츠로 건너뛰기

큰 파이썬 레포에서 첫 PR이 제일 무섭다: CPython이 보여준 ‘지도’ 만들기

큰 파이썬 레포에서 첫 PR이 무서운 이유: 코드를 고치기 전에 ‘길’부터 봐야 한다 처음 큰 레포를 열었을 때, 나는 코드가 아니라 공기가 무거웠다. 파일이 많고, 디렉터리가 많고, 규칙이 많아 보였다. 어디서부터 손대면 되는지 감이 없었다. 그래도 PR 하나는 해야 하니까, 제일 작은 걸 골랐다. 오타 하나, 로그 한 줄, 그 정도. 그런데 이상하게도 그 작은 변경이 나를 하루 종일 잡아먹었다. 내가 바꾼 건 한 줄인데, 테스트는 수백 개가 돌았고, 중간중간 실패가 났고, 다시 돌리면 또 다른 곳이 깨졌다. 그때부터는 코드가 아니라 ‘길’을 찾는 시간이 됐다. 어디서 실행해야 하는지, 어디서 깨진 건지, 내가 바꾼 게 도대체 어디에 영향을 주는지. 오늘 글은 설치 팁이 아니다. uv가 좋다는 얘기로 시작하지 않는다. 큰 파이썬 코드베이스를 처음 건드릴 때 제일 먼저 필요한 건 도구가 아니라, “지금 내가 어디에 서 있는지”를 알게 해주는 지도다. 본문에는 링크를 넣지 않고, 읽을거리는 맨 아래 참고자료로만 모았다. 그날 10분: 테스트가 터졌는데, 어디가 문제인지도 몰랐다 첫 PR을 올리기 전에 로컬에서 테스트를 돌렸다. ‘전체 테스트’라는 말이 있다면, 나는 그걸 그대로 믿는 편이었다. 그런데 큰 레포에선 그 믿음이 바로 부메랑이 된다. 전체 테스트는 오래 걸리고, 오래 걸리는 동안 사람은 집중을 잃는다. 집중을 잃으면 로그를 대충 읽고, 대충 읽으면 더 대충 고친다. 그날도 그랬다. 실패가 뜨길래 해당 테스트 파일을 열었는데, 테스트가 하는 일이 내가 바꾼 코드랑 전혀 상관없어 보였다. 그래서 다른 테스트로 건너갔다. 또 상관없어 보였다. “내가 뭘 건드린 거지?”라는 질문이 한 시간 뒤에야 제대로 떠올랐다. 그때부터 나는 전체 테스트를 포기했다. 포기라는 말이 좀 거칠지만, 큰 레포에서 포기는 종종 ‘전략’이 된다. 대신 최소한의 확인을 하나 정했다. 이 한 줄을 돌리고 나서야 다음으로 넘어가기로. p...
최근 글

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

타입 힌트를 도입했는데 디버깅이 빨라졌다: 파이썬에서 “조사 속도”를 올리는 고정점들 그날은 로그가 있었다. 경고도 있었고, 에러도 있었다. 그런데 원인은 없었다. 재현이 안 되는 버그는 아니었다. 오히려 잘 재현됐다. 문제는 “어디서부터 잘못됐는지”를 못 찾는 거였다. 요청은 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/는 허용하되,...