2P by GN⁺ 6시간전 | ★ favorite | 댓글 1개
  • CGI 프로그래밍으로도 하루에 2억 건 이상의 웹 요청 처리가 가능함
  • 최근 하드웨어 성능 향상으로 CGI 방식의 단점이 크게 줄어듦
  • Go와 SQLite를 활용한 CGI 프로그램이 16스레드 CPU에서 탁월한 성능을 보여줌
  • CGI는 여러 CPU 코어 활용에 특히 적합한 구조 제공
  • 현대 기술로 인해 과거의 웹 애플리케이션 개발 방식도 충분히 실용 가능성 보임

CGI의 과거와 현재

  • 1990년대 후반, 필자는 CGI로 웹 개발을 시작하였으며 당시에는 NewsPro와 같은 시스템을 사용함
  • CGI는 웹 요청마다 새로운 프로세스 실행 및 종료를 반복하여 높은 오버헤드 발생함
  • 이런 이유로, 더 효율적인 PHP, FastCGI 등 대체 기술이 개발됨

하드웨어 성능 발전

  • 지난 20여 년 동안 컴퓨터 속도와 성능이 급격히 증가함
  • 2020년에 필자는 Go와 Rust로 개발된 툴(ripgrep 등)을 활용하며, 프로세스 실행 방식의 실용성을 재발견함

현대적 CGI 방식의 장점

  • Go와 Rust 같이 실행 속도가 빠른 언어로 CGI를 구현하면, 구식 CGI의 단점 대부분이 해소됨
  • CGI 프로그램은 요청당 별도 프로세스로 동작함으로써 멀티코어 CPU 활용에 최적화됨
    • 예를 들어, 16스레드 환경에서는 2400건 이상의 요청/초 = 2억+ 요청/일 처리 가능성 확인됨
    • 대형 서버는 384개 이상의 CPU 스레드 제공 가능

개발 문화에 대한 인사이트

  • 현재 Go, Rust와 같은 언어의 도입으로 1990년대 CGI 방식이 다시 의미를 가질 수 있음
  • 다만, 여전히 모든 환경에 적합한 방식은 아니며 주류 방식으로 권장하지는 않음
  • 중요한 점은 현 시점에서는 CGI가 예전만큼 비효율적인 솔루션이 아님을 실험적으로 증명함

결론

  • 현대 하드웨어와 신속한 언어 지원을 통해 CGI 프로그래밍이 과거와는 비교할 수 없는 성능을 보임
  • 멀티프로세스의 장점을 최대한 활용할 수 있는 사례로, 웹 개발자에게 흥미로운 시사점을 제공함
Hacker News 의견
  • 요즘 Python이라도 CGI는 꽤 빠른 성능 체감
    만약 CGI 스크립트가 시작에 CPU로 400밀리초를 쓴다고 해도, 서버에 64코어가 있으면 초당 160개의 요청 처리, 1일에 서버당 1,400만 트래픽 소화 가능
    억대 단위 하루 트래픽(정적 자산 제외)도 CGI 프로세스 스타트업이 병목은 아니라는 것
    예전엔 이런 기술은 "지루할 만큼 안정적인 기술"이라서 Python 표준 라이브러리에 항상 있던 것이라 생각했는데, 요즘 Python 유지보수자는 오히려 안정성과 후방호환에 부정적 입장
    그래서 너무 '지루하고 안정적인' 모듈들은 표준 라이브러리에서 제거 중, 실제로 cgi 모듈은 3.13 버전에서 삭제
    25년 가까이 Python을 프로토타이핑에 써온 습관 때문이지만 이제는 후회
    JS랑 Lua 중에서 갈등하는 중인 심정

    • cgi 제거 관련 공식 설명 링크는 PEP 594 cgi
      이 링크에서 2000년(25년 전) 작성된 PEP 206 으로 이어지는데, 이미 그때도 "cgi 패키지는 설계가 별로고 손대기도 어렵다"고 설명
      jackrosenthal/legacy-cgi 저장소에서 표준 라이브러리 모듈을 그대로 대체해주는 drop-in replacement 확인

    • Python 개발자들이 cgi라는 이름의 모듈만 뺀 것
      CGI 스크립트 구현은 여전히 http.server 모듈의 CGIHTTPRequestHandler에서 지원
      원래 cgi 모듈엔 HTML 폼 데이터 파싱하는 함수 몇 개만 있었던 점 짚어줌

    • Python에서 cgi 모듈이 표준 라이브러리에서 빠지는 걸 비판하는 건 이해하지만, 대체로 손꼽는다는 JS는 아예 표준 라이브러리가 아님
      Lua도 stdlib에 CGI 모듈 없는 점 지적

    • 개인적으로는 PHP나 JS를 선호
      이런 경우에는 박스 안에 JIT가 기본 제공되니 편의성
      Python 1.6부터 써오면서 주로 OS 스크립팅에만 사용
      예전엔 Tcl을 Apache나 IIS 모듈로 연동하며 계속 C로 모듈을 다시 쓰는 반복이 있었던 경험이 있음(1999~2003년)

    • CGI 스크립트가 400밀리초 CPU 사용한다면, 해당 엔드포인트의 응답 속도도 최소 그만큼 되어버리니 사용성 타격

  • 최근 350달러짜리 미니 서버에 golang 바이너리, rabbitmq, redis, MySQL을 올려서, 같은 서버에서 5,000 req/s 지속적으로 처리
    24시간이면 4억 요청 처리 성능
    요즘 무료 도구들은 정말 훌륭함을 체감
    그럼에도 클라우드 비용은 너무 높다는 생각
    물론 1:1 비교는 어렵지만, 개발과 튜닝을 집 지하실 서버에서 직접 할 수 있었던 만족감이 큼

    • 쿠버네티스 기반 마이크로서비스 덩어리를 쓰면서 개발 속도가 10배 느려지는 경우도 존재
      서버라는 게 1초에 1요청만 처리하는 기계가 아니라는 사실 모르는 경우가 많음
      구글이 하니까 따라간다는 이유만으로 과도한 오버헤드를 지불하는 현실
      나 역시 우리 팀에 잘 먹히는 '모듈러 모놀리식' 아키텍처 관련 글을 써야겠다는 생각

    • 사이드 프로젝트를 집에서 직접 호스팅하려 했지만, 전원 장애, ISP 다운타임, 원격 접근 불가, 하드 드라이브 고장 등 리스크 큼
      결국 내 시간까지 고려하면 경제적 이득 애매
      클라우드 서비스는 규모의 경제 덕을 볼 수 있어서 실제로는 합리적인 선택

    • 굳이 클라우드 말고 호스팅 프로바이더에서 전용 서버 임대도 가능
      물론 대역폭/트래픽 제한 존재
      클라우드가 주류인 이유는 VC, 투자자가 해당 기업의 지분을 갖고 있거나, "무한 트래픽이 터질지 모른다"는 불안감 때문
      클라우드 영업 전문가들은 투자자의 불안을 교묘히 파고듦

    • 꼭 클라우드만 쓰는 건 아님

    • 실제 서비스에서 VM 비용 높은 이유는 고성능 컴퓨트 때문이 아니라, 엄청난 용량의 로컬 디스크가 필요해서임
      계산 능력이 높을 필요는 없고, 20TB짜리 하드 4개와 적당한 CPU만 있어도 대단한 서비스 구상 가능
      클라우드에선 이런 조합 찾기 거의 불가능

  • cgi-bin에서 DB에 접근이 필요한 경우, 매번 프로세스가 DB 커넥션을 새로 생성하는 불편
    메모리 내에서 코드가 동작(fastcgi 등)한다면 단순히 스타트업 시간만 줄이는 게 아니라, DB 커넥션 풀 혹은 스레드당 영구적인 커넥션 유지 가능

    • 대규모로 돌리면 DB 커넥션 수가 너무 많아져서 DB가 힘들어지는 현상
      "python은 싱글스레드라 여러 프로세스, python이 느리니 더 많은 프로세스"라는 이유로 다수 프로세스 운영
      결국엔 python 프로세스 외부에서 shared connection pool(pg bouncer 등)로 분리하고, 다양한 튜닝이 필요
      마지막엔 다스리는 언어(멀티스레드 지원에 성능 더 좋은 언어)로 다시 구현해서 훨씬 단순해졌던 경험

    • 그래서 결국 CGI가 요청 간 정보를 남기는 모델(fastcgi 등)로 발전

    • 전통적으로는 독립 데몬을 띄워서 프록시 역할하게 하기도 했고, 연결도 Unix 소켓을 쓰면 TCP/IP보다 훨씬 효율적

    • UDP 쓰라는 의견

  • 나에게 inetd는 바로 CGI 그 자체
    그 덕에 인터넷이 훨씬 재밌어졌다는 인상
    직접 inetd로 여러 셸 스크립트, 심지어 Bash로만 쓴 HTTP도 돌아갔던 시절
    오래된 VPS, 백업이나 버전 컨트롤 안 했던 랩탑 등은 사라졌지만 재미 있었던 추억
    배포도 Makefile + scp로 단순했고, 테스트도 netcat과 grep으로 작성한 Bash 스크립트로 가능
    정말 살기 좋은 시대 체감

  • hello world 앱을 2400 rps(초당 요청 처리량) 달성한 것이 현재 하드웨어 기준으로 별로라는 인상
    코드가 더 간단해진 것도 아닌데, 대체 뭘 위해 성능을 희생하는지 의문

    • 2000 rps 넘게 처리할 필요 없다면 문제될 게 없음
      그런 트래픽이 필요한 사이트는 아주 소수라는 논지

    • 숫자상으론 높지 않지만, 실제로는 많은 환경에 충분
      HN 개발자들이 이야기하는 '허그 오브 데스'(특정 순간 유입 폭증)도 견딜 수 있을 수준

  • 우리 회사에서는 아직까지도 cgi-bin 디렉터리로 간단한 내부용 웹앱 빠르게 띄우는 방식 사용
    간단하게 쓰면 개발 효율이 매우 좋음
    cgi라도 http/1.0을 직접 print하지 않아도 되고, Python의 wsgiref.handlers.CGIHandler를 사용해서 어떤 wsgi 앱이든 cgi 스크립트로 실행 가능
    Flask 예시 코드도 아래와 같이 간단함

     import wsgiref.handlers, flask
     app = flask.Flask(__name__)
     wsgiref.handlers.CGIHandler().run(app)
    

    실무에서는 uwsgi의 cgi 플러그인으로 스크립트 실행
    Apache나 lighttpd에서 mod_cgi 돌리느니 훨씬 간단하고 유연성 높다는 느낌
    uwsgi는 시스템 단위로 실행되니까 systemd의 하드닝과 샌드박싱 다 쓸 수 있다는 점도 장점
    또, uwsgi의 cgi 핸들링에서는 각 파일 타입마다 인터프리터 지정 가능

     cgi = /cgi-bin=/webapps/cgi-bin/src
     cgi-allowed-ext = .py
     cgi-helper = .py=/webapps/cgi-bin/venv/bin/python3 # all dependencies go here
    

    최초 바이트 전송까지 250~350ms, 우리 용도에는 충분히 허용범위
    uwsgi cgi 관련 문서

    • 좋은 팁 공유
      wsgiref.handlers.CGIHandler가 아직 deprecated 안 된 점, 정보 유용함
  • 어제 논의된 관련 스레드 링크

  • 최근 Apache를 사이드 프로젝트에 쓸 때 .htaccess 기능이 유용해서 썼던 경험
    어느 디렉터리든 .htaccess파일만 놓으면 개별 요청시마다 추가 서버 설정 로드
    htaccess 공식문서
    예전엔 매 요청마다 디스크 접근하는 오버헤드 때문에 성능상 .htaccess를 피하는 게 좋았고, 가급적 메인 설정에 통합하라고 했음
    하지만 요즘은 SSD와 램도 충분하기 때문에, 물론 성능이 아주 약간 손해보긴 하지만, CPU가 웬만큼 좋아져서 대다수는 무시해도 되는 수준
    [내 프로젝트 StaticPatch][https://github.com/StaticPatch/StaticPatch/tree/main]도 이미 적용해 사용 중

    • PHP 창시자 Rasmus Lerdorf의 명언
      "나는 진짜 프로그래머가 아니고, 그냥 돌아가게 만들고 넘어간다. 진짜 프로그래머는 '메모리 누수 심하니까 고쳐야 한다'고 한다. 나는 10번마다 apache를 재시작할 뿐"
      PHP는 그 이후 긴 여정을 거쳐서, 초기 실수 극복하며 크게 발전
      "PHP 8은 내 코드가 적을수록 더 좋아진다"는 말도 남겼다는 일화

    • Apache가 파일 시스템을 감시해서 바뀔 때마다만 읽도록 하면 될 텐데, 왜 매 요청마다 불필요한 디스크 접근을 하게 만드는지 이해불가
      그 결과 99.99%의 http 요청이 느려진다는 점 지적

  • 최근 워크플로우에서 빠른 프로토타이핑에 이런 구조 고민
    JIT 언어들은 fastcgi 형태가 아니면 import가 병목
    직접 썼던 h2o 웹서버는 mruby와 fast-cgi 핸들러 설정 파일이 간단해서 지역 스크립트 작업에 딱
    h2o fastcgi 문서
    또 하나 장점은, 고객이 직접 커스텀 코드를 추가할 수 있도록 로컬 소프트웨어를 확장하게 할 때도 쓰임
    예를 들어 기존엔 확장 위해 MCP를 썼어야 했지만, 이제는 CGI로 구조화된 요청만 구현하면 끝

    • 엔드유저 환경으로 쓸 때 MCP 프론트로 CGI 프로그램 연결도 생각해볼 만한 아이디어
      MCP 서비스도 충분히 CGI로 구현 가능성
      스펙을 더 살펴볼 필요성 느끼는 중

    • fastcgi는 cgi가 가진 장점 거의 다 사라진다는 의문

  • 예전에 C 프로그램과 CGI 조합 직접 사용
    당시에는 100개 넘는 코어나 충분한 램 없었고, 최대 1GB 정도 메모리로도 동작
    그때 가능했던 걸 생각하면, 요즘은 훨씬 쉬울 거라는 확신

    • 1995년 Amazon은 C++ 실행파일을 CGI 방식으로 사용
      부하분산이 필요해진 이후에는 확장에 어려움 있었지만, 그 전까지는 꽤 잘 동작했던 사례
      참고로 프론트와 백오피스 서로 따로 실행파일 2개였음