-
웹프록시 구현 및 HTTP 캐싱 공부 일지SWJungle 2023. 9. 20. 15:28
9월 20일 수요일
09:00 ~ 10:50 기상 및 공부법 영상 시청
10:50 ~ 11:00 영양제 섭취, 수분보충, 영양보충
11:00 ~ 11:10 하루 계획
오늘 해야할 것들
thread 방식으로 동시성 프록시서버 구현클라이언트 요청마다 쓰레드 생성쓰레드 풀 적용- 캐싱기능 적용
- 12장 완독
11:10 ~ 11:21 쓰레드풀 기반 웹프록시 코딩 중
착각한 것들
- 요청이 올 때마다 쓰레드를 생성하는 방식에서 connfd를 동적할당받아 사용했었는데, 쓰레드풀 기반은 이럴 필요 없다. 왜? sbuf를 사용해서 mutual exclusive를 구현하기 때문
- 따라서 처음에 고민했었던, Pthread_create의 subroutine에 전달될 인자에 뭘 넣어줘야 할지는 고민할 필요 없이 NULL을 넣어주면 되었었다.
- 이렇게 쓰레드풀 크기만큼 미리 쓰레드를 만들어 집어넣고, 사용자 요청이 들어오면 해당 요청fd를 sbuf에 집어넣는다. 이때, sbuf는 모든 쓰레드가 공유하는 변수인 shared variable이기 때문에 전역변수로 미리 선언해주고 main에서 초기화해준다.
- 서브쓰레드는 create되는 순간 subroutine을 수행하는데, 여기서 무한루프를 돌면서 요청이 올 때까지 sbuf_remove()에서 block된다.
- 요청이 오면 요청을 처리하고 connfd를 닫는다.
11:21 ~ 11:42 쓰레드풀 기반 동시성 웹프록시 구현
전시간에 나온 문제 보완 완료.
하지만 driver 돌렸을 때 timeout 발생 → nop-server의 파이썬 버전을 3으로 변경 후 해결
테스트 결과 → 기본 sequential 점수: 32점, concurrent: 15점
sequential: 40 → 32점으로 됨. sbuf의 크기 문제인가 싶어서 sbuf 사이즈를 16 → 50, 쓰레드 개수를 4 → 10으로 바꿈
결과
- sbuf사이즈:16, 스레드개수:10 → 40점
- sbuf사이즈:50, 스레드개수:4 → 32점
재 테스트 결과 40점 나옴
이제 다시 sbuf는 그대로 하고, 쓰레드 개수만 증가시켜보고, 쓰레드 개수는 그대로 하고 sbuf만 증가시켜볼 예정
→ sbuf사이즈에 관계없이 쓰레드의 개수만 점수에 영향을 미쳤음
11:45 ~ 12:24 점심밥 및 양치
12:24 ~ 14:46 브라우저 캐싱기법 공부
https://web.dev/i18n/ko/http-cache/9단계 기법 사용해서 위 포스팅 읽기
1. 본문을 읽기 전(집중, 조감, 질문)
집중
환경 세팅 완: 강의실 조용함. 마인드 세팅 완료
목적: HTTP Cache에 대해서 이해하는 것
조감 및 질문
- 브라우저 호환성: 브라우저별로 호환이 되나 안되나가 정해져 있나보다
- HTTP 캐시 작동 방식: 캐시의 작동방식은 어떨까? 일단 사용자 요청라인과 헤더를 기반으로 캐싱된 데이터를 줄 지 서버까지 갈지를 판단하겠지. 이 캐시데이터는 얼마동안 보관되어야 할까? 어떻게 판단할 수 있을까?
- 요청 헤더 - 기본값 유지(일반): 기본값을 유지하는 캐싱?
- 응답 헤더 - 웹 서버 구성: 캐싱된 컨텐츠를 보낼시 응답헤더에는 어떤게 들어가야할지?
- 어떤 응답 헤더값을 사용해야 할까요?
- 버전 지정된 URL(: #versioned-urls)에 대한 장기 캐싱: url에 버전이 지정되었다? 일단 캐싱을 한 후에 버전을 붙이고 나중에 컨텐츠가 바뀌면 버전 갱신?
- 버전 없는 URL: (: #unversioned-urls)에 대한 서버 재검증: 버전이 없으면 캐시된 데이터가 서버의 최신 데이터와 같은지 검증을 해야한다?
2. 본문을 읽으면서(도해, 통합, 결합)
도해 및 통합
캐싱이 필요한 이유
- 네트워크를 통해 리소스를 가져오는 것은 느리고 비용이 많이 드는 일
- 사용성 문제: 중요한 리소스가 모두 다운로드될 때까지 페이지 로드 안됨
- 사용자 네트워크가 느릴 때, 모든 요청을 처리해야 하면 시간 많이걸림(비용)
불필요한 네트워크 요청 피하는 법(질문: 불필요한 네트워크 요청이 뭘까?)
- 브라우저의 HTTP 캐시: 1차 방어선
- 질문: 그럼 2차, 3차 방어선은 뭘까? 밑에 답변
- 브라우저의 HTTP 캐시 (Browser Cache): 브라우저는 이미 다운로드한 웹 페이지나 리소스를 로컬 저장소에 저장하여, 동일한 웹 페이지를 방문할 때 재요청하지 않고 캐시된 데이터를 사용합니다. 이것이 1차 방어선입니다. 브라우저 캐시를 통해 불필요한 다운로드를 방지하고 페이지 로딩을 빠르게 할 수 있습니다.
- 프록시 캐시 (Proxy Cache): 중간 프록시 서버나 CDN(Content Delivery Network)는 브라우저와 웹 서버 사이에 위치하며, 요청된 웹 페이지나 리소스를 캐시하여 클라이언트에게 제공합니다. 프록시 캐시는 웹 서버로의 요청을 줄여주고, 네트워크 대역폭을 절약합니다. 이것이 2차 방어선입니다.
- 서버 캐시 (Server Cache): 웹 서버 자체도 HTTP 응답을 캐시할 수 있습니다. 이를 통해 동일한 요청에 대한 응답을 재생산하지 않고 저장된 캐시를 반환할 수 있습니다. 서버 캐시는 웹 서버의 부하를 줄여주고 빠른 응답을 제공합니다. 이것이 3차 방어선입니다.
- 콘텐츠 제공자의 캐시: 콘텐츠 제공자(CDN 등)는 전 세계에 분산된 서버와 캐시를 사용하여 콘텐츠를 효율적으로 전달합니다. 클라이언트가 콘텐츠 제공자의 서버에 요청할 때, 캐시된 콘텐츠를 받아볼 수 있습니다.
캐시된 응답의 수명 제한
캐시를 나타내는 요청헤더들
- Cache-Control
- ETag
- Last-Modified
HTTP 캐시 작동 방식
- 브라우저 캐시로 라우팅되어 요청을 수행하는 데 사용할 수 있는 유효한 캐시 응답이 있는지 확인
- 질문1: 브라우저 캐시로 라우팅된다는게 뭘까?
- 답: 브라우저가 요청을 서버에 보내기 전에 우선 자기 안에 있는 브라우저 캐시에 요청에 대해 캐시된 응답이 있는지 확인한다는 뜻
- 질문2: 브라우저 캐시에 있으면 왜 네트워크 대기 시간과 전송비용도 제거되는지? 아하 내 예상은 브라우저 내에 브라우저 캐시가 있어서 웹서버에 요청이 가기 전에 판단하는 것 같다.
- 질문3: 브라우저 캐시는 어디에 위치해 있는가?
- 답:
- Windows 운영 체제:
- Google Chrome:
C:\Users\<사용자 이름>\AppData\Local\Google\Chrome\User Data\Default\Cache
- Mozilla Firefox:
C:\Users\<사용자 이름>\AppData\Local\Mozilla\Firefox\Profiles\<프로필 이름>\cache2
- Microsoft Edge:
C:\Users\<사용자 이름>\AppData\Local\Packages\Microsoft.MicrosoftEdge_<랜덤 문자열>\LocalState\Cache
- macOS 운영 체제:
- Google Chrome:
~/Library/Caches/Google/Chrome/Default/Cache
- Safari:
~/Library/Caches/com.apple.Safari/Cache.db
- Linux 운영 체제:
- Google Chrome:
~/.cache/google-chrome/Default/Cache
- Mozilla Firefox:
~/.cache/mozilla/firefox/<프로필 이름>/cache2
캐싱여부 확인에 필요한 요청 헤더
- If-None-Match, If-Modified-Since 헤더 - 새로고침 관련
- 새로고침시 브라우저는 If-None-Match, If-Modified-Since 헤더를 요청에 포함해서 서버에 요청보냄
- 질문: 페이지를 새로고침 하는 것과, url을 현재 페이지랑 똑같이 적고 엔터를 누르는 것의 차이?
- 답: 일반적으로 동일한 작업임. 다만 명시적으로 새로고침 작업을 수행하는 것과 URL을 입력하여 이동한다는 점.
캐싱 헤더
- Cache-Control: response header에 들어가고, 캐싱 방법 및 기간 지정 가능
- ETag: 브라우저가 캐싱한 데이터가 만료되었나 안되었나를 확인할 때 사용하는 해시된 데이터
ETag 작동방식 - 질문1: 설명에서 브라우저는 컨텐츠를 해시한 값을 서버에 보낸다는데, 요청 헤더에 Etag헤더가 없다. 쿠키로 보내는건가?
- 답: 요청헤더의 If-None-Match에 기존에 캐시된 데이터의 해시값이 들어있었다. 그리고 이 헤더 위에 If-Modified-Since가 있는데, 날짜가 있는 걸로 보아 캐싱된 날짜를 저장한 것으로 보인다. 서버는 이 헤더의 값을 읽어서 자기가 가지고 ETag값을 비교함
- 같으면 304 Not Modified, response body 없음 → 브라우저 캐시에서 가져옴
상태코드, ETag, If-None-Match - 질문2: If-Modified-Since와 If-None-Match가 둘 다 헤더에 들어있는 경우, 어떤 값으로 캐시 사용 여부를 판단할까?
- 예상: ETag 값이 들어있는 If-None-Match
- 답: If-None-Match는 리소스의 해시값이기 때문에 서버측 값과 대조해서 가장 정확하게 리소스 업데이트 유무를 판단할 수 있다.
- If-Modified-Since는 파일 최종 수정시간을 기준으로 판단하고, 클라이언트에서 서로 다른 시간대를 사용하거나 시간정보가 없으면 문제 발생
- 질문3: 브라우저는 서버에게 요청을 보낼 때, 요청헤더의 If-None-Match에 받을 컨텐츠의 Etag를 넣어주는데, 이건 어디서 받아서 적어주는건가? 브라우저 캐시에 (키:컨텐츠url, 값:ETag) 이런식으로 저장되는건가?
- Last-Modified: 원본 서버 리소스가 마지막으로 수정된 날짜,시간
- → ETag보단 부정확. 근데 왜사용? → 대비책임
Cache-Control 응답헤더
- 사용예: Cache-Control: max-age=31536000
- 생략해도 HTTP 캐싱 비활성화 안됨. 왜? → 브라우저는 heuristic caching 지원함(알고리즘 써서 캐싱할지 말지 예측. 알고리즘 공개안됨)
특정 파일의 캐시 max-age가 1년인데 파일 업데이트가 발생되어서 모두 반영을 해줘야 하는 상황
- versioned-urls인 경우
리소스 URL을 번경하고 사용자가 파일 변경될 때마다 새로운 응답 다운로드 하도록 해야함
→ style.x234dff.css 같이 버전정보를 내장해서 수행
질문: 버전이 url에 들어가는건 알겠는데 그래서 어떻게 갱신?
답
- 초기 URL:
http://example.com/style.css
- 업데이트된 URL:
http://example.com/style-v2.css
- 이런식으로 요청 자체가 다르게 보내지기 때문에 기존의 캐시를 무시하게됨
- unversioned-urls인 경우
- 서비스 워커, Cache Storage API 사용
- 서비스 워커: 브라우저 요청을 가로채서 로직 수행
- Cache Storage API: 캐시 제어 기능 제공
- 깊게 안 볼 내용
Cache-Control 값들
- no-cache: 캐시된 버전 사용 전, 서버에 매번 캐시 유효성을 확인해야 한다고 알리기
- no-store: 브라우저 및 중간캐시(CDN)에 어떤 파일 버전도 저장하지 않도록 지시
- 질문1: no-store은 결국 캐시를 저장하지 않는다는건데, 그럼 그냥 Cache-Control 없애면 되지않나?
- 내 예상: 없애버리면 브라우저가 자체적으로 휴리스틱 캐싱을 하기 때문에 안한다고 명시해줘야 한다.
- 답:
no-store
지시어는 브라우저와 중간 캐시(CDN)에게 해당 리소스를 저장하지 말라는 엄격한 지시를 내립니다. 반면에Cache-Control
헤더를 생략하면 브라우저는 자체적으로 휴리스틱 캐싱 규칙을 사용하게 됩니다. 이러한 규칙은 리소스의 특성 및 서버 응답에서 제공되는 헤더를 기반으로 캐싱 동작을 조절합니다.- 질문2: 중간캐시가 정확히 뭔가? 브라우저와 서버 사이에 있는 서버들?
- 답:
중간 캐시는 브라우저와 웹 서버 간의 네트워크 경로에 있는 캐싱을 수행하는 서버 또는 프록시 서버입니다. 이러한 중간 캐시 서버는 클라이언트(브라우저)와 원격 서버(웹 서버) 간의 트래픽을 관리하고, 웹 리소스(예: 웹 페이지, 이미지, 스타일 시트 등)를 캐시하여 브라우저 요청에 대한 응답 시간을 최적화하는 역할을 합니다.
- private: 브라우저는 캐싱 가능, 중간캐시는 캐싱불가
- 질문1: 중간캐시가 여러개일 때, 모든 중간캐시는 캐싱이 불가능한가?
- 답:
private
로 표시된 리소스는 모든 중간 캐시 서버에 대해 캐싱이 불가능하며, 브라우저만 캐싱하여 개인적인 목적으로 사용합니다. 요청이 중간캐시를 거쳐갈 때, Cache-Control값을 확인하고 응답을 넘김 - public: 응답이 모든 캐시서버에 의해 저장될 수 있음
결합
패스
3. 본문을 읽은 후(확인, 평가, 교정)
확인
클라이언트가 서버에게 똑같은 요청을 100번 했을 때, 서버는 100번 응답 바디에 컨텐츠를 담아서 준다.
그런데 사람들은 생각했다. 어차피 서버는 계속 같은 응답을 주는데 왜 계속 클라이언트에게 주느라 비용낭비를 하고, 왜 클라이언트는 이전에 똑같은걸 받았는데 다시 달라고 하는건가?
그래서 캐시가 나왔다.
캐시를 클라이언트-서버 관계에 적용하면, 클라이언트가 서버에게 받은 응답을 브라우저(클라이언트) 캐시에 저장해 놓는다. 보통 브라우저캐시는 사용자 컴퓨터 디스크이다. 이후, 같은 요청이 들어올 때, 브라우저 캐시 저장소를 뒤져서 이전에 캐시된게 있는지 찾고, 찾았으면 이제 그 캐시 내에 적혀있는 ETag값 등 캐시 타당성을 체크할 값들을 요청헤더에 담아서 서버에 넘긴다.
이후 서버는 ETag 등을 자기가 가지고 있는 ETag값과 대조한 후, 같으면 브라우저가 최신 정보를 가지고 있다는 뜻이므로 응답 바디를 주지 않고 응답 헤더만 보낸다. 헤더의 상대코드는 304 Not Modified로, 리소스에 변경이 이루어지지 않았음을 브라우저에게 알려주기 위함이다. 브라우저는 상태코드 304를 읽어들이면 자기가 가지고 있는 캐시된 컨텐츠를 사용한다.
서버측에서는 응답바디를 채워줄 필요가 없어서 빠르게 응답을 보내줄 수 있고,그 결과 브라우저는 빠르게 로컬 캐시저장소에서 데이터를 받아 화면을 렌더링해줄 수 있다.
평가
캐시가 나온 인과관계를 지어냄
캐시의 작동방식을 설명함
브라우저가 캐시된 데이터를 사용해도 되는지 파악할 수 있는 방법을 설명함
캐시의 효과를 설명함
확인 파트의 부족한 점
- Cache-Control 설명 없음, versioned, unversioned url 설명 없음 → 귀찮아서 안함 -> 귀찮음 버리기
교정
Cache-Control 헤더는 서버의 응답 헤더에 들어가는거고, 캐시에 대한 여러 설정을 해줄 수 있는 헤더다.
브라우저-웹서버간 네트워크에 있는 서버들은 저 헤더에 적힌 속성들을 읽고 이에 맞게 처리를 해야하는 규약이 있다.
max-age는 캐시의 수명을 결정해준다.
no-cache는 일단 무조건 서버에 요청이 가서 캐시 타당성을 검증해야 한다는 거다.
no-store은 명시적으로 모든 거쳐가는 서버들 및 브라우저에게 리소스를 캐싱하지 말라고 알려주는거다.
no-store이랑 Cache-Control을 아예 안쓰는거랑 뭔 차이인가? → 브라우저는 heuristic freshment를 지원해서 자체 알고리즘 기반으로 응답 리소스에 대해 캐싱할지 말지를 결정하는게 디폴트다. 이때, no-store을 적어주면 저 기능을 꺼버리면서 아예 캐싱이 일어나지 않는다.
→ 중간 서버? 중간 캐시만 캐시 안되게 막는 설정이 있었는데 기억이 안난다.
- private이 중간캐시 캐싱 막는거
- public은 중간캐시 캐싱 가능
versioned, unversioned url
문제상황: 리소스A의 캐시 수명이 1년인데 리소스A가 업데이트되면서 이 변경사항을 모든 사용자에게 반영시켜야 한다.
- versioned: 파일 자체에 파일의 버전을 써넣으면 버전 업데이트가 일어난 경우, 해당 파일 요청시 url 자체가 바뀌기 때문에 기존의 캐시를 무시하고 새로운 요청을 보낸다.
- unversioned: versioned가 아니라서 어쩔 수 없이 문제가 방치된다. 이걸 해결하기 위해 서비스워커와 Cache API가 등장했다.
총평
시간 오래걸림(2시간 이상), 결합법 적용 안함(미숙), 질문 많이 생각 안남
노션 -> 티스토리 스타일 안망가지게 옮기는 법 찾기 -> 없음. 노션에서 복붙 후 마크다운 수정하는 방식으로 하기
14:46 ~ 15:25 블로그 글 작성완료
15:32 ~ 18:00 캐시 적용중
일단 pdf 읽어보기
Part 3: Caching web objects
실제 HTTP는 복잡한 캐시 모델이 있다.
- 어떻게 캐시되는지,
- 캐시된 리소스를 쓸지 새로 받을지 등 고려할게 많다.
우리는 간단한 접근을 하겠다.
- 프록시 서버가 서버로부터 object를 받으면, 클라이언트에 응답 보내면서 일단 무조건 프록시서버 메모리에 캐시한다.
- 이후, 다른 클라이언트가 같은 object 요청하면, 프록시는 서버와 연결하지 말고 바로 캐시된 object를 준다.
- 제약조건
- 무제한의 object를 캐시할 수 없다.
- 엄청 큰 object를 캐시하고 메모리 공간이 없어서 다른 object를 캐시하지 못하는 문제가 발생한다.
- 최대 캐시 사이즈: 1MB
- 캐시 사이즈를 계산할 때, 실제 웹 object 크기만 고려함.
- 최대 object 사이즈: 100KB
- 100KB를 넘는 object는 캐시 안함
- Eviction policy(캐시 비우는 정책)
- least-recently-used(LRU)랑 비슷하게 정책 만들기
- object 읽기, 쓰기 모두 object를 사용한 것이다. -> 캐시 히트된 것임.
- 캐싱 해줘야 하는데 캐시 공간 없을 때 어떤 object 없앨지 결정할 때 사용하는 정책임
- 동기화(Synchronization)
- 여러 스레드가 동시에 캐시에 접근함
- 이때, 캐시 접근은 thread-safe, no race condition.
- 캐시에 쓰기: 동기화O
- 캐시 읽기: 동기화X. 제한 없음
- 기법들
- 캐시 파티셔닝
- Pthread readers-writers lock 사용
- 세마포어 사용해서 readers-writers 구현
'SWJungle' 카테고리의 다른 글
PintOS Project 2. User Program - WIL (0) 2023.10.18 PintOS Project 1. Thread - WIL (2) 2023.10.02 레드-블랙 트리 Red-Black Tree 5가지 조건의 의미 (3) 2023.09.06 SW정글 7기 4주차: 알고리즘 끝, Red-Black Tree 시작 (0) 2023.09.03 SW정글 7기 3주차 알고리즘 후기 (2) 2023.08.27