테스트 | Tester

서비스 폴더 세팅

42에서 제공하는 tester를 실행하세요. 여러분이 세팅해야 할 서비스 디렉토리 및 파일 구조에 대한 설명이 있습니다. 설명을 자세히 읽고, YoupiBanane 폴더를 생성하고 안에 필요한 파일과 폴더들을 만드세요. cgi 프로그램을 세팅해야 하는 경우 제공되는 cgi_tester의 이름을 바꿔서 사용합니다.

Configuration 파일도 안내에 맞게 수정해야 할 것입니다. 여러분의 Configuration 문법이 tester에서 얘기하는 안내를 충족시킬 수 없다면, 문법을 일부 바꿔야 할 수도 있습니다. 저희는 client_limit_body_size를 server 단위에서 설정하도록 했는데, server에서는 default 값을 설정하고 필요시 location 단위에서 오버라이드가 가능하도록 변경해야 했습니다.

주요 에러 케이스

  • bad status code : 여러분이 보낸 응답의 상태코드가 테스터의 기대값과 다릅니다. 다양한 번호로 테스트해보세요.

  • broken pipe : cgi program에게 write할 때 pipe buffer보다 큰 사이즈를 보낼 때 주로 발생합니다.

  • unexpected EOF : chunked encoding에서 size token을 잘못 파싱하거나, 10진수로 변환하는 로직에 에러가 있을 가능성이 높습니다. 결과적으로 100을 보내겠다고 해놓고 사이즈 토큰을 보낸 뒤 70의 데이터만 보냈을 때 주로 발생하는 문제입니다.

  • connection closed by peer : 에러 응답을 리턴하는 경우를 제외하면 client socket을 닫지 않고 재사용하여 다음 리퀘스트를 읽어야 합니다. 그런데 에러 응답을 보내지도 않았고, 클라이언트가 먼저 닫지 않은 상황에서 서버가 닫으면 이 에러가 발생할 수 있습니다. old connection을 제거하는 로직이 있다면 그 부분을 살펴보세요.

  • 서버 프로그램 종료 : 이미 닫힌 client socket fd에 대해 send 함수를 실행하는 경우 정상적인 예외처리 로직을 거치지 않고 서버 프로그램이 즉시 종료됩니다.

  • 정상적인 상황에서의 에러 응답 생성 : 구현방식에 따라 다르겠지만 정상적인 응답을 보내야 할 상황에서 에러 응답이 자꾸 생성된다면, 요청의 시작줄을 읽는 로직을 한 번 살펴보세요. 시작줄을 읽을 때 출력하도록 코드를 추가하면 이전의 버퍼에 남은 내용 때문에 시작줄이 출력되지 않을 확률이 높습니다.

  • PUT 리퀘스트 에러 : 커넥션을 순회하면서 요청을 처리하는 경우, 동시에 같은 파일을 열고 write 작업을 하게 되면 문제가 생길 수 있습니다. put 요청과 같은 경우 한 리퀘스트의 처리가 모두 끝나고, 다른 리퀘스트를 처리할 수 있도록 로직을 설정하면 됩니다.

  • POST 리퀘스트 에러 : POST 테스트 케이스가 자꾸 실패하는 경우 데이터 손실을 가장 먼저 체크하세요. 응답을 클라이언트에게 전송할 때 바디 사이즈를 꼭 체크해보세요. CGI 프로그램의 입력과 출력 크기가 같을 필요는 전혀 없지만, 고맙게도 42에서 제공하는 CGI 테스트 스크립트는 입출력의 사이즈가 같도록 되어 있습니다. CGI 프로그램에 입력를 줄 때, write를 리턴값을 확인하여 실제로 보내진만큼만 보내진 것으로 간주해야 하는데 시도한 것만으로 보내졌다고 간주했을 위험이 있습니다. write가 몇 번 실패하지는 않는지 확인해보는 게 좋습니다.

  • Special Header : 일반적인 CGI 환경변수 이외에도 리퀘스트의 헤더들을 CGI 환경변수로 추가해서 실행해야 하는 경우들이 있습니다. X-Header를 키워드로 검색하여 학습해보세요.

체크 포인트

환경변수에 status_code를 200으로 강제 설정하는 key:value를 넣지 마세요. CGI 자체가 입력에 문제가 있을 경우 error status와 그에 따른 header/body를 출력할 수 있어야 합니다. 환경변수를 제대로 설정했다면 정상적으로 실행됩니다.

tester의 마지막 케이스에서 클라이언트들이 할당받는 fd 값들을 확인하세요. 100을 넘어가는 fd가 있다면 클라이언트 측에서 종료한 커넥션들의 fd가 남아있고, 커넥션 제거 처리가 제대로 안 되었다는 뜻입니다.

타임 플래그를 활용한 속도 개선

여러분의 서버가 얼마나 빠른지 궁금한가요? 혹은 너무 느려서 속도를 개선하고 싶으신가요? 새로운 요청이 감지되는 시점의 시각을 커넥션에 저장하고, 응답 메시지를 완전히 발신 완료할 때까지 타임 플래그를 남겨보세요.

std::string getSpeed(timeval from)
{
    timeval t;
    gettimeofday(&t, NULL);
    return (ft::to_string((t.tv_sec - from.tv_sec) * 1000000 + (t.tv_usec - from.tv_usec)));
}

로그 파일의 이름에 로그 파일의 생성 시각을 남기고 있다면, 마지막 테스트 케이스가 완료되는 시각과 차를 비교하여 전체 테스트 시간을 구해보세요. 5분 이상 걸린다면 저는 비효율적으로 설계된 웹서버라고 생각합니다. 3분 정도가 적정선이라고 생각하고, 그보다 빠르다면 더 좋겠지요.

주로 POST 요청에서 시간이 지연됩니다. 1억 사이즈의 바디를 가지고 요청이 오는 경우 바디를 처리하는 데 시간이 많이 필요합니다. 특히 마지막 케이스는 20개의 클라이언트가 각각 5번의 리퀘스트를 보내는데, 1사이클(20개 리퀘스트의 응답)에 1분 이상 걸린다면 느린 서버입니다.

초 단위는 큰 의미가 없습니다. usecond 단위로 확인해야 합니다. 저희는 요청 메시지 읽기, 요청 처리, CGI 프로그램 실행, 응답 메시지 생성, 응답 메시지 전송을 단계마다 시작/종료 시점에서 플래그를 세워서 확인했습니다. 시간이 많이 소요되는 단계들을 찾아내고, 그 안에서 세부 플래그들을 세워서 시간이 특히 많이 소요되는 구간이나 함수들을 찾아냈습니다.

모든 부분에서 시간적 효율성을 잡으려는 것은 비효율적입니다. 분석해보면 절대적으로 시간을 많이 잡아먹는 오퍼레이션이 반드시 존재합니다. 그런 오퍼레이션을 2~5개 해결하는 것만으로 속도는 확 빨라집니다.

저희는 처음 테스터를 통과했을 때 무려 1시간이 넘게 걸렸습니다. 마지막 테스트 케이스에서 1사이클을 도는 데 15분이 걸렸으니까요. 분석 결과를 토대로 2가지의 큰 변화를 줬습니다. 하나는 CGI 프로그램을 POST 메소드로 실행할 때 요청 메시지의 바디를 버퍼에 저장하지 않고 바로 CGI 프로그램으로 넘겼습니다. 다른 하나는, 버퍼(String 클래스)에서 데이터를 사용한 후 erase하는 것이 아니라 index를 바꾸는 방식으로 erase 작업을 없앴습니다. 그 결과 최종적으로 tester 프로그램 전체를 완료하는데 2분 안쪽으로 줄일 수 있었습니다.

Last updated