파이썬은 백엔드를 위해 태어난 언어가 아니다
최규민, Claude
2026년 3월 3일6 분 소요
"왜 이렇게 많은 기업들이 Python 백엔드를 버리고 다른 언어로 떠나는 걸까?"
Python은 의심의 여지 없이 세계에서 가장 인기 있는 프로그래밍 언어 중 하나입니다. 간결한 문법, 낮은 진입 장벽, 풍부한 라이브러리 생태계 덕분에 데이터 사이언스부터 웹 개발까지 광범위한 영역에서 사랑받고 있습니다. Django와 FastAPI 같은 프레임워크는 수많은 스타트업의 첫 번째 백엔드 선택지이기도 합니다.
그런데 흥미로운 현상이 관찰됩니다. Python으로 백엔드를 구축한 기업들 중 상당수가, 서비스가 성장하면서 다른 언어로의 전환을 결정하고 있다는 것입니다. 그것도 한두 곳이 아닙니다. 한국의 스타트업부터 글로벌 SaaS 기업까지, 다양한 규모와 도메인의 기업들이 저마다의 이유를 들어 Python 백엔드를 떠나고 있습니다.
이 글에서는 Python에서 다른 언어로 백엔드를 전환한 8개 기업의 기술 블로그를 분석합니다. 각 기업이 직면한 문제를 유형별로 재구성하여, Python이 백엔드 언어로서 가지는 구조적 한계를 살펴보겠습니다. 단, 이 글의 목적은 "Python은 나쁜 언어다"라는 주장이 아닙니다. Python이 백엔드라는 특정 영역에서 보이는 한계를, 실제 사례를 통해 관찰하는 것이 이 글의 목적입니다.
분석 대상
이 글에서 분석하는 8개 기업의 전환 사례는 다음과 같습니다.
| 기업 | 전환 방향 | 도메인 |
|---|---|---|
| 스포카 | Python/Flask → Kotlin/Spring | POS/매장 관리 |
| Scald (로제타렌즈) | Python/Django → Node.js/Express | RAG API 플랫폼 |
| Blux (블럭스) | Python/FastAPI → TypeScript/NestJS | CRM 마케팅 |
| Lovable | Python → Go | AI 코드 생성 |
| Telemetry Harbor | Python/FastAPI → Go/Fiber | 시계열 데이터 플랫폼 |
| Travel Audience | Python+Go 이중 구조 → TensorFlow 통합 | 광고 플랫폼 |
| Verihubs | Python/FastAPI → Go + Rust + Triton | 신원 인증 (AI) |
| Funnel.io | Python → Rust | 데이터 파이프라인 |
전환한 목적지는 Kotlin, Node.js, TypeScript, Go, Rust로 제각각입니다. 그러나 떠난 이유에는 놀라울 정도로 공통적인 패턴이 존재합니다. 이 패턴들을 하나씩 살펴보겠습니다.
#1. 동적 타입 시스템의 대가
Python의 가장 큰 매력이자 가장 큰 약점은 동적 타입 시스템입니다. 변수에 타입을 선언하지 않아도 되는 유연함은 빠른 프로토타이핑을 가능하게 하지만, 코드베이스가 커지고 비즈니스 로직이 복잡해질수록 그 대가를 치르게 됩니다. 분석한 8개 사례 중 5개 기업이 동적 타입 시스템을 핵심 전환 사유로 지목했습니다.
타입 힌트는 약속이지, 계약이 아니다
스포카의 키친보드 팀은 이 문제를 다음과 같이 설명합니다.
"Python은 동적 프로그래밍 언어입니다. 기능이 점점 많아지고 기존 기능을 수정해야 할 필요성이 증가함에 따라 이 동적 타입은 우리의 발목을 잡기 시작했습니다."
Python 3.5부터 도입된 타입 힌트(type hints)는 이 문제를 완화하기 위한 시도였습니다. 그러나 타입 힌트는 본질적으로 문서화 도구에 불과합니다. "타입 힌트는 단지 코드 작성 시 도움을 주기 위한 기능이지 런타임에 해당 타입으로 동작함을 보장하지는 않습니다." 스포카 팀은 "적혀진 타입과 다른 값이 들어가 한참을 디버깅했던 기억"을 회상하며, 이것이 단순한 이론적 한계가 아닌 실제 개발 현장의 고통이었음을 증언합니다.
TypeScript나 Kotlin 같은 언어에서는 컴파일러가 타입 불일치를 빌드 단계에서 차단합니다. Java의 제네릭, Rust의 소유권 시스템도 마찬가지입니다. Python의 타입 힌트는 이러한 정적 보증을 제공하지 못합니다. def process(data: dict) -> str: 이라고 선언하더라도, 런타임에 data에 리스트가 들어오는 것을 막을 방법이 없습니다.
타입 안전성을 위한 우회 비용
Blux 팀 역시 동일한 문제에 직면했습니다. 상품 추천 제품 시절에는 비즈니스 로직이 단순하여 동적 타이핑이 큰 문제가 되지 않았습니다. 요청을 받고, 고객 히스토리를 조회하고, 모델 추론을 수행하고, 응답을 반환하는 선형적 흐름이었기 때문입니다.
그러나 CRM 마케팅 제품(Blux Message)으로 확장하면서, 이메일·문자·푸시 알림 등 여러 채널을 통한 메시지 발송, 결과 분석, 행동 데이터 기반 개인화까지 포함하는 복잡한 시스템을 구축해야 했습니다. B2B 제품 특성상 "클라이언트가 100만 명의 고객에게 CRM 메시지를 보내려 한다면 그 즉시 100만 명에게 발송할 수 있어야" 했기에, 런타임 타입 오류는 곧바로 비즈니스 리스크가 되었습니다.
Python에서 타입 안전성을 확보하려면 Pydantic, Attrs, Dataclass 같은 외부 라이브러리와 Mypy 같은 별도 정적 분석 도구를 조합해야 합니다. 이 도구들은 분명 도움이 되지만, 언어 차원에서 타입 안전성을 보장하는 것과는 근본적인 차이가 있습니다. Blux 팀이 기존 상품 추천 제품에 이러한 도구를 적용하지 않았다는 사실 자체가, Python 생태계에서 타입 안전성이 "선택사항"으로 취급되고 있음을 방증합니다. 별도로 도입하지 않으면 존재하지 않는 안전장치입니다.
덕 타이핑의 추적 비용
스포카 팀은 Python의 덕 타이핑(duck typing)이 야기하는 구체적인 유지보수 문제도 지적합니다.
"기능의 수정이 필요할 때 인터페이스가 없기 때문에 구현 코드를 추적하여 수정하는 것이 쉽지 않다"
정적 타입 언어에서는 인터페이스나 추상 클래스를 따라가면 해당 메서드를 구현하는 모든 클래스를 IDE가 즉시 찾아줍니다. Kotlin의 interface, TypeScript의 interface, Go의 암묵적 인터페이스, Rust의 trait 모두 이 역할을 수행합니다. 그러나 Python에서는 "특정 메서드를 가진 객체"를 사용하는 코드가 있을 때, 그 메서드를 실제로 구현하는 객체가 무엇인지 코드를 직접 읽어야만 알 수 있습니다. 코드베이스가 작을 때는 감당할 수 있지만, 수만 줄을 넘어가면 이 추적 비용은 기하급수적으로 증가합니다.
조용한 타입 변환의 위험
Telemetry Harbor의 사례는 동적 타입 문제의 또 다른 차원을 보여줍니다. Go로 카나리 배포를 진행한 후, 에러율이 예상보다 높게 나왔습니다. 원인을 추적하니 Python의 Pydantic 라이브러리가 수행하는 자동 타입 강제변환(type coercion)이었습니다.
구체적으로 다음과 같은 변환이 경고 없이 이루어지고 있었습니다:
True/False값이 조용히1/0으로 변환"123.45"같은 문자열이 경고 없이float으로 변환- 기본 설정이
StrictFloat가 아닌float이라 느슨한 변환이 "기대 동작"
저자는 이렇게 표현합니다:
"우리의 '더 엄격한' Go 구현이 사실은 '유연한' Python 구현이 조용히 통과시키고 있던 데이터 품질 문제를 잡아내고 있었다."
유연함이 미덕이 될 수 있지만, 시계열 데이터 파이프라인처럼 데이터 무결성이 생명인 영역에서는 치명적인 결함이 됩니다. Pydantic의 이런 동작은 "버그"가 아니라 "설계 철학"이라는 점에서 더 근본적인 문제입니다. Python 생태계 전체가 "유연함 우선"이라는 철학 위에 서 있기 때문입니다.
디버깅의 어둠
Verihubs 팀은 이 문제를 가장 직접적으로 표현합니다:
"동적 타입 시스템 때문에 문제를 추적하는 것이 극도로 어려웠다."
코드베이스가 스프린트마다 커지면서, 리팩토링은 점점 더 고통스러운 작업이 되어갔습니다. 동적 객체와 가비지 컬렉터가 빈번한 메모리 누수를 유발했고, 이 누수의 원인을 추적하는 것조차 동적 타입 시스템 때문에 극도로 어려웠습니다.
Funnel.io 역시 "타입 안전성을 개선할 방법"을 찾는 것이 Rust 도입의 핵심 동기였음을 밝힙니다. 하루 18~20TB의 데이터를 처리하는 파이프라인에서, 런타임에서야 발견되는 타입 오류는 감당하기 어려운 리스크였습니다. Rust의 컴파일 타임 검사와 소유권 모델이 "concurrent environments에서 문제를 일찍 잡아낸다"는 점이 결정적이었습니다.
#2. 비동기와 동시성의 구조적 한계
현대 백엔드 서비스는 수많은 외부 API 호출, 데이터베이스 쿼리, 파일 I/O를 동시에 처리해야 합니다. Python의 비동기 처리 모델은 이 요구사항에 대해 구조적인 한계를 보입니다. 8개 사례 중 4개 기업이 이 문제를 핵심 전환 사유로 꼽았습니다.
"나중에 덧붙여진" 비동기
Scald(로제타렌즈)의 분석이 이 문제의 핵심을 정확히 짚습니다.
"파이썬 async 지원은 나중에 덧붙여졌고, 거기에 어려움의 핵심이 있습니다."
JavaScript는 처음부터 이벤트 루프 기반의 비동기 모델을 채택했습니다. Go는 고루틴(goroutine)이라는 경량 동시성 원시 타입을 언어 수준에서 지원합니다. 반면 Python의 asyncio는 Python 3.4(2014년)에 도입되어, 기존의 동기식 생태계 위에 비동기를 "얹은" 형태입니다. 이 차이는 단순한 역사적 사실이 아니라, 개발 경험 전체에 영향을 미치는 근본적인 설계 차이입니다.
이 설계 결정의 영향은 여러 층위에서 드러납니다.
네이티브 async 파일 I/O의 부재. Scald 팀은 이렇게 지적합니다: "파이썬에는 네이티브 async 파일 I/O가 없습니다." aiofiles 같은 라이브러리는 async API를 제공하지만, 내부적으로는 스레드 풀에 작업을 위임하는 방식입니다. 진정한 비동기가 아닌, 비동기의 외양만 갖춘 것입니다. Node.js의 libuv나 Go의 런타임처럼 OS 수준에서 비동기 I/O를 수행하는 것과는 본질적으로 다릅니다.
색칠된 함수(Colored Functions) 문제. Python에서 async 함수와 sync 함수는 서로 자유롭게 호출할 수 없습니다. async 함수 안에서 sync 함수를 호출하면 이벤트 루프가 블로킹되고, sync 함수 안에서 async 함수를 직접 호출할 수도 없습니다. Django에서는 이 경계를 넘을 때마다 sync_to_async와 async_to_sync 래퍼를 사용해야 하는데, Scald 팀에 따르면 이를 "정말 모든 곳에 써야" 하는 상황이 됩니다. 코드의 가독성과 유지보수성이 급격히 저하되는 지점입니다.
생태계의 파편화. Python의 비동기 생태계는 통일되어 있지 않습니다. Gevent는 그린릿(greenlets)을 사용하지만 표준 라이브러리를 monkey patching해야 동작합니다. Gunicorn에서는 async 코드의 실제 동작이 사용하는 워커 타입(gthread, uvicorn, gevent)에 따라 달라집니다. 같은 코드가 배포 환경의 설정에 따라 다르게 동작할 수 있다는 뜻입니다.
Scald 팀이 조사한 PostHog 사례는 이 문제의 심각성을 여실히 보여줍니다. PostHog는 대규모 오픈소스 분석 플랫폼임에도 불구하고 "여전히 WSGI 위에 Gunicorn Gthread 워커를 돌리고" 있으며, async_to_sync를 자체적으로 구현하여 유지보수하고 있었습니다. Python 생태계에서 가장 성공적인 프로젝트조차 async 문제를 완전히 해결하지 못한 채 우회하고 있다는 뜻입니다.
Scald 팀은 RAG API 플랫폼의 특성상 LLM 및 임베딩 API 호출이 매우 잦고, 문서 청킹 및 벡터 임베딩 생성 시 동시에 여러 요청을 보내야 했습니다. Django에서는 "상황이 금세 지저분해졌다"고 합니다. Node.js로 전환한 후 "박스에서 꺼내 바로 쓴 상태로도 처리량이 약 3배 늘었다"고 보고합니다. 아직 async 최적화도 본격적으로 수행하지 않은 상태에서의 수치입니다.
GIL이라는 구조적 병목
Lovable의 사례는 Python의 동시성 한계가 비즈니스 성장을 직접적으로 저해한 경우입니다. Lovable의 워크로드는 단일 채팅 요청 하나를 처리하는 데 50개 이상의 HTTP 요청을 동시에 보내야 하는 구조였습니다.
Python의 GIL(Global Interpreter Lock)은 한 시점에 하나의 스레드만 Python 바이트코드를 실행할 수 있도록 제한합니다. 이 설계는 CPython의 메모리 관리를 단순화하기 위해 도입되었지만, CPU 바운드 병렬 처리를 사실상 불가능하게 만듭니다. I/O 바운드 작업에서는 GIL이 해제되어 어느 정도의 동시성이 가능하지만, 복잡한 데이터 처리와 I/O가 혼재하는 실제 백엔드 워크로드에서는 이 제약이 심각한 병목이 됩니다.
엔지니어 Viktor Eriksson은 이렇게 말합니다:
"Python is great, but not for what we do. Our loads are highly concurrent and parallel."
결국 Lovable은 42,000줄의 Python 코드를 Go로 전면 재작성하는 결정을 내렸습니다. Go의 goroutine은 수천 개를 동시에 실행해도 메모리 오버헤드가 미미하며, channel을 통한 goroutine 간 통신은 언어 수준에서 지원됩니다. Python의 asyncio + threading + multiprocessing 조합으로 달성해야 하는 것을, Go는 언어의 기본 기능으로 제공합니다.
순차 처리의 함정
Telemetry Harbor의 경우, Python의 RQ(Redis Queue) 워커가 페이로드를 하나씩 순차적으로만 처리하는 구조가 근본적인 병목이었습니다. 시계열 데이터 수집 파이프라인에서 "sequential database writes simply weren't going to cut it"이라고 직접 언급합니다. 멀티스레딩이나 슈퍼바이저 프로세스로 수평 확장을 시도했지만, 아키텍처 자체의 한계를 극복하지 못했습니다.
Blux 팀 역시 Python의 asyncio와 aiohttp가 Node.js의 async/await만큼 "자연스럽지 않았다"고 평가합니다. CRM 제품처럼 외부 API와 네트워크 I/O가 빈번한 서비스에서는, 비동기 처리의 자연스러움이 개발 생산성과 코드 품질에 직접적인 영향을 미칩니다. Node.js에서는 "비동기 I/O가 기본 설계"이기 때문에 별도의 라이브러리나 래퍼 없이도 자연스러운 비동기 코드를 작성할 수 있습니다.
#3. 성능과 리소스 효율성
Python은 인터프리터 언어로서 태생적인 성능 한계를 가집니다. 소규모 서비스에서는 이 한계가 체감되지 않지만, 트래픽이 증가하고 처리해야 할 데이터의 규모가 커지면 극명하게 드러납니다. 분석 대상 4개 기업이 전환 후 구체적인 성능 개선 수치를 공개하고 있으며, 그 차이는 단순한 "개선" 수준이 아닌 질적 전환에 가깝습니다.
CPU 800%의 공포
Telemetry Harbor의 Python(FastAPI) 서비스는 부하 증가에 따라 다음과 같은 CPU 사용량 변화를 보였습니다:
| 상태 | Python (FastAPI) | Go (Fiber) |
|---|---|---|
| 유휴(Idle) | 10% | 1% |
| 중부하 | 120~300% | ~60% (안정적) |
| 피크 | 800% | - |
더 충격적인 것은, "매우 가벼운 연결 부하, 프로덕션 서비스에겐 사소해야 할 부하"에서도 서비스가 크래시했다는 점입니다. Redis 메모리가 예측 불가하게 치솟고, Python 프로세스가 예고 없이 종료되며, 장애가 시스템 전체로 캐스케이드(cascade)되었습니다.
Go로 전환한 후에는 동일한 워크로드에서 약 10배의 효율 개선을 달성했습니다. 유휴 시 CPU 1%, 중부하 시에도 약 60%로 안정적으로 유지됩니다. 무엇보다 중요한 것은 예측 가능성입니다. 저자는 "raw speed improvements mattered less than eliminating unpredictable CPU spikes and crashes"라고 강조합니다. 단순히 빠르고 느린 것의 차이가 아니라, 시스템이 예측 가능하게 동작하느냐의 차이입니다.
전환 후 애플리케이션 레이어가 더 이상 성능 병목이 아니게 되었고, 병목이 데이터베이스로 이동했습니다. 이는 Python 시절에는 애플리케이션 코드 자체가 서비스 확장의 천장이었음을 의미합니다.
200대에서 10대로
Lovable의 전환 결과는 Python과 Go의 리소스 효율성 차이를 가장 극적으로 보여줍니다.
| 항목 | Python | Go | 개선 |
|---|---|---|---|
| 서버 인스턴스 | 200대 | 10대 | 95% 감소 |
| 배포 시간 | 15분 | 3분 | 80% 감소 |
| 평균 응답 시간 | 기준 | 12% 단축 | - |
200대의 서버를 10대로 줄였다는 것은 단순한 성능 개선이 아닙니다. 인프라 비용, 운영 복잡성, 장애 표면적(blast radius), 모니터링 부담 모두가 근본적으로 변화합니다. Python에서는 수평 확장(더 많은 서버 투입)으로 해결하려 했던 문제가, Go에서는 단일 인스턴스의 효율성으로 해결된 것입니다.
배포 시간 역시 중요한 지표입니다. 15분이 3분으로 줄었다는 것은 하루에 수십 번 배포하는 팀의 생산성에 직접적으로 영향을 미칩니다.
메모리 복사의 오버헤드
Verihubs의 사례는 Python의 메모리 관리 방식이 야기하는 구체적인 비효율을 보여줍니다. 얼굴 인식 서비스에서 원본 이미지가 각 처리 모듈로 복사되어, PIL 리사이즈, OpenCV 변환, 정규화, numpy 텐서 변환 등을 거치면서 메모리 오버헤드가 급증했습니다. AI 모델 자체가 수십~수백 MB를 차지하는 상황에서 이미지 복사까지 더해지면, 트래픽 증가에 따른 메모리 소비는 기하급수적으로 늘어납니다.
Rust로 전환한 후에는 하나의 원본 이미지만 유지하고 참조(reference)만 전달하는 방식을 채택했습니다. Rust의 소유권(ownership) 모델이 이미지의 수명(lifetime)을 컴파일 타임에 보장하여, 사용 중 해제되는 일 없이 안전하게 참조를 공유할 수 있었습니다.
결과는 다음과 같습니다:
| 항목 | Python | Go + Rust | 개선 |
|---|---|---|---|
| 응답 시간 | 기준 | 50~70% 단축 | - |
| 인프라 비용 | 20+ c6in.2xlarge 인스턴스 | 대폭 축소 | 78% 절감 |
| Docker 이미지 | 수 GB | ~500 MB | - |
| CI 파이프라인 | 30분 | 5분 | - |
| 메모리 누수 | 빈번 발생 | 완전 해소 | - |
가비지 컬렉터의 예측 불가능성
Funnel.io는 하루 18~20TB의 데이터를 처리하는 파이프라인을 운영합니다. 이 규모에서 Python의 가비지 컬렉터(GC)는 예측 불가능한 성능 변동을 일으켰습니다. GC가 언제 동작할지 정밀하게 제어할 수 없기 때문에, 대부하 상황에서 갑작스러운 지연(latency spike)이 발생할 수 있습니다.
Rust는 GC 없이 소유권 모델로 메모리를 관리하므로, "greater control over memory management, ensuring more predictable performance under heavy loads"를 달성할 수 있었습니다. 이는 단순한 벤치마크 수치의 차이가 아닌, 시스템의 예측 가능성이라는 프로덕션 환경에서 가장 중요한 특성의 차이입니다.
#4. 프레임워크와 생태계의 성숙도
언어 자체의 한계를 넘어, Python 백엔드 프레임워크 생태계의 구조적 문제도 전환 사유로 지목됩니다.
경량 프레임워크의 양면성
스포카 팀은 Flask 같은 경량(마이크로) 프레임워크의 구조적 한계를 지적합니다. 높은 자유도는 곧 개발자 역량에 따른 코드 품질 편차를 의미합니다. 정해진 컨벤션이 적기 때문에, 새로운 팀원이 프로젝트의 구조와 패턴을 이해하는 데 상당한 시간이 소요됩니다.
반면 Spring Framework와 같은 성숙한 프레임워크는 풍부한 생태계와 보편적인 설정, 광범위한 레퍼런스를 제공합니다. 개발자가 인프라스트럭처 코드가 아닌 비즈니스 로직에만 집중할 수 있는 환경을 만들어줍니다. 한국에서 웹 서버 개발에 Java/Spring을 사용하는 개발자 비율이 압도적으로 높다는 현실적 요인도, 스포카 팀이 Kotlin/Spring으로의 전환을 결정한 이유 중 하나였습니다.
이 문제는 Python 언어 자체의 결함이라기보다, Python 웹 프레임워크 생태계가 Spring이나 .NET 같은 엔터프라이즈급 프레임워크 수준의 구조화된 개발 경험을 아직 제공하지 못하고 있다는 점을 보여줍니다.
Django의 미완성된 현대화
Django는 Python 웹 프레임워크 중 가장 성숙하고 기능이 풍부한 프레임워크입니다. "batteries included" 철학에 기반한 ORM, 인증, 관리자 패널 등은 여전히 강력합니다. Scald 팀도 "Django ORM은 정말 인체공학적"이라고 인정합니다.
그러나 Scald 팀이 분석한 바에 따르면, Django의 async 지원은 여전히 미완성 상태입니다. ORM의 async 지원이 완전하지 않고, 공식 문서 자체에 async 사용에 대한 "너무 많은 경고"가 포함되어 있습니다. 비동기 처리가 필수적인 현대 백엔드 아키텍처에서, Python 생태계에서 가장 성숙한 프레임워크조차 완전한 async 지원을 제공하지 못한다는 사실은 구조적 한계를 시사합니다.
Scald 팀은 FastAPI로의 전환도 고려했습니다. FastAPI는 async를 기본 지원하며 SQLAlchemy async ORM과의 통합도 가능합니다. 그러나 저자는 Python async 생태계 전체에 대한 신뢰 부족을 이유로 Node.js를 선택했습니다. 이미 백그라운드 워커를 Node.js로 구축한 상태였기에 "한 생태계에 올인할 좋은 기회"라고 판단한 것입니다.
FastAPI의 천장
Blux 팀은 FastAPI가 "경량 구조로 초기 개발 속도가 매우 빠르고 비동기 처리를 기본으로 지원"한다는 점에서 초기 선택으로 적합했다고 평가합니다. 머신러닝 모델과 동일한 Python 생태계에서 개발할 수 있다는 점도 큰 장점이었습니다.
그러나 CRM처럼 "각 기능이 서로 밀접하게 연관되어 있어 복잡한 형태의 엔지니어링 협업이 필요"한 도메인에서는, FastAPI가 "구조적인 복잡성을 다루는 데 한계"가 있었습니다. NestJS(TypeScript)는 모듈 기반 아키텍처, 의존성 주입, 데코레이터 패턴 등 엔터프라이즈급 패턴을 기본으로 제공하여, 복잡한 비즈니스 로직을 체계적으로 관리할 수 있게 합니다.
이는 FastAPI 자체의 결함이라기보다, Python 웹 프레임워크 생태계가 대규모 엔터프라이즈 수준의 아키텍처 패턴을 충분히 지원하지 못한다는 점을 보여줍니다. Django는 기능은 풍부하지만 현대화가 느리고, FastAPI는 현대적이지만 구조화 수준이 낮습니다. Python 개발자는 이 사이에서 타협해야 하는 상황입니다.
#5. 빌드, 배포, 운영의 무게
이 문제는 Verihubs의 사례에서 가장 두드러지게 나타납니다.
AI 모델을 포함한 Python 서비스의 Docker 이미지는 PyTorch 등의 대형 라이브러리와 모델 파일로 인해 수 기가바이트(GB)까지 비대해졌습니다. GitLab CI 파이프라인은 테스트 → 커버리지 체크 → 빌드 → 후처리를 거치는 데 30분이 소요되었습니다. 이미지가 많아질수록 레지스트리 관리도 부담이 되었고, 배포 자체가 고통스러운 작업이 되었습니다.
Go와 Rust로 전환한 후 Docker 이미지 크기는 약 500MB로 줄었고, CI 시간은 5분으로 단축되었습니다. 이는 단순히 빌드 속도의 차이가 아닙니다. 배포 빈도, 롤백 속도, 개발자 피드백 루프 전체에 영향을 미치는 근본적인 운영 효율성의 차이입니다.
Python은 인터프리터 언어이기 때문에 실행 환경 전체(런타임 + 모든 의존성 패키지)를 컨테이너에 패키징해야 합니다. 반면 Go와 Rust는 단일 바이너리로 컴파일되어, scratch나 distroless 같은 최소한의 베이스 이미지 위에서도 실행할 수 있습니다. 이 근본적인 차이가 빌드, 배포, 운영 전반에서의 경험 차이로 이어집니다.
Lovable의 배포 시간이 15분에서 3분으로 줄어든 것도 같은 맥락입니다. 하루에 수십 번 배포하는 팀에게 배포 한 번에 15분은 상당한 마찰입니다. CI/CD 파이프라인의 속도는 곧 개발 팀의 이터레이션 속도이며, 이터레이션 속도는 곧 제품의 경쟁력입니다.
#6. 이중 언어 아키텍처의 마찰
Travel Audience의 사례는 다소 특수하지만, Python의 성격을 이해하는 데 중요한 시사점을 제공합니다.
이 회사는 ML 모델을 Python으로 학습(training)하고 Go로 서빙(serving)하는 이중 언어 아키텍처를 운영했습니다. Python이 ML 학습에는 최적이지만 실시간 서빙에는 성능이 부족하다는 판단 하에 만들어진 구조입니다. 그 결과 다음과 같은 문제가 발생했습니다:
- 전처리(feature engineering)와 예측 로직을 Python과 Go 양쪽에 이중으로 구현·유지보수해야 했습니다. "any change in the algorithms or our feature engineering requires re-implementing huge parts."
- Python과 Go 구현 사이의 미묘한 불일치가 감지하기 어렵고, 이것이 모델 성능 저하로 이어졌습니다. 동일한 로직을 두 언어로 완벽히 일치시키는 것은 현실적으로 매우 어렵습니다.
- 새 모델 라이브 테스팅에 관련 당사자(데이터 사이언티스트, Go 엔지니어, QA 등)가 많아 "long feedback cycles"이 발생했습니다.
이 사례가 주목할 만한 이유는, Python의 백엔드 서빙 성능 한계가 이중 구현이라는 복잡성의 근본 원인이었다는 점입니다. Python이 모든 것을 처리할 수 있었다면 이중 아키텍처 자체가 필요하지 않았을 것입니다.
최종적으로 이 팀은 TensorFlow의 SavedModel 포맷을 통해 Python에서 학습한 모델을 Go에서 직접 로드하는 방식으로 이중 구현 문제를 해결했습니다. Python 자체를 떠난 것은 아니지만, Python의 역할을 학습 단계로 한정하고 서빙은 Go에 완전히 위임한 것입니다.
그래서, Python은 백엔드에 부적합한가?
지금까지 살펴본 6개의 균열을 종합하면, 하나의 패턴이 보입니다. 이 기업들이 Python을 떠난 이유는 Python이 "나쁜 언어"여서가 아니라, Python이 프로덕션 백엔드 서버를 위해 설계된 언어가 아니기 때문입니다.
Python은 1991년 Guido van Rossum이 만든 범용 프로그래밍 언어입니다. 읽기 쉬운 코드, 빠른 프로토타이핑, 풍부한 표준 라이브러리가 핵심 설계 목표였습니다. "The Zen of Python"이 말하듯, "Beautiful is better than ugly. Simple is better than complex." 이 철학은 놀라울 정도로 성공적으로 구현되었습니다. Python은 프로그래밍 교육, 과학 컴퓨팅, 데이터 분석, 머신러닝 분야에서 사실상의 표준이 되었습니다.
그러나 프로덕션 백엔드 서버는 Python의 설계 목표와는 다른 것을 요구합니다.
| 백엔드 서버의 요구사항 | Python의 현실 |
|---|---|
| 컴파일 타임 타입 안전성 | 런타임에만 발견되는 타입 오류, 타입 힌트는 선택적 문서화 |
| 높은 동시성 처리 | GIL에 의한 병렬 처리 제약, 후발적 asyncio |
| 예측 가능한 성능 | GC에 의한 불규칙한 지연, 인터프리터 오버헤드 |
| 효율적 메모리 관리 | 객체 복사 기반, 참조 전달의 안전성 미보장 |
| 컴팩트한 배포 | 런타임 + 전체 의존성 패키징 필요 |
| 성숙한 엔터프라이즈 프레임워크 | Django의 미완성된 async, Flask의 자유방임, FastAPI의 낮은 구조화 |
Python이 여전히 빛나는 영역
이 분석이 "Python을 사용하지 마라"는 결론으로 이어져서는 안 됩니다. 분석 대상 기업들조차 Python의 강점을 인정합니다.
- Scald: "Django ORM은 정말 인체공학적입니다" — ORM의 편의성은 Node.js 전환 후에도 아쉬운 부분으로 언급
- Blux: ML 모델과의 통합, 빠른 프로토타이핑에 FastAPI는 최적이었다고 평가
- Telemetry Harbor: "시장 검증과 빠른 피드백 수집"에 Python은 탁월했다고 인정
- Lovable: "Python is great" — 다만 그들의 워크로드에 맞지 않았을 뿐
Python은 다음 영역에서 여전히 대체 불가능한 강점을 가집니다:
- 데이터 사이언스 및 머신러닝: PyTorch, TensorFlow, scikit-learn, pandas, numpy 등 Python 생태계는 이 분야에서 압도적입니다.
- 빠른 프로토타이핑 및 MVP 검증: Telemetry Harbor의 사례처럼, 시장 반응을 빠르게 확인해야 하는 초기 스타트업에서 Python의 개발 속도는 진정한 강점입니다.
- 스크립팅 및 자동화: 시스템 관리, 데이터 처리, 워크플로우 자동화 등에서 Python의 간결함은 여전히 유용합니다.
- 내부 도구 및 관리자 대시보드: 높은 동시성이 필요하지 않은 내부 서비스에서 Django Admin의 생산성은 여전히 압도적입니다.
한계가 드러나는 조건
8개 사례를 관통하는 "Python 백엔드의 한계가 드러나는 조건"을 정리하면 다음과 같습니다:
- 높은 동시성: 단일 요청이 다수의 외부 API 호출을 동시에 수행하는 구조 (Lovable, Scald)
- 대규모 트래픽: 가벼운 부하에서도 안정성이 보장되어야 하는 서비스 (Telemetry Harbor)
- 복잡한 비즈니스 로직: 다수의 도메인 모델이 상호작용하는 엔터프라이즈급 시스템 (Blux, 스포카)
- 데이터 무결성 요구: 타입 오류가 곧바로 비즈니스 리스크로 이어지는 B2B 서비스 (Blux, Telemetry Harbor)
- 대용량 데이터 처리: 하루 수 TB 이상의 데이터를 파이프라인으로 처리하는 서비스 (Funnel.io)
- 빠른 배포 사이클: CI/CD 속도가 개발 생산성에 직결되는 환경 (Verihubs, Lovable)
- 리소스 효율성: 서버 비용 최적화가 사업 지속성에 영향을 미치는 스타트업 (Lovable, Verihubs, Telemetry Harbor)
이 조건들 중 하나 이상에 해당한다면, Python 이외의 선택지를 검토하는 것이 합리적입니다. Blux 팀이 표현한 것처럼, "기술 스택은 그 자체가 목적이 아니라 문제를 해결하는 수단"이기 때문입니다.
참고한 사례
- 스포카: 서버 언어 전환 이야기
- Scald (로제타렌즈): Python to Node
- Blux (블럭스): 블럭스 엔지니어팀이 개발 언어를 바꾼 이유
- Lovable: From Python to Go
- Telemetry Harbor: From Python to Go
- Travel Audience: Training TensorFlow Models in Python and Serving with Go
- Verihubs: Migrate Python to Golang Rust
- Funnel.io: From Python to Rust
Python은 백엔드를 위해 태어난 언어가 아닙니다. 그러나 그것이 Python을 쓰지 말아야 한다는 뜻은 아닙니다. Python은 여전히 배우기 쉽고, 생산적이며, 특정 영역에서는 대체할 수 없는 언어입니다. 다만, 서비스가 성장하고 요구사항이 복잡해질수록 Python 백엔드는 구조적인 한계에 직면할 가능성이 높습니다. 그 시점을 인지하고 적절한 전환을 준비하는 것이, 8개 기업의 사례가 전하는 공통된 교훈입니다🤗
편집자의 말
파이썬 백엔드를 파보신 분들이라면 아마 “좋은 Python 백엔드는 Pythonic하지 않다”는 점에 일정 부분 동의하실 것이라 생각합니다.
Python이 3.15로 넘어오며 GIL이 폐지되며 앞으로의 방향성은 지켜봐야 하겠지만,
전반적으로 Python이라는 언어의 철학이 백엔드와는 맞지 않는다는 생각을 이번 포스트를 기획하며 다시 한번 떠올리게 되었습니다.
이 글은 파이썬을 까는 글이 아니라 파이썬 백엔드에서 고려할 지점들을 지적하는 글에 가깝습니다. 저도 FastAPI를 자주 사용하고 Pydantic과 @Dataclass를 자주 사용하는, Python Backend Engineer이기 때문입니다 ㅎㅎ




