본문으로 건너뛰기
피드

Julia Evans가 Django로 ‘2010년식 웹사이트’를 만들며 좋았던 것들

backend 약 9분
vote
0
댓글
북마크

Julia Evans가 프론트엔드 중심의 작은 앱 대신, SQL 데이터베이스와 서버 렌더링 HTML을 쓰는 Django 방식으로 웹사이트를 만들며 느낀 장점과 불편함을 정리했다. QuerySet, 템플릿 필터, 자동 마이그레이션은 생산성을 높였고, 클래스 기반 뷰와 성능 설정은 여전히 고민거리로 남았다. 특히 템플릿 캐시 설정 하나로 처리량이 초당 2-3 요청에서 약 12 요청으로 오른 대목이 실전적이다.

  • 1

    Django QuerySet 메서드 체이닝은 복잡한 필터 조건을 읽기 좋게 만드는 장점이 있음

  • 2

    urlize, linebreaksbr, date, json_script, querystring 같은 템플릿 유틸리티가 HTML 생성의 잔일을 줄여줌

  • 3

    자동 마이그레이션 덕분에 문제 이해가 바뀔 때 데이터 모델을 계속 고치기 쉬움

  • 4

    클래스 기반 뷰 상속보다 함수로 공통 로직을 빼는 방식이 더 직관적이었다고 평가함

  • 5

    템플릿 캐시를 다시 켜자 월 10달러 정도의 가상머신에서 처리량이 초당 2-3 요청에서 약 12 요청으로 개선됨

왜 갑자기 ‘2010년식 웹사이트’인가

  • Julia Evans는 요즘 SQL 데이터베이스를 두고, 서버에서 HTML을 렌더링하는 방식으로 웹사이트를 만드는 중임

    • 본인은 2000년대나 2010년대에 이런 방식의 웹 개발을 제대로 배운 적이 없어서, 생각보다 쉽지는 않다고 함
    • 이전에는 정적 사이트 생성기, 약간의 JavaScript가 있는 정적 사이트, Vue.js 단일 페이지 앱, Lambda나 Go 백엔드 조합에 익숙했음
  • 프론트엔드 중심 접근이 싫어진 게 아니라, 페이지가 많은 사이트에서는 로직을 한곳에 두고 싶어진 쪽에 가까움

    • 단일 페이지 앱에서는 가능한 한 백엔드를 얇게 만들고 프론트엔드에 로직을 모으는 게 편했음
    • 반대로 Django식 서버 렌더링에서는 JavaScript를 최소화하고 백엔드에 로직을 모으는 식임
    • 겉보기엔 정반대지만, 글쓴이 입장에선 “복잡도를 한곳에 둔다”는 점에서 비슷한 선택임

QuerySet 메서드 체이닝이 생각보다 좋았다

  • Django QuerySet에 도메인별 필터 메서드를 붙여두면 뷰 코드가 꽤 읽기 좋아짐

    • 예시는 Events.objects.approved().for_tab(tab).with_festivals(...).is_free(...).is_outdoors(...) 같은 형태임
    • 각각의 메서드는 내부적으로 WHERE 조건을 추가하지만, 사용하는 쪽에서는 “승인된 이벤트 중 탭과 축제, 무료 여부, 야외 여부로 필터링”처럼 읽힘
  • 글쓴이는 원래 “SQL을 아는데 query builder가 왜 필요하지?” 쪽에 가까웠음

    • 그런데 복잡한 조건을 직접 매번 늘어놓는 대신 의미 있는 메서드로 감싸니 구조가 훨씬 좋게 느껴졌다고 함
    • 필터 정의 문법 자체가 취향은 아니지만, 대부분의 시간은 정의가 아니라 사용 쪽에서 보내기 때문에 만족도가 높았음
  • Django 템플릿 필터도 잔일을 많이 줄여줌

    • urlize는 일반 텍스트 URL을 링크로 바꿔줌
    • linebreaksbr는 줄바꿈을 <br>로 바꿔줌
    • date는 날짜 표시를 포맷해줌
    • json_script는 Python dictionary를 안전하게 JSON으로 바꿔 <script> 태그에 넣어줌
  • 특히 querystring 템플릿 태그를 마음에 들어 함

    • ?date=2026-06-01 같은 필터가 있는 페이지에서 기존 query string을 유지한 채 특정 값만 바꾸는 링크를 만들 수 있음
    • 예를 들어 이전 날짜 링크를 만들거나, outdoors 파라미터만 제거하는 식의 UI가 깔끔해짐

자동 마이그레이션은 여전히 강력하다

  • Django의 자동 데이터베이스 마이그레이션은 글쓴이가 계속 좋아하는 기능임

    • 모델에 필드를 추가하거나 구조를 바꾸면 Django가 migration을 생성해줌
    • 지금까지 이미 19개의 데이터베이스 migration을 만들었고, 앞으로도 더 생길 것 같다고 함
  • 이게 중요한 이유는 문제 이해가 개발 중에 계속 바뀌기 때문임

    • 처음부터 완벽한 스키마를 설계하기 어렵고, 실제 데이터를 보면서 모델을 바꾸게 됨
    • 그때 migration이 자연스럽게 따라와주면 데이터 구조 변경이 덜 무서워짐

상속 기반 뷰는 취향이 아니었다

  • Django 문서는 class-based view와 상속으로 뷰 코드를 조직하는 선택지를 자주 제시함

    • 글쓴이도 공통 코드가 많은 4개 뷰를 상속 구조로 묶어보려 했음
    • 결과적으로는 경험이 별로였고, 공통 로직을 함수로 빼는 방식으로 바꿨다고 함
  • 다만 Django가 제공하는 인터페이스를 쓰기 위한 상속은 괜찮게 받아들임

    • 예를 들어 class EventQuerySet(SearchableQuerySetMixin, models.QuerySet) 같은 형태는 그냥 필요한 인터페이스를 구현하는 느낌으로 사용함
    • 싫은 건 “내 애플리케이션 로직을 상속 계층으로 공유하는 방식”에 더 가까움

성능은 아직 감이 잘 안 잡힌다

  • LLM 스크래퍼가 사이트를 발견하면서 초당 약 10 요청을 보내기 시작했고, 그때부터 처리 용량을 고민하게 됨

    • 일단 차단은 했지만, 사이트가 어느 정도 버틸 수 있어야 하는지 감이 필요해짐
    • Go 백엔드에 익숙할 때는 보통 그냥 충분히 빠른 경우가 많았는데, Django는 느낌이 다르다고 함
  • 가벼운 부하 테스트 결과는 처음엔 꽤 낮았음

    • ab -n 1000 -c 1 기준, 월 약 10달러짜리 가상머신에서 초당 2-3 요청 정도였음
    • profiling을 파고들고 싶은 유혹이 있었지만, 지금 가장 배워야 할 게 profiling은 아니라고 선을 그음
  • 성능 고민 포인트도 여러 개 남아 있음

    • 트래픽이 가끔 몰리는 사이트라면 scale up을 염두에 둬야 하는지
    • 캐시하기 쉬운 구조로 설계해야 하는지
    • Django 문서에서 Jinja가 더 빠르다고 하는데 템플릿 엔진을 바꿀 가치가 있는지
    • {% block %}{% include %}보다 빠르다는 말이 실제로 얼마나 중요한지

중요

> 템플릿 캐시 하나가 체감 성능을 크게 바꿨다. 초당 2-3 요청 수준이던 사이트가 캐시를 다시 켠 뒤 약 12 요청까지 무리 없이 처리하는 쪽으로 좋아짐.

  • 실제로 가장 큰 힌트는 데이터베이스가 아니라 템플릿 렌더링에서 나왔음

    • CPU profiling을 보니 템플릿 렌더링에 시간이 많이 쓰이고 있었음
    • Django 성능 문서에서 cached template loader가 템플릿을 매번 컴파일하지 않게 해 성능을 크게 올릴 수 있다는 설명을 봄
    • 알고 보니 기본으로 켜져 있어야 할 cached template loader를 다른 설정을 만지다 실수로 꺼둔 상태였음
  • 캐시를 다시 켠 뒤에는 처리량이 대략 초당 12 요청까지 올라감

    • 엄밀한 전후 벤치마크는 아니지만, CPU를 다 쓰지 않고도 꽤 쉽게 처리하는 수준이 됨
    • 흔히 “성능 문제면 DB 쿼리부터 봐라, 인덱스 봐라”라고 하지만, 이번에는 템플릿 설정이 더 중요했음
    • SQLite를 쓰고 있어서 느린 DB 쿼리도 CPU profile에 드러날 거라, 지금은 CPU profile부터 보는 게 더 유용했다고 함

기술 맥락

  • 이 글의 큰 선택은 서버 사이드 렌더링으로 로직을 백엔드에 모으는 거예요. 페이지가 많아질수록 프론트엔드 상태와 라우팅, API 조합이 복잡해질 수 있어서, Django처럼 HTML을 서버에서 만들어주는 방식이 오히려 단순해질 때가 있거든요.

  • QuerySet 메서드를 쓰는 이유는 SQL을 몰라서가 아니에요. approved, future, with_tags 같은 이름으로 조건을 감싸두면 뷰 코드가 데이터베이스 문법보다 제품의 의미에 가깝게 읽혀요. 유지보수할 때 이 차이가 꽤 커요.

  • 자동 마이그레이션이 편한 이유는 초기 스키마가 거의 항상 틀리기 때문이에요. 실제 서비스를 만들다 보면 필드가 추가되고 의미가 바뀌는데, Django는 모델 변경과 DB 변경을 연결해줘서 실험 비용을 낮춰줘요.

  • 성능 쪽에서는 템플릿 캐시 사례가 중요해요. 병목이 항상 DB에 있는 건 아니고, 프레임워크 설정 하나 때문에 매 요청마다 템플릿을 다시 컴파일하는 식의 낭비가 생길 수 있어요. 그래서 Django에서는 쿼리 확인과 함께 CPU profiling도 꽤 실용적인 출발점이에요.

Django의 매력은 화려한 새로움보다 ‘웹 앱 만들 때 매번 필요한 것들이 이미 있다’는 데 있다. 동시에 설정을 잘못 건드리면 성능이 크게 흔들릴 수 있어서, 프레임워크의 기본값을 이해하는 일이 꽤 중요하다.

댓글

댓글

댓글을 불러오는 중...

backend

클릭하우스가 PgBouncer 처리량을 4배로 끌어올린 방법

ClickHouse Managed Postgres 팀은 단일 스레드인 PgBouncer를 여러 프로세스로 띄우고 `so_reuseport`와 피어링(peering)을 조합해 16 vCPU 머신을 제대로 쓰게 만들었다. 동일한 AWS 환경에서 단일 프로세스는 약 8.7만 TPS 근처에서 막혔지만, 16개 프로세스 구성은 약 33.6만 TPS까지 올라갔다.

backend

SQLite 쓸 거면 STRICT 테이블을 기본값으로 보자는 주장

SQLite의 STRICT 테이블은 INTEGER 컬럼에 문자열이 들어가는 식의 타입 실수를 막아주는 기능이다. 글쓴이는 SQLite 특유의 유연한 타입 시스템보다, 실수는 빨리 터뜨리는 쪽이 실무에서 낫다고 주장한다. 다만 기존 테이블을 바로 STRICT로 바꾸기 어렵고, SQLite 3.37.0 이상에서만 쓸 수 있다는 제약도 있다.

backend

왜 아직도 Lisp를 배워볼 만한가

Lisp는 괄호가 많은 이상한 언어처럼 보이지만, 핵심은 문법이 아니라 언어 자체를 확장할 수 있다는 점에 있음. 매크로, 코드-데이터 동일성, REPL 중심 개발이 합쳐지면 프로그램을 '작성'한다기보다 살아 있는 시스템을 계속 진화시키는 방식으로 개발하게 됨.

backend

EDB 포스트그레스 AI, 포레스터 멀티모델 데이터 플랫폼 리더 선정

EDB의 AI 데이터 플랫폼 EDB 포스트그레스 AI가 2026년 2분기 포레스터 웨이브 멀티모델 데이터 플랫폼 보고서에서 리더로 선정됐음. 트랜잭션, 분석, AI 워크로드를 하나의 플랫폼에서 다루는 흐름이 강해지는 가운데, EDB는 전략과 현재 제공 기능 모두에서 높은 평가를 받았다고 밝힘.

backend

Bun, Zig에서 Rust로 갈아탔다: 11일짜리 초대형 런타임 포팅 실험

Bun 팀이 53만 줄이 넘는 Zig 코드베이스를 Rust로 기계적으로 포팅했고, Bun v1.4.0부터 Rust 기반으로 전환될 예정임. 핵심 이유는 성능보다 안정성이었고, use-after-free, double-free, 메모리 누수 같은 문제를 컴파일러와 타입 시스템으로 더 빨리 잡기 위해서였음. Claude Code 워크플로 64개를 병렬로 돌려 11일 만에 전 플랫폼 테스트 통과까지 밀어붙인 사례라, LLM 기반 대규모 코드 마이그레이션의 꽤 강한 신호탄임.