42서울/webserv

Webserv 그 길었던 과정의 기록[1편]

뜨거운 개발자 2024. 1. 28. 22:16

시작하면서

웹서브 장엄한 과제 시작 표지

 
42 공통 과정 중 큰 규모와 긴 기간, 난이도로 악명 높은 WebServ 과제를 진행하였습니다.
과제는 공부와 설계 그리고 코드 완성까지 해서, 2023년 6월 26일부터 2023년 8월 21일까지 진행하였습니다.
팀원은 총 4명이서 진행하였고, 진행과정 및 팀 프로젝트 후기에서 어떻게 협업을 진행했는지 다루도록 하겠습니다.
WebServ 과제를 한 단어로 말하면 HTTP1.1로 동작하는 웹서버를 만드는 과제입니다.
과제의 공부했던 내용과 코드에 대한 디테일 한 부분은 추후에 게시물로 가이드 형식으로 남기고 이곳에는 프로젝트를 어떻게 진행했는지와 느낀점을 위주로 쓰는 회고록을 먼저 작성하도록 하겠습니다.

개념공부 시간 (대략 1주~2주)

과제의 주제는 'nginx의 동작을 참고하여 웹서버를 만들기'였습니다. 이에 대한 지식이 전혀 없었던 저는, 과제에 포함된 키워드들을 토대로 부족한 개념들을 공부하게 되었습니다.
이전에 진행했던 과제들은 대체로 과제의 구조를 파악하는 데 큰 어려움이 없었지만, 이번 과제는 많은 생소한 단어와 개념들이 포함되어 있어, 이를 이해하고 감을 잡는 데 상당히 오랜 시간이 소요되었습니다.
개념을 이해하는 데 많은 시간을 할애해야 했던 이유는, 알지 못하는 내용이 너무 많았기 때문입니다. 이에 따라, 저는 약 2주 동안 필요한 개념들을 학습하는 시간을 가졌습니다.
학습 과정에서는 노션을 활용하여 공부한 내용들을 정리하였습니다. 주로 공부한 내용은 HTTP, NGINX(웹서버의 개념), NGINX Config, 소켓 프로그래밍 등이었습니다. 이러한 과정을 통해 웹서버 개발에 필요한 기본적인 개념들을 익히게 되었습니다.

012
공부했던 노션의 내용들..!

팀원들 개념공부 / Nginx config 파싱

초기 협업 방식

저는 자연스럽게 팀장 역할이 되어, 팀원들을 이끄는 형태로 가게 되었습니다.
첫 회의록을 보면 제가 먼저 공부를 한 기간이 2주정도 있기 때문에 팀원들에게 커리큘럼을 짜주는 식으로 진행했습니다.

첫 회의때 제가 짰던 커리큘럼입니다..!

이렇게 첫날 회의록을 보면 제가 짠 커리큘럼 및 앞으로 어떻게 진행할지가 기록되어있습니다.
이렇게 필수적으로 공부해야하는 부분들을 학습하도록 하고, 제가 공부했던 내용들을 발표하고 질문을 받아서, 팀원들이 같이 이해하고 함께 가도록 노력했습니다.

구조 설계 전 config 파일 파싱부터!!

팀원 들이 학습의 시간을 가지는 동안, 저는 config 파일을 파싱하는 코드를 짜고 있었습니다. config 파일 파싱은 규모가 크고 오래걸리지만, 전체 설계를 할 때 큰 부분을 담당 하지는 않기에 제가 먼저 해도 된다고 판단했기 때문입니다.

config파일을 담는 class diagram

위 다이어그램은 제가 생각한 config를 담을 class의 구조 다이어그램입니다. 제 나름의 영혼을 담은 다이어그램입니다. 자랑하고 싶어서 가져와봤어요.
위와 같은 파싱 구조를 정하고, 재귀적으로 nginx conf 파일 파싱의 큰 틀을 완성하고 난 후, 전체적인 구조를 짜기 시작했습니다.
추후에 config 파일에 대해서도 게시글로 다루고, 어떻게 파싱했는지 자랑하도록 하겠습니다.
개인적으로 template 재귀함수로 파싱을 진행해서 뿌듯하게 생각하고 있습니다.
저희 웹서버는 테스터를 기준으로 동작을 정했기 때문에 다음과 같은 구조를 따릅니다.

http {
  server { # php/fastcgi
	listen       80;
	server_name  localhost;
	root         ./var/www/YoupiBanane;
	error_page 404 405 500  /nop/youpi.bad_extension;


    location / {
		autoindex   on;
		error_page 404 405 /nop/youpi.bad_extension;
		limit_except GET {
			deny all;
		}
	}

	location /put_test {
	    upload_store upload_put;
	}

	location /post_body {
        client_max_body_size 100;
        upload_store upload_post;
	}

	location /.bla {
	    cgi_pass /cgi_tester;
	}

	location /.py {
	    cgi_pass /CGI_2.py;
      limit_except GET {
        deny all;
      }
	}

	location /.php {
	    cgi_pass /CGI_1.php;
      limit_except POST {
        deny all;
      }
	}

	location /directory {
	    index          youpi.bad_extension;
	    error_page 404 405 /nop/youpi.bad_extension;
	}

  }
  server {
    listen       8080;
    server_name  domain2.com localhost;
	root         ./var/www;
	error_page 404 502 503 504 /errorPages/404.html;

	location / {
		autoindex    on;
	}

	location /dir {
		root         ./var/www;
	}

    location /dir/ {
		root         ./var/www;
    }

	location /404.html {
		root         ./var/www/errorPages;
	}

	location /fileupload {
		root         ./var/www;
		upload_store upload;
	}

    location /cgi {
      cgi_pass       /cgi_tester;
    }

    location /delete {
        root        ./var/www/upload;
    }

    location /redirecturl {
        limit_except GET POST HEAD {
			deny all;
		}
		autoindex off;
		return 302 https://google.com;
	}
  }
}

실제 nginx config 보다는 적은 기능을 구현하기도 하고, 다른 부분도 있지만 최대한 비슷하게 동작하도록 하려고 하였습니다.

구조 설계

팀원들과 회의를 진행하면서, 어떤 구조가 맞는지에 대해서 논의하면서 얼추 그림을 완성하고 코드를 짜기 시작했습니다. 대략 구조설계는 1주일에서 2주일가량 진행했습니다.

이 다이어그램은 팀원 다같이 회의를 통해서 계속해 만든 구조들입니다. 중반부 구조 설계까지 그린 다이어그램이라, 최종 완성본과는 상이한 부분들이 조금 있지만 큰 틀은 비슷합니다.
이렇게 상세하게 설계를 한다고 했는데도, 사실 코드를 짜다보면 구조를 바꾸는 일이 많이 발생해서, 더욱 더 초기 구조설계의 중요성을 느꼈습니다.

근데 왜 설계부터했어? 코드 빨리 짜야지!

저는 코드부터 작성하는 팀의 경우를 목격한 적이 있습니다. 그런데 그렇게 되면 코드가 복잡해지고, 결국에는 '테이프를 덕지덕지 붙이듯' 코드를 작성하게 되어서 오히려 작업 속도가 더 느려지는 경우가 허다했습니다. 심지어 코드를 완전히 뒤집어서 처음부터 다시 작성하는 경우도 많았습니다.
저는 설계부터 시작하는 것이 협업에 더 유리하다고 생각합니다. 특히 이번 과제처럼 각 파트를 따로 테스트하고 합치는 방식으로 진행할 때는, 처음부터 구조를 잘 짜놓는 것이 효율적입니다. 그렇지 않으면, 각 파트가 합쳐졌을 때 어떤 부분이 잘못되었는지 파악하기 어렵기 때문입니다.
특히 이 과제에서는 non-blocking i/o 방식을 사용하다 보니, 어디까지 입력이 들어왔는지 확인하는 것도 까다로웠습니다. 
다만, 이번 과제에서는 설계를 너무 오래 해서, 코드를 직접 작성하기 시작하는 데까지 시간이 많이 소요되었습니다. 그래서 1차적으로는 상당히 지치는 과제였습니다.
하지만, 큰 틀이 잡힌 후에는 본격적으로 코드를 작성하면서 설계를 실체화하기 시작했습니다. 처음 설계한 대로 완벽하게 만들었다고 말하고 싶지만, 그렇게 말하면 거짓말입니다.
구현하는 과정에서 새로 배운 부분도 많았고, 예상치 못한 부분들도 있었습니다. 그래서 실제로 만들어진 구조는 처음 생각했던 것과는 약간 달랐지만, 전체적인 틀은 유사했습니다.
이후에는 코드를 직접 작성하면서 프로젝트를 완성했습니다. 더 자세한 내용은 추후 웹서버 가이드 게시물에서 다루도록 하겠습니다.
 
 

배운 점(좋았던 점)

1. 설계의 중요성

구조 설계의 중요성이 가장 큰 교훈 중 하나였습니다. 전반적인 구조에 대한 깊은 고민 후, 효율적인 코드를 짤 수 있었던 것 같습니다. 
전체적인 구조 설계에 긴 시간동안 힘을 쏟고 나니, 코드는 상대적으로 짧고 굵게 짤 수 있었던 것 같습니다. 코드는 약 디버깅 기간을 포함해서, 약 4주 정도 걸려, 완성한 것 같습니다.
구조를 어떻게 잘 설계할 수 있는지, 다이어그램을 통해 구체화하고, 미리 문제를 예측하여 올바른 구조를 설정하는 과정이 귀중한 경험으로 남았습니다.
그리고 이 과정을 통해 팀원과 어떻게 효과적으로 협업할 수 있는지도 배웠습니다.

2. 이벤트 기반 프로그래밍

이벤트 기반 프로그래밍에서는 코드가 완성된 이후에도 오류를 발견할 수 있는 부분이 많았습니다. 이는 프로그램이 순차적으로 실행되는 것이 아니라, 이벤트 발생에 따라 코드가 구성되기 때문입니다.
HTTP 이벤트뿐만 아니라 kqueue가 반환하는 readable 이벤트와 writable 이벤트까지 고려하며, 예상치 못한 결과가 나오는 경우 어느 부분에서 에러가 발생하는지 확인해야 했습니다. 이 과정에서 꼼꼼한 테스팅의 중요성을 깨달았습니다.
어느 정도 코드가 완성되고 나서야 코드의 오류를 발견 할 수 있는 부분이 많았습니다.

3. 새로운 협업 방식

협업 방식의 변화도 중요한 학습 경험이었습니다. 이전에는 대부분의 코드를 혼자 빠르게 작성하는 경우가 많아, git을 주로 제 방식대로 사용했습니다. 이번 프로젝트를 통해 git을 활용한 협업이 부족하다는 것을 인지하고, branch 관리법, commit message convention, pull request template를 만들어 이를 준수하며 협업을 진행했습니다.
또한, 구글 cpp convention을 참고하여 필요한 부분들을 팀원들과 협의해 code convention을 설정하였습니다. 이로 인해 코드는 통일성을 가지며, 깔끔하게 작성되었습니다.

01
code convetion 이전에 구글 cpp 가이드를 번역해둔걸 활용해서 핵심만 정해서 컨벤션을 정했습니다.

코드를 합치는 과정에서는 live share를 사용해 팀원과 함께 코드를 작성하였습니다. 특히 휴일에 슬랙 콜과 live share로 함께 작업한 경험은 이것이 정말 이상적인 프로젝트라는 생각이 들었습니다.
협업의 편의성을 위해 작성한 독시젠 타입 주석도 매우 유익했습니다. 이는 프로젝트 후반부에는 다소 간소화되었지만, 여전히 중요한 역할을 했습니다.

01
독시젠 타입 사용해서 코드 설명을 적어 줌

아쉬운 점

1. 문제 해결 기록의 부재

개발 과정에서 다양한 문제들을 해결했으나, 이를 기록하지 못했습니다. 예를 들어, 파이프의 버퍼 크기 이상의 입력이 들어오는 경우 표준 출력이 나가지 않는 문제, listen 함수의 연결 대기 큐 크기 문제 등 다양한 이슈들이 있었습니다. 
이런 문제들을 겪을 때마다 시간을 내서, 기록을 남겼다면, 같은 문제를 겪는 개발자들이나, 나 자신에게 좋은 자료가 되었을텐데, 기한을 너무 타이트하게 잡아 기록할 시간이 부족했습니다. 이는 깊이 있는 학습과 지식 공유의 기회를 놓친 것으로 아쉽습니다.
다음부터는 간단하게 짧게라도 기록하도록 노력해야겠습니다.
또한, 구조가 제가 생각한 것과 다를 때, 그 구조를 바꾸고, 다이어그램에 추가할 여유가 없이 역시 빠르게 완성을 위해 더 달려버렸습니다. 이 점은 나중에 다른 사람들에게 설명할 때, 조금 더 아쉬웠습니다.

2. 협업 간 팀장의 역할

일단 협업은 Working Time을 정했다. 노션에 초기에 이렇게 적어뒀습니다.

규칙 표

규칙이 이렇게 명시되어있었지만, 이것을 엄격하게 이행하지도 않고 지각비는 아에 걷지 않았습니다.
다음 프로젝트에서는 규칙을 정하면 반드시 즉시 이행해야겠다고 생각했습니다. 팀적 분위기에 큰 영향을 준다는 것을 알았습니다.
팀원들이 늦어서 그렇다라는 말은 아니고, 전체적인 분위기가 조금 더 진지하게 팀원들에 참여를 독려할 수 있는 부분이 많았는데, 그렇지 못했던 것 같아서 아쉬움이 있었습니다.
여기서 팀장은 좋은 분위기를 유지하는 것도 좋지만, 팀의 분위기를 잡는 역할까지 해야 한다는 점을 배웠습니다.

3. 투자 가능 시간의 차이

위에 보이듯, working Time이 되게 점심 지나서 시작하는다는 것을 볼 수 있습니다.
초기 목표가 방학동안 2개의 팀 프로젝트를 끝내는 것이었습니다. 그렇기에 6월 말부터 7월 말까지, 한달 만에 끝내고자 목표하였기에 그래서 저는 아침부터 저녁까지 모두 함께 코드를 짜고 싶었습니다.
팀원 모두 함께 일찍 와서 코드를 짰으면 좋았겠지만, 아쉽게도 그렇지 못했습니다.
집이 먼 팀원, 오전에 일정이 있는 팀원도 있고 등등 다양한 이유로 아침부터 저녁까지 하는 규칙은 생기지 못했습니다.
하지만, 저는 매일 아침 10시까지 학습장소에 가는 챌린지에 참여하고 있었기도하고, 다른 취미를 가지기 보다는, 빠르게 과제를 끝내고 싶은 마음이 가득했습니다.
그래서 팀원들보다 대략 하루에 3시간~4시간정도 먼저 과제를 진행하고 있었고, 심지어 주말에도 계속 코드를 짜서, 이는 팀원들에게 부담을 주었습니다.  

012
카드를 놓고온 날도 몇번 있었다는.. 그만큼 열정가득 이었습니다

4. 팀원 간 진도 격차 발생

과제를 시작하기 전, 팀원들보다 종강이 빠르기도 했고, 팀원들 보다 진도가 빨라, 팀원들을 기다리는 시간이 있었기에 혼자서 개념을 학습하고 코드를 짜는 시간이 대략 2주 정도 있었습니다.
그래서 필자가 먼저 공부를 하고 설계를 한 후 팀원들과 공유하며, 부족한 부분을 보완하는 방향으로 진행하였습니다.
시작할 때부터 대략 2주 정도 먼저 공부했던 것도 있었고,위에서 말했듯, 평일에 약 3~4시간 정도 더 코드를 쳤고, 주말에도 계속해서 코드를 짰기에, 팀원과 템포가 다를 수 밖에 없었습니다.
빠르게 과제를 완성하겠다는 측면에서는 좋은 방법이었을지 모르지만, 팀원들 입장에서도, 제 입장에서도 협업 측면에서는 좋지 못한 방법이었습니다.
왜냐하면 주말에 코드가 많이 진행 된 경우 팀원들은 그것을 이해하는데 시간을 소비 해야 했습니다.
상세하게 주석을 쓰고 pull request를 상세하게 작성한다고 해도, 작업량이 너무 많았고, 그 점이 함께 진행하는 팀원들에겐 부담이 되었을 것 같습니다.
계속해서 팀원 간의 진도 격차가 벌어진 부분과 빠른 템포에 의해서, 팀원 한 명은 중반부터는 아에 참여를 하지 못할 정도로 간격이 벌어져 버렸습니다.
그때, 조금 더 팀원을 챙겼어야 하는데, 효율적인 완성을 위해선 그냥 제가 진행하는 것이 더 빠르다고 판단해서 팀원을 방치하였던 것도 있었습니다.
지금 생각해보면 팀원들과 속도를 맞추고, 남는 시간에 다른 것을 하는 것이 서로에게 더 좋은 방향이지 않았을까 하는 아쉬움이 남아있습니다.

5. 팀원과 갈등 발생

위의 폭주하며 코드를 짜던 기간 이전에, 구조를 짜면서 한명의 팀원과 의견의 격차로 갈등도 발생하기도 했습니다.
팀원은 저의 설계의 근거를 요구하며,저의 설계를 GPT에 넣으면서 확인하는 모습에 저는 GPT가 틀렸음을 증명해야만 했습니다. 팀원은 저의 의견을 신뢰하지 못하였고, 저는 신뢰받는 팀장이 되기 위해서 계속해서 증명해야만 했습니다.
빠르게 프로젝트를 완성하고 싶었지만, 생산성 없는 부분에서 끊임없이 갈등이 발생하니 스트레스를 많이 받았습니다. 제가 이 프로젝트에 가장 열심히 임하고 있음에도 불구하고, 억울함을 느꼈습니다.
그 무렵, 저는 너무 힘들어서 한참을 울며 다른 사람에게 상황을 털어놓았습니다. 그 전까지는 제가 공부한 내용을 팀원에게 설명하려고 했고, 제가 작성한 모든 부분을 팀원에게 이해시키려고 노력했습니다.
하지만 팀원과의 갈등이 계속해서 해결되지 않자, 저는 결국 제가 정한 구조를 강행하고, 모든 것을 이해시키는 것을 포기하게 되었습니다. 신뢰하지 않는 팀원에게 모든 것을 이해시키는 것은 불가능하다는 것을 깨달았고, 이는 매우 비생산적이라고 생각했습니다.
결국, 팀원들에게 역할을 분배하고 프로젝트를 계속 진행했습니다. 그러나 한 팀원과의 불화는 프로젝트가 완성될 때까지 계속되었습니다.
결국, 프로젝트를 완성하는 데는 성공했지만, 팀장으로서는 아쉬움이 남았습니다. 이 경험을 통해 저는 아직 많이 부족한 팀장임을 다시 한번 깨달았습니다.

6. 미숙한 협업 방식

commit을 기능별로 쪼개서 하는게 익숙치 않다보니, 또한 위에서 말했듯, 작업량이 많았기에, 팀원들 입장에서는 코드 리뷰를 하는 것이 쉽지 않았을 것입니다.
작업량이 엄청 많은데, commit convetion을 지키려고 노력을 하긴 했지만, 리뷰하는 사람의 입장을 고려해서 commit을 하는 능력이 부족해서, pull request 또한 항상 그랬던 건 아니지만 저렇게 규모가 크면.. 지금 보니까 아찔합니다..

이렇게 큰 풀리퀘라니,,, 참 리뷰어들을 배려하지 못한 풀리퀘였습니다,...

회고를 마치며..

이것저것 쓰다보니 힘들었지만, 정말 배운것이 많은 과제였습니다.
많은 우여곡절도 있었고, 정말로 살면서 이정도로 열심히 한 프로젝트가 있을까 싶을 정도로 자신을 갈아넣었습니다.
그로 인해서 많은 성장을 했고, 많이 부족한 팀장을 따라서 할일을 묵묵히 해준 팀원들에게도 너무나도 고마웠습니다.
참 많은 아쉬움도 있고, 과제 외적으로도 많은걸 느꼈던 프로젝트였습니다.
다음에는 웹서브에서 무엇을 공부했는지 공부했던 내용들을 좀 더 풀어서 정리하는 게시글로 돌아오겠습니다.!
긴 회고록인데도 읽어주셔서 감사합니다!! 
마지막으로 보너스까지 끝낸 웹서브 통과 사진으로 글을 마치겠습니다. 
[마지막 사진에서 팀원이 2명인 이유는 4명이서 진행해서, 2명씩 쪼개서 평가를 받았기 때문입니다!] 

728x90