EVE 온라인의 240만 줄 Python 2 — 미룬 이행은 부채인가 판단인가

2003년에 출시된 MMO 가 2026년에야 Python 3 이행을 시작했다. 16년 동안 같은 런타임에 머문 선택은 방치된 기술 부채인가, 아니면 비용과 위험을 정확히 계산한 경영 판단인가.

도입

2026년 8월 25일, EVE 온라인 개발팀이 「The Move to Python 3 Begins!」라는 제목의 개발 블로그를 냈다. 2003년에 서비스를 시작해 23년째 운영 중인 이 우주 MMO 의 서버 코드가 드디어 Python 3 으로 이동을 시작한다는 공지다. 발표문이 스스로 내건 성공의 정의는 이상하리만치 소박하다. 이 이행의 성공은 “완전히 눈치채지 못하게” 끝나는 것이며, 예외가 있다면 “가끔 무언가가 조금 더 매끄럽게 돌아가는 순간” 정도라고 적었다.

Python 2.7 의 공식 지원은 2020년에 끝났다. 발표 시점에서 6년이 넘게 지났다. 그동안 EVE 는 2010년에 올린 Stackless Python 2.7 위에서 런타임을 단 한 번도 바꾸지 않고 돌아갔다. 발표문의 표현을 그대로 옮기면, 16년 동안 같은 버전에 머물렀다는 사실은 그 선택이 얼마나 잘 작동했는지를 말해 준다. 그리고 이어지는 문장이 이 글 전체의 열쇠다. “충분히 안정적이었기 때문에, 크고 잠재적으로 위험한 이행은 지금까지 스스로를 정당화하지 못했다.”

Hacker News 에 올라온 이 글은 406점과 227개의 댓글을 모았다. 첫 반응은 조롱이 아니라 놀람이었다. “날짜를 확인해야 했다”고 적은 이용자가 있었고 (@jamesnorden), “2026년에 이런 헤드라인을 읽는 것이 좀 웃긴다”는 반응도 나왔다 (@q8zd3). 그러나 토론이 깊어지면서 질문의 방향이 바뀌었다. 16년은 실패한 방치인가, 아니면 매 순간 옳았던 계산의 누적인가. 다행히 이번 발표문은 감상 대신 숫자를 내놓았다. 숫자를 따라가면 답의 윤곽이 잡힌다.

컴파일러를 유일한 진실로 삼는다: 240만 줄의 실제 좌표

발표문이 공개한 코드베이스의 크기는 Python 240만 줄, 파일 약 2만 개다. 단순히 나누면 파일당 평균 120줄 남짓이다. 이 가운데 상당 부분은 Python 2.7 보다도 앞선 시대의 코드로, 2.3 과 2.5 시절의 관행으로 쓰여 Python 3 이 아예 파싱조차 거부하는 문법을 포함한다.

여기서 개발팀이 택한 방법론이 이 발표문의 가장 실무적인 부분이다. 그들은 이행 거리를 추정하지 않고 측정했다. 약 2만 개의 파일을 전부 실제 Python 2.7 인터프리터와 실제 Python 3 인터프리터로 각각 컴파일했다. 이유는 명확하다. 코드가 파싱되는지에 대해서는 컴파일러가 유일한 진실(ground truth)이기 때문이다. 정적 분석기의 추정이나 개발자의 감각이 아니라, 실행 가능한 판정이 매일 관측 가능한 숫자를 만든다.

첫 스캔 결과는 발표문의 표현대로 “기분 좋은 놀람”이었다. 파일의 95.9퍼센트가 이미 양쪽 버전에서 컴파일됐다. Python 3 이 거부하는 문법을 쓴 이른바 차단 라인은 240만 줄 가운데 약 3,300줄이었다. 비율로는 0.14퍼센트다. 내역도 구체적이다. 구식 print 문 약 1,500건, 123L 같은 long 리터럴 약 800건, EVE 출시보다도 먼저 폐기된 문법으로 쓰인 예외 절 약 600건, 그리고 “같지 않음”을 뜻하는 <> 가 50건이다. 마지막 항목에 대해 발표문은 현업 Python 개발자 다수가 본 적조차 없을 만큼 오래된 표기라고 덧붙였고, HN 에서도 실제로 그 반응이 나왔다 (@orsenthil).

산술적으로 보면 파일의 4.1퍼센트, 대략 800개 남짓한 파일에 3,300줄이 흩어져 있다는 뜻이다. 발표문이 “산인 줄 알았더니 크지만 매우 측정 가능한 언덕이었다”고 쓴 근거가 이것이다. 1단계는 이 언덕을 깎는 작업이며, 도구는 Python-Future 다. Python 자신이 이런 이행을 위해 배포했던 코드 재작성 기계인 2to3 위에 세워진 도구로, 낡은 패턴 하나를 현대적 형태로 바꾸는 “픽서(fixer)“들을 적용한다. 핵심은 결과물이 Python 2.7 과 Python 3 양쪽에서 모두 받아들여지는 형태라는 점이다. 즉 1단계 동안 서비스는 계속 2.7 위에서 돌아간다.

제약 조건도 명시돼 있다. 모든 캐릭터, 모든 스킬 포인트, 모든 격납고의 모든 자산, 모든 지갑의 모든 ISK 가 Python 2 코드로 쓰였고, 그 전부가 Python 3 에서 정확히 같은 값으로 읽혀야 한다. 게다가 본섭 Tranquility 는 하루 24시간 가운데 23.75시간을 계속 살아 있어야 한다. 7월에 테스트 서버 Singularity 에서 1단계 변경분을 플레이어들과 함께 검증했고, 발표 당일 그 변경분이 본섭에 배포됐다.

성공 사례도 이미 있다. 같은 개발사의 EVE Frontier 는 자사 Carbon 엔진을 최신 Python 3 위에서 이미 돌리고 있다. 발표문에 따르면 Frontier 의 이행은 Python 마이너 버전 12개, 언어 진화 16년치를 한 번에 건너뛴 프로젝트였다. 다만 발표문은 그 성공을 그대로 옮겨 붙이지 않는다. Tranquility 에는 23년치 코드뿐 아니라 23년치 실제 플레이어 데이터가 쌓여 있고, 그 무게가 Frontier 와 결정적으로 다르다고 스스로 선을 그었다.

미룬 것은 게으름이 아니라 가격표였다

이행을 미룬 16년을 이해하려면 EVE 가 그냥 Python 이 아니라 Stackless Python 위에 세워졌다는 사실부터 봐야 한다. 발표문의 설명대로 Stackless 의 경량 “태스크릿(tasklet)“은 서버 노드 하나가 수천 명의 파일럿을 동시에 굴릴 수 있게 해 준 물건이다. 그리고 개발사는 Stackless 를 그냥 채택한 것이 아니라 가장 중요한 기여자 가운데 하나가 됐다. 여기서 문제의 성격이 달라진다. 이것은 런타임을 갈아 끼우는 작업이 아니라, 자신이 공동 저자인 언어 포크를 버리는 작업이다.

상류의 상황도 이미 정리됐다. Stackless 의 본 저장소 stackless-dev/stackless 는 현재 아카이브 상태이며 마지막 푸시는 2025년 2월 13일이다(GitHub API 로 확인). 상류가 멈추면 유지 비용은 전부 자기 몫이 된다. 발표문이 든 두 번째 동기가 정확히 이 지점이다. 현대적인 라이브러리, 디버거, 프로파일러는 전부 Python 3 을 전제로 만들어지고, Python 2 에 남아 있는 해마다 그중 더 많은 것이 손에 닿지 않는 곳으로 밀려나며, 그만큼 “게임을 개선하는 대신 우리가 직접 유지해야 하는” 몫이 늘어난다.

HN 토론에서 가장 날카로운 질문은 여기서 나왔다. 한 이용자는 Stackless 를 어떻게 처리했는지, greenlet 으로 갔는지 asyncio 로 갔는지 물었고, 직접 찾아본 뒤 “둘 다 아니고 자체 구현”이라며 carbonengine/scheduler 저장소를 공유했다 (@zzzeek). 실제로 확인해 보면 그 저장소는 MIT 라이선스의 C++ 프로젝트로, 저장소 설명은 “greenlet 코루틴을 위한 채널과 스케줄러를 제공한다”이고, README 는 태스크릿과 채널의 스케줄링 순서·동작을 Stackless Python 에 최대한 가깝게 맞추도록 설계했다고 밝힌다. 다만 Stackless API 전부가 아니라 Carbon 이 요구하는 기능만 구현했다고도 명시한다. 정확히 옮기면 asyncio 도 아니고 순수 greenlet 도 아니다. greenlet 코루틴 위에 Stackless 호환 스케줄러와 채널을 다시 구현한 자체 계층이다.

여기서 반드시 구분해야 할 것이 있다. 이번 발표문은 EVE 온라인 본섭의 Stackless 를 어떻게 할지에 대해 한 마디도 하지 않는다. Simon Willison 도 같은 점을 지적했다. 공개된 것은 Carbon 엔진과 EVE Frontier 쪽에서 Stackless 를 떠난 해법이 있다는 사실이며, 그 해법이 Tranquility 에 그대로 적용될지는 발표되지 않았다. 이행 계획의 가장 어려운 축이 아직 공개 범위 밖에 있다는 뜻이다.

미룸의 경제학은 EVE 만의 이야기도 아니다. HN 에서 한 이용자는 골드만삭스와 JP모건이 불과 몇 년 전까지 Python 2.7 을 코어 언어로 유지했으며 여전히 프로덕션에 남아 있다고 증언했다 (@chews). 다른 이용자는 은행권에서 Python 이 프런트오피스 트레이딩 소프트웨어의 “비주얼 베이직” 같은 위치를 차지하고 있으며, 메이저 버전 이행은 수년이 걸리고 수억 달러 규모의 비용과 수천 명의 개발자를 요구하며 실수는 비싸게 치른다고 덧붙였다 (@cyberpunk). 또 다른 이용자는 잘 알려진 금융 기업이 10년 넘게 낡은 Python 버전에 묶여 지원 종료된 SDK 를 계속 쓰다가 API 변경에 깨졌다는 경험을 전했다 (@lokar). 이 증언들이 그리는 그림은 명확하다. 런타임 이행을 미루는 결정은 개별 분기 단위로 보면 거의 언제나 합리적이다. 비용은 즉시 발생하고, 편익은 흐릿하며, 실패의 손실은 재앙적이다.

회의론도 실려야 공정하다. 한 이용자는 240만 줄 Python 의 해법이 더 많은 Python 이고 그 주된 동기가 “성능”이라는 점을 냉소했다 (@oybng). 이 냉소에는 기술적 근거도 붙는다. 오랜 플레이어이자 개발자인 한 이용자는 EVE 의 병목이 단일 서버의 처리 속도가 아니라 네트워크 IO 와 데이터베이스 왕복이며, 네트워크 계층은 이미 C 로 쓰여 있다고 지적했다 (@ShinTakuya). 대규모 교전에서 시간을 늘리는 TiDi 의 원인이 인터프리터 속도가 아니라면, 인터프리터 교체가 체감 성능을 얼마나 바꿀지는 검증되지 않은 약속이다. 발표문 자신도 이 지점에서 물러선다. 최근 Python 3 릴리스가 언어 역사상 손꼽히는 속도 향상을 냈다고 쓰면서도, 그것이 EVE 에 정확히 무엇을 의미할지는 “말하기에 아직 이르다”고 명시했다.

가장 근본적인 비판은 언어 정책을 향한다. 한 이용자는 2002년의 Perl 5.8 로 썼다면 파일 상단에 버전 선언 한 줄만 넣고 오늘의 Perl 5.42 에서 그대로 돌릴 수 있었을 것이라며, Python 은 이미 쓰인 코드보다 아직 쓰이지 않은 코드를 우선하는 것처럼 보인다고 적었다 (@amiga386). 동시성 설계에 대한 아쉬움도 나왔다. Stackless 의 태스크릿과 컨티뉴에이션 조합이 지속적 상태를 갖는 장기 실행 작업에 큰 잠재력이 있었는데 현대 Python 은 asyncio 라는 다른 방향으로 갔다는 지적이다 (@est).

남은 2만 줄, 그리고 EOL 을 장부에 올리는 법

이번 발표에서 실무자가 가장 오래 곱씹어야 할 숫자는 3,300 이 아니라 20,000 이다. 같은 스캔이 세어 낸 바로는, 양쪽 버전에서 문제없이 컴파일되지만 동작이 달라지는 코드가 약 2만 줄이다. 기계가 고칠 수 있는 부분보다 사람이 한 줄씩 읽어야 하는 부분이 여섯 배 많다.

발표문이 든 고전적 예가 나눗셈이다. Python 2 에서 1 / 2 는 0 이지만 Python 3 에서는 0.5 다. 그 숫자가 대미지이거나 ISK 이거나 좌표일 때, 각 줄은 기계적 치환이 아니라 사람의 판단을 요구한다. 이것이 2단계의 내용이며, 1단계를 먼저 하는 이유이기도 하다. 기계적 잔해를 먼저 치워야 사람의 주의가 정말 사람이 필요한 곳에만 쓰인다. 주목할 점은 이 2만 줄이 정적 분석으로 찾아낸 후보 목록일 뿐 결함 목록이 아니라는 것이다. 실제로 몇 줄이 잘못 동작하는지는 아무도 모른다. 검사가 만들어 낸 것은 답이 아니라 유한하고 셀 수 있는 작업 대기열이다. 이행의 불확실성을 없앤 것이 아니라 경계를 그은 것이다.

시나리오는 셋으로 갈린다. 낙관적으로는 2단계의 2만 줄이 대부분 테스트로 걸러지고, 데이터 호환성이 유지된 채 인터프리터가 교체되며, 플레이어는 정말 아무것도 눈치채지 못한다. 비관적으로는 정수 나눗셈이나 문자열·바이트 경계에서 오는 미묘한 오차가 경제 시스템이나 전투 판정에 스며들어, 발생 시점과 검출 시점이 몇 주씩 벌어진다. 23년치 실데이터가 쌓인 세계에서 이런 오차는 롤백이 아니라 보정 작업을 요구한다. 현실적으로는 그 중간이다. 발표문이 이미 밝힌 대로 마일스톤마다 Singularity 플레이테스트를 붙이는 이유가 이것이며, 여기서 플레이어 커뮤니티는 마케팅 장치가 아니라 실제 검증 인프라다.

정리하면 실무자에게 남는 교훈은 세 가지다. 첫째, 이행 가능성은 논쟁이 아니라 측정 대상이다. 컴파일러 통과율은 회의에서 우기지 못하는 숫자이고, 매일 재실행할 수 있다. 둘째, EOL 의 이자는 코드가 아니라 주변부에 쌓인다. 코드 자체는 EOL 다음 날에도 똑같이 잘 돈다. 대신 라이브러리, 디버거, 프로파일러, 채용 시장, 상류 저장소가 하나씩 떨어져 나간다. Stackless 저장소가 아카이브된 시점부터 그 유지 비용은 전액 자기 부담이 됐다. 셋째, EOL 은 날짜가 아니라 조건이다. 2020년 1월 1일에 아무 일도 일어나지 않았다는 사실이 오해를 낳는다. 조직이 장부에 올려야 할 것은 날짜가 아니라 “언제부터 이 런타임 위에서 우리가 필요로 하는 도구를 구할 수 없게 되는가”라는 조건이며, 그 조건이 충족되는 시점은 발표 없이 조용히 온다.

결론

미룬 이행은 실패인가 판단인가. 정직한 답은 둘 다 참이되 시점이 다르다는 것이다. 분기 단위로 보면 매번 옳았다. 발표문 자신이 “충분히 안정적이었기 때문에 크고 위험한 이행은 스스로를 정당화하지 못했다”고 썼고, 이것은 변명이 아니라 정확한 서술이다. 그러나 그 옳은 결정 64분기를 누적하면, 언어 역사상 가장 큰 성능 향상을 그냥 보내고, 상류가 아카이브된 포크를 혼자 떠안고, 세대가 바뀐 도구 생태계 바깥에 서 있는 조직이 된다. 개별적으로 합리적인 선택의 합이 집합적으로 비합리적인 위치를 만든다.

그래서 이 발표에서 가장 배울 만한 것은 이행 계획이 아니라 측정 방법이다. 2만 개 파일을 두 인터프리터로 컴파일해 3,300 과 20,000 이라는 두 숫자를 얻는 순간, 16년짜리 막연한 공포는 유한한 작업 목록이 됐다. 부채를 갚기 전에 부채를 계상한 것이다.

남는 질문은 각자의 조직을 향한다. 지금 프로덕션에서 돌고 있는 런타임과 프레임워크 가운데, 상류가 이미 멈췄는데 아직 장부에 올라오지 않은 것은 무엇인가. 그리고 그 거리를 오늘 재려면 어떤 명령을 한 줄 돌려야 하는가. EVE 는 그 명령이 컴파일러 두 개였다고 알려 줬다.


출처: