4P by GN⁺ 6시간전 | ★ favorite | 댓글 2개
  • 초기 웹 시대에 널리 쓰였던 CGI 프로그램이 현대 하드웨어에서 여전히 높은 성능을 낼 수 있음을 실험으로 확인함
  • CGI는 프로세스별로 요청을 처리해 메모리 관리가 자동으로 이뤄지고, 배포가 단순한 장점이 있음
  • 벤치마크 결과, 평범한 16스레드 CPU 서버에서도 초당 2400건 이상, 하루 2억 건 이상 요청 처리 가능성을 입증함
  • Go 언어와 SQLite로 작성된 guestbook.cgi 예제 코드 및 Dockerfile을 오픈소스로 공개함
  • CGI는 지금은 흔히 쓰이지 않지만, 여전히 실용적이고 현대적인 대안이 될 수 있음을 보여줌

CGI 프로그램과 동작 원리

  • 2000년대 초반에는 CGI(Common Gateway Interface) 프로그램이 동적 웹사이트 구축의 주요 방식이었음
  • 대부분 Perl이나 C 언어로 작성되었으며, 성능 향상을 위해 C가 선택되기도 했음
  • CGI의 개념은 간단하지만 강력함
    • 웹서버는 환경 변수에 요청 메타데이터(HTTP 헤더, 쿼리 등) 설정
    • 별도의 프로세스 생성하여 CGI 프로그램 실행
    • 요청 본문을 stdin으로 전달
    • 프로그램의 stdout을 HTTP 응답으로 캡처
    • stderr 출력을 서버 에러 로그로 전달함
    • 프로그램이 요청을 마치면 프로세스가 종료되어 파일 디스크립터 및 메모리가 자동 해제
  • 개발자 입장에서는 신규 버전 배포도 cgi-bin/ 디렉터리에 파일만 복사하면 배포가 끝나 매우 간단했음

Hug of death(트래픽 폭주)

  • 2000년대 초반 웹서버 대부분은 1~2 CPU, 1~4GB 메모리 환경이 일반적임
  • Apache 웹서버가 접속마다 httpd 프로세스를 fork하는 구조 특성상, 다수 접속 시 메모리 요구량이 증가함
  • 동시 접속이 100개를 넘기 힘들었고, 유명 사이트에 링크만 걸려도 서버가 쉽게 과부하되는 경우가 많았음
    • ( Slashdot Effect : 그 당시 유명했던 Slashdot에 링크가 올라오면 트래픽이 쏟아짐. 요즘의 해커뉴스 탑에 오르는 것과 비슷)

현대 서버 환경에서의 CGI

  • 현재는 384개의 CPU 스레드를 가진 서버도 출현, 비교적 작은 VM에서도 16개의 CPU 제공 가능
  • CPU 및 메모리 성능이 대폭 향상됨
  • CGI 프로그램은 별도 프로세스 기반이므로 멀티코어를 자연스럽게 활용 가능함
  • 이러한 점 때문에 현대 하드웨어에서 CGI 프로그램이 얼마나 빠른지 직접 벤치마크 테스트를 진행함
  • 실험은 AMD 3700X(16스레드) 서버에서 수행함

벤치마크 주요 결과

  • 간단한 CGI 프로그램을 ApacheGo net/http 서버 환경 모두에서 테스트
  • guestbook.cgi 프로그램 설명

  • HTTP 부하 생성 도구 plow 를 사용해 16개 연결로 10만 건씩 요청 수행
  • 평범한 하드웨어상에서도 초당 2,400건 이상, 즉 하루 2억 건 이상 요청 처리 가능함
  • 현재 CGI가 대세는 아니나, 여전히 실제 서비스 운영에서도 사용 가능함
  • Apache 환경에서의 write 벤치마크

    • 초당 약 2468건의 요청 처리, 평균 6.47ms의 응답 지연 시간
    • 10만 건의 POST 요청을 40.5초 만에 처리
    • 대부분 요청이 7ms 내 응답, 극소수만 100ms 초과
    • 실질적으로 높은 쓰기 처리 성능 입증
  • Apache 환경에서의 read 벤치마크

    • 초당 약 1959건의 요청 처리, 평균 8.16ms의 응답 지연 시간
    • 10만 건의 GET 요청을 51초 만에 처리
    • 절반 이상의 요청은 8ms 이내, 최대 지연도 31ms에 그침
    • 읽기 성능 역시 충분히 우수함
  • Go net/http 환경에서의 write 벤치마크

    • 초당 약 2742건의 요청 처리, 평균 5.83ms의 응답 지연 시간
    • 10만 건의 POST 요청을 36.4초 만에 처리
    • 처리량은 평균 2,742 RPS, 평균 지연 5.8ms로 Apache보다 수치상 더 나은 성능
    • 95% 이상의 요청이 6ms 이내 처리됨
    • Go 환경에서의 CGI도 충분한 실전 성능 보유
  • Go net/http 환경에서의 read 벤치마크

    • 초당 약 2469건의 요청 처리, 평균 6.47ms의 응답 지연 시간
    • 10만 건의 GET 요청을 40.4초 만에 처리
    • 대부분의 요청이 7ms 안에 서비스 가능
    • 읽기 처리량과 응답 속도 모두 Apache와 비슷하거나 우수함

결론 및 링크

  • CGI 프로그램은 최신 하드웨어에서 초고속 동시성, 간단한 배포, 운영체제가 자동 자원 해제 등의 장점 보유
  • 현대적인 프레임워크에 비해 극히 단순하지만, 일정 규모 서비스에는 지금도 실전 활용 가능
  • 방명록 예제 및 벤치마크 실험 데이터는 아래 깃허브에 공개
    https://github.com/Jacob2161/cgi-bin
Hacker News 의견
  • 1990년대에도 C로 작성된 CGI 프로그램이 정말 빠른 속도를 보여줬던 환경 기억, 그러나 에러가 많이 발생하는 점이 단점이었던 점 인정, 기사에 언급된 Go 프로그램이나 Nim 같은 최신 언어, 데이터베이스 연결을 하지 않는 한 로컬호스트에서 굉장히 빠르고 지연시간이 적은 느낌, 마치 CLI 유틸리티에서 fork & exec을 사용하는 기분, 네트워크 레이턴시에 비하면 비용이 거의 무시할 만한 수준이었음

    • 다만 특정 기술에 중독되기 쉬운 문화 언급, 예를 들어 파이썬 인터프리터 같이 시작 비용이 큰 언어에 익숙해지고 나면 멀티샷 혹은 영속적인 모델을 필요로 하게 됨

    • 초창기 HTTP의 원샷 모델은 FTP 서버가 수백 개의 유휴 로그인 세션을 오래 유지할 만큼의 메모리가 부족했던 문제에서 출발한 것이었음

    • CGI에서 pre-forking(지연을 숨길 수 있음)과 Rust 같은 안전한 언어를 결합하면 뛰어난 시스템 설계 가능성 언급, TLS 종단 처리는 멀티스레드 웹 서버(또는 CloudFront 같은 레이어)에서 처리할 수 있어 편리성 강조

      • 상태가 남지 않고, 코어 덤프 및 디버그가 매우 쉬운 환경, 주로 선형적인 요청 모델로 확장도 간단하게 가능
      • stdin에서 읽고 stdout으로 쓰기만 하면 되는 간결함을 찬양, Websockets가 복잡도를 조금 높이긴 하지만 걱정할 수준 아님
      • Java의 부상으로 인해 fork()의 비용과 C의 위험성 회피 목적에서 애플리케이션 서버로의 전환이 급격히 진행된 흐름 상기, 이제 다시 단순성으로 돌아갈 수 있음 주장
      • Rust를 좋아하지 않지만 이런 방식의 웹 백엔드 코드가 손쉽게 작성될 수 있는 시대가 오면 node/js, php, python 개발자들에게도 매력적으로 다가올 것 기대
  • CGI 시절부터 개발을 시작하며 짧게 실행되는 서브프로세스를 돌리는 것에 대한 강한 반감을 가지게 된 경험

    • PHP와 FastCGI가 웹 요청마다 신규 프로세스를 만드는 성능 문제를 벗어나기 위해 만들어졌다는 배경 설명

    • 최근 하드웨어의 발달 덕분에, 프로세스 시작 비용이 실제로 큰 문제는 아니라는 현실을 알게 됨

    • 이 벤치마크가 초당 2000개의 요청을 처리할 수 있고, 수백 개 정도만 처리해도 여러 인스턴스로 확장하기 쉬운 현대적 환경 언급

    • AWS Lambda를 CGI 모델의 재탄생으로 묘사한 의견에 동의, 꽤 적절한 비유라고 생각함

    • 만약 CGI 스크립트를 정적 링크된 C 바이너리로, 크기까지 신경 쓰며 배포했다면 실망이 덜했을 것이라 언급

      • PHP 해석기나 각종 라이브러리 로딩, 파일 파싱 등 동적 링크의 프로세스 시작 비용이 훨씬 큼
      • Go를 쓴다는 것은 25년 전에도 충분히 경쟁력이 있을 수 있었던 방식이라 확신
      • SQLite 데이터베이스 오픈이 컨텍스트 스위치로 소켓을 넘기는 것과 거의 비슷한 성능이며, 원격 mysql 접속과 비교하면 훨씬 빠른 점 강조
      • FastCGI가 새로운 애플리케이션에도 여전히 뛰어난 선택임을 주장
    • CGI는 저부하 환경에서 금전적·성능적으로 부담이 크지 않았음

      • 고부하 상황에서는 FastCGI처럼 지속적으로 실행되는 프로세스가 더 유리
      • CGI도 초당 2,000 rps까지 처리 가능하지만, FastCGI는 훨씬 높은 성능 달성 가능
      • 별도 서버 프로세스 추가 및 업그레이드 시점에 재시작만 하면 되는데, 성능이 중요할 때 가치 있다고 밝힘
    • Go가 등장하기 전에는 CGI 프로그램을 C/C++로 만드는 게 안전성, 개발 난이도 모두 높았던 00년대 상황

      • Perl과 Python은 해석기 시작 및 컴파일 비용이 상당히 컸고, Java는 실질적으로 더 느렸음
      • AWS Lambda = CGI 모델의 환생에 가깝다는 점 동의
      • 지금은 관리형 FastCGI와 거의 동일한 모델로 돌아온 느낌
      • 단순히 실행파일만 업로드해서 돌리면 될 텐데 복잡도를 잔뜩 추가하는 기술의 홍수에 아쉬움
  • 오늘날 서버에 384 CPU 스레드가 달려있고, 작은 VM조차 CPU 16개 가질 수 있는 시대

    • 이런 하드웨어에서 Kestrel로 개발하면 하루에 수조 번의 요청도 무난히 처리 가능

    • PHP와 비슷한 개발 경험을 string interpolation 연산자로 제공 가능

    • LINQ와 String.Join()을 활용하면 HTML 테이블과 중첩 요소를 간단히 템플릿화

    • 진짜 어려운 점은 MVC/Blazor/EF 같은 생태계의 지뢰밭을 잘 피하는 방법을 아는 것

    • 프로그램 전체를 하나의 최상위 파일로 CLI에서 실행하는 방식도 가능한데, "Minimal APIs"라는 키워드를 모르면 잘못된 문서의 미로로 들어가기 쉬움

      • 코어 기술 위에 추상화 레이어를 덧씌워서 Director/VP 자리를 승진하는 사례가 무척 많다는 점에 놀라움
  • CGI의 장점은 멀티테넌트 환경에서 격리 원시 기능을 새로 구축할 필요 없다는 점

    • 한 요청에 버그가 있어도 프로세스 격리 덕분에 다른 요청에 영향 없음
    • 무한 루프도 선점 스케줄링 덕에 서비스 거부(DoS)로 이어지지 않음
    • rlimit으로 오래 걸리는 요청을 강제로 종료 가능
    • cgroup을 사용해 테넌트별 메모리, CPU, 디스크/네트워크 IO 사용량을 공정하게 할당 가능
    • 네임스페이스/감옥, 권한 분리로 요청마다 접근 권한을 제한할 수 있음
  • CGI 스크립트 덕분에 perl이 빠른 시작 시간을 위해 최적화됐던 이유

    • time perl -e '' 명령 실행 시 perl은 5ms, python3는 33ms, ruby는 77ms로 perl의 빠른 시작 시간 확인

      • tcc mob branch의 #!/bin/tcc -run 방식 스크립트가 perl보다 1.3배 빠르다는 점 언급
      • Julia, Java VM, thread PHP 등도 시작 시간이 매우 길어지는 사례
      • 사람들이 "큰 환경"에 습관적으로 의존하게 되는 현상
      • Lisp 커뮤니티에서도 이미지를 사용함으로써 이게 반복되고, "emacs is bloated" 밈도 여기서 탄생
      • 90년대 중후반 Perl의 전성기는 정말로 CGI 덕분에 가능했던 분위기
      • 당시 getline조차 표준이 아니어서 서드파티 C 라이브러리를 몇백~몇천 라인으로 만들기도 했던 시기 회상
      • 결국 "평판"에 따라 기술이 선택되고, 대부분 친구가 추천해주는 것으로 학습이 이뤄짐
  • apache tomcat 11을 사용해보면 .jsp 파일이나 전체 java servlet 애플리케이션(.war)을 ssh로 업로드만 하면 그냥 동작함

    • 하나의 공유 JVM으로 최대 성능 확보

    • DB 커넥션 풀, 캐시 등도 어플리케이션끼리 공유 가능

    • 정말 인상적인 경험

      • 실제 사용 패턴에 따라 다름

      • 대용량 서비스에는 훌륭하지만, 50개의 소형 어플리케이션을 각각 하루 수백 건만 처리해야 한다면, Tomcat의 메모리 오버헤드는 CGI 스크립트 기반 Apache/Nginx에 비해 너무 크다는 점 지적

      • 파일을 단순히 복사해서 배포하는 시절이 그립다는 감상

      • 왜 배포 과정이 이렇게 복잡해졌는지 아쉬움 토로

      • 지금도 Jetty로 백엔드 웹앱을 즐겁게 사용 중이라는 경험 공유

      • Tomcat/Jakarta EE/JSP 스택이 의외로 상당히 견고하다는 소감

      • PHP처럼 HTML과 코드를 뒤섞어 쓸 수도 있고, 순수 Java 라우트도 가능

      • Websockets 지원, 싱글 프로세스 멀티스레드 모델이라 실시간 통신에도 강점

      • 필요하면 요청마다 데이터 공유 가능, JSP 코드는 기본적으로 요청 범위로 제한

      • 배포가 정말 쉽고, webapps 디렉토리에 신규 파일만 업로드하면 Tomcat이 자동으로 새로운 앱을 로드 및 기존 앱을 언로드

      • 단점으로는 클래스로더 누수로 인해 garbage collection에 실패할 수 있다는 점, 싱글프로세스 모델의 숙명

  • apache 요청에 대한 시각화 도구 ibrahimdiallo.com/reqvis 제작

    • 데스크톱 브라우저에서 최고의 경험 제공
    • HN 트래픽 데이터를 바탕으로 실제 동작 흐름을 웹에서 확인 가능
  • 요즘 복잡한 아키텍처로 가고 있는 상황이 의심스러웠음, 사실 좋은 하드웨어로 충분히 기존 기술을 쓸 수도 있다는 가능성 언급

    • 수백만 명에게 실시간 주가 정보를 알려주는 시스템 설계 질문에 처음에는 Kafka, pubsub 등 복잡한 스트림 구조를 떠올렸지만, 결국 서버에 정적 파일을 두는 단순한 방식도 고민

    • 이런 방식의 실제 운용 비용 궁금

      • 실질적으로 모든 웹 API의 레이턴시는 DB 쿼리나 ML 모델 쿼리가 결정
      • 나머지 프로세스는 Python 등 느린 언어를 써도 별 거 아닌 수준
      • 변화가 드문 데이터만 반환하면 NIC 한계까지도 쉽게 도달 가능
  • serverless 아키텍처와 비슷하지만, 훨씬 간단하고 저렴한 점 강조

    • 실제로 비즈니스 현장에서 이렇게 사용하는 사례가 있는지 궁금
  • 이런 전통적인 구조를 재고하지 않고 단순히 "serverless functions"라는 새로운 패러다임만 만들어낸 것에 아쉬움

    • Lambda 같은 serverless function이 별도의 보호 메커니즘(마이크로 VM 등)이 있기는 하지만, 사실상 CGI와 권한 조정만으로도 훨씬 적은 복잡성으로 멀리 갈 수 있었을 것이라 생각

cgi는 그렇다 쳐도 jsp에 대한 반응이 놀랍네요 ㅋㅋ
jsp가 벌써 그정도로의 고대 유물이 된걸까요.