CSR와 SSR의 차이
도깨비젤리
·2021. 6. 30. 22:19
이번 게시글은 삼성 SW 아카데미 계절학기 수업을 작성자가 재구성하여 포스트 한 글입니다. 포스트에 사용된 예제들은 삼성 SW 아카데미 측에서 준비한 것이며, 작성자 본인이 준비한 것이 아닙니다.
들어가는 말
웹 어플리케이션의 View의 디자인 패턴은 두 가지가 있다.
한개의 페이지를 가진 SPA(Single Page Application)과 여러개의 페이지를 가진 MPA(Multi Page Application)이 그것들이다.
SPA는 클라이언트 측에서 View를 만드는 CSR (Client Side Rendering) 방식을 주로 사용하는 한편, MPA는 서버 측에서 View를 가져오는 SSR(Server Side Rendering) 방식을 주로 사용한다.
SPA 대표 주자로는 Vue와 React등의 FE Framework이 있으며, MPA는 PHP 등의 서버사이드 언어가 있겠다.
어떤 방식을 이용하든 간에, 두 패턴이 제공하는 서비스의 결과물은 같다. 그러나, 그 결과물을 보여주는 방식은 정말 하늘과 땅 차이 수준으로 다르며, 서비스의 종류에 따라 특정 패턴이 열악한 퍼포먼스를 보여줄 수도 있다.
그러므로, 이번 기회에 CSR 방식과 SSR 방식의 차이를 명확히 파악하여 자신이 구현하고자 하는 서비스에 맞는 방식을 택할 수 있는 안목을 길러보도록 하자.
Wire Shark를 통한 네트워크 요청 & 응답을 보며 알아봅시다
먼저 네트워크 packet을 capture 하기 위해 Wire Shark를 설치하자.
Wire Shark는 오픈소스 패킷 분석 프로그램으로 네트워크의 문제, 분석, 소프트웨어 및 통신 프로토콜 개발/ 교육에 사용된다.
WireShark의 기본 작동 원리는 같은 네트워크 구간을 이용하는 두 사용자의 패킷을 수신하여 저장하는 것이다. 이때, PCAP라는 포맷으로 패킷을 저장하는데, 이는 Packet Capture의 약자로 네트워크 트래픽을 캡처하는 API 구성이다. 따라서, WireShark는 네트워크 트래픽을 캡처하는 것이 아니라 운영체제에서 지원하는 캡처 라이브러리를 이용하여 정보를 수집한다는 것을 알 수 있다.
WireShark를 잘 활용한다면, 패킷 분석 프로그램 개발, 패킷 복호화를 통한 해킹, 보안 등을 배울 수 있다고 하는데, 단순히 내 컴퓨터에서 데이터가 잘 나가고 들어가는지 확인하는 정도로만 사용해도 무방하다.
와이어샤크를 처음 실행하면 위와 같은 화면이 뜬다. 와이어샤크가 자동으로 이더넷 인터페이스를 캡처한 것을 확인할 수 있다.
Enter a capture filter에 예제 페이지 주소를 입력하고 11시 방향에 활성화 된 상어 지느러미 버튼을 누르자. 그러면 이제 해당 주소에서 발생하는 packet을 수집하게 된다.
한편, 예제 사이트는 다음과 같이 구성 되어있다.
총 4개의 URL로 구분되어있는데, 해당 페이지가 CSR로 구현되어있는지, SSR로 구현되어있는지를 보여주고, 현재 어떤 페이지에 있는지를 알려준다. 그럼 먼저 server side rendering : page B 로 접속해보자.
접속을 한 순간, WireShark가 패킷 정보를 수신하여 기록하였다. 내가 GET 요청을 보냈고, 그에 대한 응답(파란색 부분) 으로 200 상태코드로 응답했다는 내용이 한 눈에 보인다.
여기서 Line-based text data 응답에서 View가 포함되어있는것 역시 확인할 수 있다.
그럼 이제 page a와 page b를 번갈아 가면서 전환해보자. 그러면 이동할때 마다 아래와 같이 HTTP 요청을 서버에 보내는것을 확인 할 수 있다.
이렇게 SSR 방식은 화면이 바뀔때마다 매번 요청을 보낸다는 것을 확인 할 수 있었다. 그럼 이제 Client side Rendering 페이지로 이동해보자. MPA환경과 다르게 SPA 환경에서는 최초 접속시 모든 JS과 Static 파일을 불러온다. 먼저 이것을 잘 수신하는지 확인하자
MPA 페이지를 접속했을때와 다르게 뭔가 우르르 요청과 응답이 왔다간걸 확인할 수 있다. 또한 js 및 static file의 내용까지 제대로 넘어온 것을 확인할 수 있었다. 그럼 이 상태에서, page A<-> page B로 전환을 반복적으로 시행해보자. 앞서 배운 내용에 따르면, 페이지를 전환해도 어떠한 패킷도 캡처되지 않아야한다.
SSR방식과는 다르게 wire shark에서 http packet이 capture 되지 않음을 확인할 수 있었다.
이를 바탕으로 CSR와 SSR의 특징을 말해보자면 다음과 같다.
1. CSR : Browser (Client)에서 JS에 의해 View (HTML)을 동적으로 생성한다. 때문에 page 전환이 SSR보다 상대적으로 빠르다. 대신 최초 접속 시, 화면 변화를 처리할 JS와 static 파일 (image, css 등)을 로드해야하므로, 최초 접속시 속도는 SSR에 비해 늦다.
2. SSR : Web Server에서 View를 생성한다. page가 전환 될 때 마다, client 가 server에 View 요청을 하고, server는 그것을 생성한 후 client에 보내준다. 그 때문에, View 전환 속도가 CSR에 비해 상대적으로 늦다. page 요청이 빈번해 질 수록 CSR에 비해 서버에 걸리는 부담이 커진다.
즉, 인스타그램 페이지처럼 작은 정보들 ( 좋아요 버튼 클릭할때마다 하트의 색이 바뀌는 것)이 빈번하게 바뀌는 페이지라면 CSR 방식을, 쇼핑몰 처럼 대량의 정보가 한번에 들어오고, 화면 자체가 바뀌지 않는 경우라면 SSR 방식을 채택하는 것이 더 효율적이라고 말할 수 있다.
Wire Shark의 HTTP Packet을 자세히 분석해보자
- GET : HTTP가 어떤 메서드를 가졌는지 나타내는 item
GET은 특정한 리소스를 가져오도록 요청하는 것이다. GET은 데이터를 가져올때만 사용해야한다. 다른 메서드로는 POST,PUT,DELETE,PATCH등이 있다.
- 대표적인 메서드들
- POST : 요청한 자원을 생성 (CREATE)한다. 새로 작성된 리소스인 경우 HTTP 헤더 항목에 Location: URL 주소를 포함하여 응답하여야한다.
- PUT : 요청한 자원을 수정 (UPDATE)한다. 내용 갱신을 위주로 하기 때문에 Location :URL을 보내지 않아도 된다. 클라 측은 요청된 URL을 그대로 사용하는 것으로 간주한다
- DELETE : 요청한 자원을 삭제할 것을 요청한다. 이 메서드는 안전성 문제로 대부분의 서버에서 비활성 되어있다.
- PATCH : PUT과 유사하게 요청된 자원을 수정할때 사용한다. PUT은 자원 전체를 갱신하는 의미가 있는 한편, PATCH는 해당 자원의 일부를 교체하는 의미로 사용한다.
- PUT이 해당 자원의 전체를 교체하는 의미를 지닌 대신, PATCH는 일부를 변경한다는 의미를 지니기 때문에 최근 update 이벤트에서 PUT보다 더 의미적으로 적합하다는 평가를 받고 있지만, 또한 PUT의 경우는 멱등 (연산을 여러번 적용하더라도 결과가 달라지지 않는 성질을 뜻함)하지만, PATCH는 멱등하지 않다. PUT은 전체 자원을 업데이트 하기 때문에 동일 자원에 대해서 동일하게 PUT을 처리하는 경우, 멱등하게 처리된다. 반면 PATCH로 처리되는 경우 자원의 일부가 변경되기 때문에 멱등성을 보장할 수 없다.
2. Host : Host 요청 헤더는 서버의 도메인명과 서버가 리스닝하는 부가적인 TCP 포트를 특정한다. 만약에 포트가 주어지지 않으면 요청된 서버의 기본 포트를 의미한다 (ex. HTTP URL의 경우 "80"). Host 헤더는 모든 HTTP 1.1/ 요청 메시지 내에 포함되어 전송되어야한다. 만약 Host가 없거나 한개 이상의 필드를 포함하는 요청에 대해서는 400(bad request) 상태코드가 전송될 것이다.
- 쉽게 말해서, 요청한 서버의 주인을 알려주는 속성이다.
3. Connection : Connection 일반 헤더는 현재의 전송이 완료된 후, 네트워크 접속을 유지할지 말지를 제어합니다. 만약에 전송된 값이 keep-alive라면 (사진에 나온것 처럼) 연결은 지속되고 끊기지 않으며, 동일한 서버에 대한 후속 요청을 수행할 수 있습니다.
- Keep-alive는 Http의 비연결성 (Connectionless)와 상반되는게 아닌가요???
- 나도 그렇게 생각해서 검색해봤더니, Keep-alive는 http의 비연결성에 의한 단점인 overhead라는 것을 보완하기 위해 사용하는 속성이라고 한다. overhead란 사전적인 의미로는 어떠한 처리를 하기 위해 간접적인 처리 시간 및 메모리등이 추가적으로 사용되는 현상이다. 비연결성에 의하면 서버는 클라이언트를 기억하고 있지 않으므로, 동일한 클라이언트의 요청이라도 매 연결마다 시도/해제의 과정을 거쳐야하기에 연결/ 해제에 대한 불필요한 간접적 시간 (overhead)가 발생하게 된다.
- 이 overhead를 줄이기 위해 KeepAlive 속성을 사용하는 것이다. KeepAlive 속성이 활성화 되어있으면, 지정된 시간 동안 서버와 클라이언트 사이에서 패킷 교환이 없을 경우, 상대방의 안부를 묻기 위해 패킷을 주기적으로 보낸다. 그리고 이 때 패킷에 반응이 없으면 그제서야 접속을 완전히 끊게 되는 것이다.
- 하지만 이것도 완전한 해결책은 아니다. KeepAlive 속성이 On 상태라 해도, 서버가 바쁜 환경에서는 프로세스가 기하급수적으로 늘어나기 때문에 KeepAlive로 상태를 유지하기 위한 메모리 자체를 많이 사용하기 때문이다.
4. Pragma : pragma는 요청-응답 체인에 다양한 영향을 줄 수 있는 구현 관련 헤더이다. mdn에는 브라우저가 캐싱하지 않도록 하는 no-cache 디렉티브 밖에 나와있지 않다. 추가로 Prama: no-cache는 Cache-Control: no-cache와 동일하다고 한다.
5. Cache-Control : Cache-control 일반 헤더 필드는 요청과 응답 내의 캐싱 메커니즘을 위한 디렉티브를 정하기 위해 사용된다. 캐싱 디렉티브는 단방향성이며, 이는 요청 내에 주어진 디렉티브가 응답 내에 주어진 디렉티브와 동일하다는 것을 뜻하지는 않는다.
- 대표적인 Cache 정책들
- max-age : 초 단위로 캐쉬 신선도를 설정. max-age로 설정한 초 만큼 캐쉬가 유지된다
- no-cache : 캐시가 유효한지 확인하기 위해 매번 서버에 요청을 보낸다
- no-store: 캐시를 아예 저장하지 않는다
- no-cache와 no-store의 차이 : no-cache는 캐시를 저장은 하지만, 그 캐시가 유요한지 매번 서버에 질의하는 것. no-store는 아예 캐시를 저장하지 않는것
6. Upgrade-Insecure-Requests: HTTPS:1과 동일한 표현, HTTP 메시지 전송시 보안을 적용한다.
7. User-Agent: 사용자의 웹 브라우저 종류나 기타 클라이언트의 소프트웨어 정보를 보여준다. 위 사진에서는윈도우스를 사용하였고, Webkit이 적용된 브라우저를 사용한것 같다 (아마도 크롬?)
8. Accept : 웹서버로부터 수신되는 데이터 중 웹 브라우저가 처리할 수 있는 데이터 타입을 의미. text/html은 text , html 형태의 문서를 처리할 수 있다는 뜻이고, 마지막의 '*/*' 부분은 모든 문서를 처리 할 수 있다는 뜻이다.
9. Accept-Encoding : 전송할 수 있는 인코딩의 종류 (gzip,delate....)
10. Accept-Language: 전송할 수 있는 언어의 종류
마치며
간단한 예제로 CSR과 SSR의 차이를 알아보았다. 이제 웹 앱을 제작할때, 무턱대고 프레임워크가 제공하는 디자인 패턴을 이용하기 보다는, 한번쯤은 멈춰서 내 서비스에 필요한 패턴을 가져다 쓰는 노력을 해보자.
뿐만 아니라, 막바지에 HTTP packet과 header의 속성을 조금 알아봤는데, 이 역시 FE를 목표로 하는 개발자라면 자세히 알아야할 내용이라 생각한다.
언제나 그렇듯, 공부를 아끼지말고 계속 발전하는 사람이 되도록 하자.
'웹 > 일반' 카테고리의 다른 글
[모던 프론트엔드 구성] 1. npm workspace로 모노레포 구현하기 (2) | 2022.06.06 |
---|---|
[모던 프론트엔드 구성 ] 0. 모노레포의 개념과 구현 방법 (1) | 2022.06.01 |
도와줘요!! Antd DatePicker에서 한글이 안나와요!! (0) | 2022.04.04 |
[Jest 입문] Jest를 왜 사용할까?? && Jest의 Matcher (0) | 2021.08.24 |
Javascript에서 ['10','10','10'].map(parseInt) 가 [1,NaN,3]인 이유 (0) | 2021.08.18 |