제 경험으로 서버 튜닝을 쉽게 설명해보자면 서버가 이상해지는건 결국 사람이 너무 많이 붙었기 때문이죠.. 이걸 가게와 손님이라고 생각하셔도 되요.
서버(가게)가 정상적으로 처리할 수 있는 손님의 숫자가 있는데 만약 이 한계 밖으로 손님을 받게 되면 결국 줄을 세워야 합니다. 문제는 줄을 세워두는 것만으로도 사용되는 리소스가 있다는거죠. 가게 안에다 줄을 세우게 되면 줄을 서 있는 것만으로도 사람들이 공간을 차지하잖아요? 그럼 좁은 가게가 더더욱 좁아지게 되죠.
서버도 마찬가지입니다. 일단 손님 입장을 받게 되면 서버 안에서 줄을 세워야 되요. 그럼 줄을 서있는 손님들은 기본적으로 리소스를 차지하게 됩니다. CPU, 메모리, DB커넥션, 모든걸 소비하죠.

게다가 서버 안에서 줄을 세운다고 설명했지만 사실 이 손님들은 좀 악질? 이라서 가만히 줄만 서있지도 않아요. 끊임없이 자기 요청을 처리해달라고 재촉하죠.

그래서 서버는 [동시에] [모든 손님의 요청을] 조금씩 처리해야만 합니다. 그래서 서버의 처리 효율이 급격하게 떨어지는거죠. 차분하게 하면 1초에 10명씩 손님을 처리할 수 있는데 동시에 30명이 몰려왔어요. 그럼 정신없이 이 손님 봤다 저 손님 봤다 해야하잖아요? 거기서 오는 비효율도 상당합니다. 산수처럼 30명을 3초에 처리 못하는거죠.

그래서 줄을 세울거면 서버 안에서 줄을 세우면 안됩니다. 서버 밖에서 줄을 세워야죠. 즉 서버내 입장 가능 인원 자체를 제약하면 어쩔 수 없이 서버 밖에서 줄을 서게 됩니다.

사실 사용자 입장에선 사람이 많아지면 서버 밖에서 줄을 서나 서버 안에서 줄을 서나 서비스가 느려지는건 별 차이가 없어요. 그렇잖아요? 사람 줄 잔뜩 서있는 가게에 가보셨으면 금방 이해할 듯. 그럼 차라리 가게 밖에 줄서서 가게 사람들이 일하기 편하게 해주는게 좋은거죠.

그래서 기진곰님이 모든 숫자를 타이트하게 줄이라는겁니다.

타이트하게 줄이는 기준은 이거에요. 이 서버가 가장 효율적으로 매끄럽게 동시에 처리할 수 있는 손님의 숫자만큼만 서버 입장을 허락하는거죠. 뭐 그 숫자를 찾는건 경험이 많이 필요하겠지만요.

 

그리고 특히 [프로그램이 죽는 것]에 대해서 설명을 하겠습니다.

죽는다는건 2가지로 정의할 수 있습니다.

1) 프로그램이 돌고는 있는데 반응이 없거나 반응이 이상하다.

2) 프로그램이 완전히 멈췄다. 

그리고 프로그램이 완전히 멈추는건 2가지 경우밖에 없습니다.

2-1) CPU가 잘못된 익스트럭션을 수행한 경우

      (즉 PC값이 이상하게 튀거나 코드 영역의 메모리가 훼손된 경우인데.. 원인은 굉장히 다양)

2-2) CPU가 범위 바깥의 메모리를 억세스 하려는 경우

      (돌려 말하면 메모리가 모자란 경우)

그런데 포인터가 없는 현대식 언어들은 메모리를 직접 제어하는 경우가 없어서

CPU가 잘못된 익스트럭션을 수행하는 일이 없어요.

물론 아파치는 C로 짰지만 오래되었고 거의 완벽하기 때문에 잘못된 인스트럭션을

수행해서 죽으려면 해커가 뭔가 헛점을 찾은게 아니고선 힘들겁니다.

(버퍼 오퍼플로우나 뭐 그런 해커식 공격 방법 말입니다)

 

자 이정도가 프로그램이 죽는다는 것에 대한 정의인데요..

아파치 정도 되는 프로그램은 1)로 죽는 경우도 별로 없습니다.

거의 완벽하게 에러처리를 할테니까요.

그럼 남는건 딱 하나군요? 예 2-2) 메모리가 부족한 경우입니다.

아파치 프로세스가 갑자기 메모리에서 휙 사라졌다? 

메모리가 순간적으로 부족했던겁니다.

그래서 기진곰님이 굉장히 타이트하게

PHP 세션 하나당 200MB나 되는 어마어마한 메모리를 쓴다고 가정하는거지요.

사실 보통 이렇게 많은 메모리를 쓰지는 않습니다.

하지만 메모리를 특별히 많이 쓰는 경우가 있어요.

예를 들면 대용량 파일을 업로드할때 PHP는 그 파일을 받아서

일단 메모리에 보관하는 것 같더군요.

그래서 게시판들이 파일 업로드 한계를 2MB 정도로 낮게 두는겁니다.

만약 이걸 100MB 정도로 늘려놨다면?

(제가 그렇게 해놨어요 흐흐 회원님들이 큰 파일을 업로드를 많이 해서)

그럼 아주 간단하게 서버를 죽여버릴 수 있습니다. 파일 동시 업로드를

한 몇십개 정도만 하면 그 어떤 서버도 메모리 부족을 감당 못하죠.

아니면 이미지 처리는 어떨까요?

보통 썸네일같은걸 만들려면 이미지를 메모리에 풀어놔야 하죠.

그런데 아주 커다란 10000x10000 사이즈 이미지를 업로드하는겁니다.

다만 이 이미지는 그냥 하얀색 단색으로 이뤄져 있어서 파일 사이즈

자체는 2MB도 안되기 때문에 업로드가 허락되었어요.

자 이걸 썸네일을 만들겠다고 저 이미지를 메모리에 RAW로 풀어놓는겁니다.

어마어마한 메모리를 사용하겠군요...

저 이미지를 메모리에 풀면 10000x10000x4 byte 즉 400MB를 씁니다.. 

이런 업로드를 동시에 몇개.. 서버 메모리에 따라서 동시에 몇십개만 하면

세상 그 어떤 서버도 못견딥니다.

아무래도 업로드한 이미지의 사이즈에도 제한이 필요하겠군요...

예 이런 식으로 서버를 죽일 수 있는거지요.

그래서 기진곰님은 동시 접속수자를 엄청 줄여버리는 셋팅을 추천하시는겁니다.

아 이런 극단적인 예가 아니더라도 PHP는 기본적으로 사용하는 메모리가 있어요.

프레임워크 같은거 쓰시면 세션당 필요로 하는 기본 메모리가 더욱 커질텐데

서버 안에서 손님들이 줄을 많이 서게 되면 이 기본 메모리 x 손님 숫자 만으로도

서버 메모리를 넘겨버릴 수 있지요. 

예를 들면 1GB 메모리를 쓰는데 PHP가 하나 뜰때마다 메모리를 한 10MB 정도

땡겨 쓴다고 치고 100명이 동시 접속하면? 메모리 부족해서 죽는거지요..

이게 더 일반적인 케이스일겁니다. 가게로 비유하자면 가게 안이 꽉찰 정도로

손님을 받는 바람에 가게 주인이 더이상 움직일 수조차 없을 정도로 비좁아진겁니다.

저는 원래 임베디드 개발자이고 웹 경험이 짧기 때문에 예로 든 것들중에

좀 실제와 틀린게 있을 수도 있는데 뭐 그런 부분은 그냥 이해 부탁드립니다.

  • profile

    https://xetown.com/qna/942565

    이 글에 제가 달아놓은 댓글을 단비아빠님이 훨씬 쉽게 설명해 주셨네요. *^^* 임베디드 개발자이시라면 제한된 자원을 잘 활용하는 것이 얼마나 중요한지 누구보다 잘 아시겠지요.

     

    php.ini에서 보통 memory_limit을 128M로 해둡니다. 튜닝의 튜 자도 못 들어보신 분이라면 기본값 그대로 쓰고 계실 가능성이 높지만, 가끔 이걸 심하게 늘려놓는 분도 있습니다. 게다가 개인서버라면 아파치(PHP)뿐 아니라 MySQL도 설치되어 있을 가능성이 높고, 심지어 GUI를 돌리는 분도 있습니다. 어떤 상황인지 모르기 때문에 넉넉하게 200M 기준으로 아파치(PHP) 동시 실행 갯수를 제한할 것을 권해 드렸습니다. 님 사이트(가게) 구조가 어떻게 되어 있고 어떤 손님들이 주로 드나드는지 답변자는 알 수 없으니, 덩치 큰 손님도 감안해서 설계해야 언젠가 강호동이 방문했을 때 당황하지 않겠지요?

     

    메모리가 부족하면 swap을 늘리면 되지 않느냐고 하는 분도 종종 봅니다. 그러나 swap은 동시에 실행하는 프로그램 갯수가 비교적 적은 일반 사용자용 PC에서나 도움이 되는 개념입니다. 서버에서는 아주 많은 프로그램이 동시에 실행되기 때문에, 사용하지 않는 프로그램을 swap으로 치워서 메모리를 추가로 확보하기가 어렵습니다. 억지로 swap을 쓰도록 하면 당장 실행중인 프로그램이 swap에 들어가면서 속도가 어마어마하게 느려지는 참사가 발생합니다. 전문용어로 thrashing이라고 하는데요, 이런 상황이 되면 뭐가 문제인지 살펴보기 위해 ssh로 접속하는 것조차 어려워집니다. 서버가 그냥 공황상태에 빠져버려요. 따라서 swap을 사용해서 20명의 손님을 받느니 그냥 10명의 손님만 받는 것이 가게 주인 입장에서나 손님 입장에서나 훨씬 쾌적합니다. 계산대 앞에 줄 설 공간이 없으니 화장실에서 기다려 달라고 하면 손님이 좋아하겠어요? 그냥 가게 밖에 줄을 서는 편이 훨씬 쾌적하지요. 어차피 0.5초만 기다리면 다 입장할 수 있습니다.

     

    그런데 이 일을 오래 하다 보니 MaxRequestWorkers나 PHP-FPM의 pm.max_children처럼 동접수와 관련된 설정을 확 줄이라고 말씀드리면 심리적인 저항이 꽤 크다는 것을 느낍니다. "에이, 그래도..."라며 자기 서버 사양에 맞지 않는 세팅을 고집하시는데요... 이 설정의 의미를 잘못 이해해서 "내 사이트 동접수가 줄어드는 거 아냐?"라고 걱정하기 때문입니다. (제한에 딱 맞춰서 가장 효율적으로 돌아가고 있을 때 하필 pm.max_children을 늘리라는 메시지가 로그에 찍히는 것도 오해를 부추기는 원인 중 하나입니다. 그 말 들으면 안 됩니다.)

     

    서버에서 동접수 관련 설정을 40으로 제한한다는 것은 우리가 흔히 생각하는 동접수 40과는 전혀 다른 의미입니다. 단비아빠님 비유처럼 가게 안에서 줄 서는 사람 수를 40명으로 제한하고, 나머지는 유명 맛집처럼 가게 밖에 줄 세운다는 뜻일 뿐이지요. 안에 줄 서든 밖에 줄 서든 어차피 줄 서서 기다리다가 순차적으로 처리하는 것은 마찬가지이고, 일정 시간 내에 실제로 처리할 수 있는 손님 수는 계산대(CPU) 수에 달려 있습니다. 계산대는 몇 개 없는데 줄을 많이 세워봤자 가게가 좁아질 뿐이지요. 잘 튜닝된 XE/라이믹스 사이트 기준으로 pm.max_children = 40 정도 되면 우리가 흔히 생각하는 동접수 기준으로 3000~6000명을 감당할 수 있는 세팅입니다.

     

    아파치보다 nginx가 좋다고 하는 것은 많은 사람이 줄을 서도 리소스를 별로 잡아먹지 않도록 설계되어 있는 것은 물론, 서버 자원이 많이 필요한 PHP 요청과 아주 간단하게 처리할 수 있는 CSS, JS, 이미지 파일 등의 요청을 각각 다른 줄로 나눠놓고 효율적으로 처리하기 때문입니다. 아파치를 쓰더라도 mpm_prefork보다 mpm_event가 훨씬 빠른데, 그것도 같은 이유고요.

  • profile ?
    심리적 저항이 컷던 사람중 한명입니다. 부끄럽습니다 ㅎㅎ 기진곰님 셋팅에 토 달지 않겠습니다. ㅎㅎ
  • ? profile
    저도 실수할 때가 있으니 뭔가 이상하다 싶으면 토 다셔도 됩니다 ㅎㅎ
  • ?
    와우..두분의 좋은 내용 감동입니다.
  • ?
    추천하고갑니다..
  • ?
    저는 튜닝에 튜, 서버에 서도 잘 모르는 1인으로서

    pm = dynamic
    pm.max_children = 9
    pm.start_servers = 3
    pm.min_spare_servers = 2
    pm.max_spare_servers = 4

    php_admin_value[session.cookie_httponly] = 1

    ;php_flag[display_errors] = off
    ;php_admin_value[error_log] = /var/log/fpm-php.www.log
    ;php_admin_flag[log_errors] = on

    ;php_admin_value[memory_limit] = 128M
    ;php_admin_value[max_execution_time] = 120
    ;php_admin_value[max_input_time] = 300

    ;php_admin_value[post_max_size] = 25M
    ;php_admin_value[upload_max_filesize] = 25M

    지금 제가 쓰는 환경에서 (1코어, 메모리 2048 서버에서)
    이렇게 설정되어있는데 적당한건지 문득 여쭈어 보고 싶습니다.

    늘 건강 행복하세요!~
  • ? profile

    위의 공식대로라면 메모리 2기가 × 5 = 10 내외가 적정값입니다. 9로 되어 있으니 적당합니다.

    물론 사이트 이용패턴에 따라 차이가 있을 수도 있습니다만, 1코어라면 더 조정할 범위가 넓지 않겠네요.

  • profile ?
    감사합니다. 곧 메모리 4기가 변경할 예정인데 그때 20정도로 수정하면 되겠네요~

    좋은 밤되시구요~ 늘 건강 행복하세요!~^^
  • profile
    좋은 정보 감사합니다~!