낡은 데이터
낡은 데이터는 복사본이 옛 값을 그대로 들고 있는 상태입니다. 값을 어딘가에 복사해 두고 그 복사본을 읽는 순간부터 저절로 생깁니다. 읽는 쪽은 답을 제대로 받습니다. 그 답이 지금 원본과 다를 뿐입니다.
상세
이 상태에 이름을 붙여 둔 규격이 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) 캐시입니다. RFC(Request for Comments) 9111 은 저장된 응답을 신선한 것과 낡은 것 둘로 가릅니다. 나이가 아직 신선도 수명을 넘지 않은 응답이 신선한 응답입니다. 넘긴 응답이 낡은 응답입니다.
판정은 두 값이 만듭니다. 신선도 수명은 원본 서버가 응답을 만든 시점부터 그 응답의 만료 시점까지의 길이입니다. 나이는 원본 서버가 응답을 만들었거나 원본 서버와 성공적으로 검증한 뒤로 흐른 시간입니다. RFC 9111 은 판정식을 이렇게 적습니다.
response_is_fresh = (freshness_lifetime > current_age)
만료 시점은 두 갈래로 정해집니다. 명시적 만료 시점은 원본 서버가 정합니다. 캐시가 더 검증하지 않고는 저장된 응답을 더 쓸 수 없게 되는 시각입니다. 원본 서버가 만료 시점을 언제나 주는 것은 아닙니다. 그래서 캐시는 정해진 조건 아래에서 어림짐작으로 만료 시점을 매기는 것도 허용됩니다. 이쪽이 휴리스틱 만료 시점입니다.
낡음이 어디서 자라는지도 같은 절에 적혀 있습니다. RFC 9111 은 응답이 신선하면 원본 서버에 연락하지 않고 뒤따르는 요청에 그 응답을 쓸 수 있다고 적습니다. 그렇게 해서 효율이 올라간다고 적습니다. 연락하지 않는 그 구간이 낡음이 자라는 구간입니다. 그 사이에 원본이 바뀌어도 캐시는 바뀐 줄을 모릅니다.
발생 조건
세 자리에서 나옵니다. 셋 다 값을 복사해 두고 그 복사본을 읽는 구조입니다.
flowchart TD
A["복사본을 읽는다"] --> B{"원본이 그 사이에 바뀌었나"}
B -- 아니오 --> C["같은 값을 받는다"]
B -- 예 --> D{"읽는 쪽이 원본 확인을 요구했나"}
D -- 요구했다 --> E["원본까지 가서 새 값을 받는다"]
D -- 안 했다 --> F["옛 값을 받는다"]
신선도 수명이 남은 동안 원본이 바뀔 때
응답 하나를 캐시에 저장합니다. Cache-Control: max-age=600 이면 나이가 600초를 넘기 전까지 그
응답은 신선합니다. RFC 9111 은 응답이 신선하면 원본 서버에 연락하지 않고 뒤따르는 요청에 쓸 수
있다고 적습니다. 그 600초 안에 원본이 바뀌면 캐시는 그 사실을 모릅니다. 601초째에 처음으로 판정이
뒤집힙니다.
조건 하나를 빼면 창이 닫힙니다. max-age 를 0 으로 두면 신선도 수명이 0 입니다. 판정식은 신선도
수명이 나이보다 커야 신선하다고 적습니다. 저장 직후의 나이 0 은 수명 0 을 못 채웁니다. 그래서 그
응답은 곧바로 낡은 응답이 됩니다. 낡은 응답을 내주는 것은 RFC 9111 이 §4.2.4 에서 조건을 붙여 둔
자리입니다.
최종 일관성 읽기로 방금 끝난 쓰기를 읽을 때
Amazon DynamoDB 는 읽기 일관성을 두 가지로 둡니다. 최종 일관성 읽기가 모든 읽기 작업의 기본값입니다. AWS 개발자 안내서는 테이블이나 인덱스에 최종 일관성 읽기를 내면 그 응답이 방금 끝난 쓰기 작업의 결과를 안 비출 수 있다고 적습니다. 조금 뒤에 같은 읽기 요청을 되풀이하면 결국 더 최근 항목이 돌아올 것이라고도 적습니다.
재현 조건은 순서입니다. 쓰기를 하나 끝냅니다. 곧바로 ConsistentRead 를 주지 않은 채 그 항목을
읽습니다. 방금 쓴 값이 안 돌아올 수 있습니다.
조건 하나를 빼면 달라집니다. GetItem · Query · Scan 에 ConsistentRead 를 참으로 주면
DynamoDB 가 성공한 이전 쓰기 작업을 전부 반영한 응답을 돌려줍니다. 다만 강한 일관성 읽기는
테이블과 로컬 보조 인덱스에서만 지원됩니다. 전역 보조 인덱스와 스트림에서 오는 읽기는 전부 최종
일관성 읽기입니다.
복제가 대기 서버에 닿기 전에 읽을 때
PostgreSQL 문서는 대기 서버의 데이터가 주 서버에서 도착하는 데 시간이 걸린다고 적습니다. 그래서 주 서버와 대기 서버 사이에 잴 수 있는 지연이 생깁니다. 문서는 같은 질의를 두 서버에서 거의 동시에 돌리면 결과가 다르게 나올 수 있다고 적습니다. 대기 서버의 데이터가 주 서버와 최종 일관성을 이룬다는 표현을 씁니다.
재현 조건도 순서입니다. 주 서버에서 트랜잭션을 커밋합니다. 거의 동시에 대기 서버에 같은 질의를 던집니다. 결과가 갈릴 수 있습니다.
경계선은 커밋 레코드의 재생입니다. 문서는 어떤 트랜잭션의 커밋 레코드가 대기 서버에서 재생되고 나면, 그 트랜잭션이 만든 변경이 대기 서버에서 새로 잡는 스냅샷에 보인다고 적습니다. 재생이 끝난 뒤에 시작한 질의는 이 자리에 안 걸립니다.
이 창을 설정으로 넓힐 수도 있습니다. 문서는 충돌하는 질의가 짧으면 WAL(Write-Ahead Log, 미리 쓰기
로그) 적용을 조금 늦춰 그 질의를 끝내게 하는 편이 대개 바람직하다고 적습니다. 다만 WAL 적용이 오래
늦어지는 것은 대개 바람직하지 않다고 적습니다. 그래서 max_standby_archive_delay 와
max_standby_streaming_delay 가 WAL 적용에 허용되는 최대 지연을 정합니다. 늦춘 만큼 대기 서버가
들고 있는 값이 더 뒤처집니다.
예시
Cache-Control 과 Age
Cache-Control: max-age=600, stale-while-revalidate=30
RFC 5861 §3.1 에 실린 응답 헤더입니다. 문서는 이 응답이 600초 동안 신선하다고 적습니다. 그 뒤로는 비동기 검증이 시도되는 동안 추가로 30초까지 낡은 채로 제공될 수 있다고 적습니다. 검증이 결론을 못 내거나 검증을 부를 트래픽이 없으면 30초 뒤에 이 기능이 멈춥니다. 그러면 캐시된 응답이 진짜로 낡은 것이 됩니다. 다음 요청은 블록되어 정상 처리됩니다. 630초 구간 전체가 읽는 쪽이 옛 값을 받을 수 있는 창입니다.
읽는 쪽이 그 묵은 정도를 값으로 볼 수 있는 자리가 Age 헤더입니다.
Age = delta-seconds
RFC 9111 은 Age 를 응답이 원본 서버에서 만들어졌거나 성공적으로 검증된 뒤로 흐른 시간에 대한
보낸 쪽의 추정값으로 정의합니다. 값은 초 단위의 음이 아닌 정수입니다. max-age 지시어는 나이가
지정한 초 수보다 커지면 그 응답을 낡은 것으로 봐야 한다는 뜻입니다. 문서는 이 지시어가
max-age=5 처럼 토큰 꼴을 쓰고 max-age="5" 같은 따옴표 꼴을 쓰지 않는다고 적습니다.
DynamoDB 의 ConsistentRead
ConsistentRead 는 GetItem · Query · Scan 같은 읽기 작업이 받는 선택 파라미터입니다. 이 값을
참으로 두면 DynamoDB 가 성공한 이전 쓰기 작업의 갱신을 전부 반영한 최신 데이터로 응답합니다. 이
파라미터를 안 주면 기본값인 최종 일관성 읽기가 적용됩니다. 값 하나가 낡음을 볼지 말지를 가릅니다.
PostgreSQL 의 pg_last_xact_replay_timestamp
pg_last_xact_replay_timestamp () → timestamp with time zone
복구 중에 마지막으로 재생된 트랜잭션의 타임스탬프를 돌려줍니다. 그 값은 해당 트랜잭션의 커밋 또는 중단 WAL 레코드가 주 서버에서 만들어진 시각입니다. 복구 중 재생된 트랜잭션이 하나도 없으면 NULL 을 돌려줍니다. 복구가 아직 진행 중이면 이 값은 단조 증가합니다. 복구가 끝났으면 마지막으로 적용된 트랜잭션의 시각에 멈춰 있습니다. 대기 서버가 주 서버의 어느 시점까지 따라왔는지가 이 값으로 드러납니다.
경계
클라이언트가 max-stale 로 받아 간 응답도 낡은 데이터인가. 맞습니다.
RFC 9111 은 max-stale 요청 지시어를 클라이언트가 신선도 수명을 넘긴 응답을 받아들이겠다는
표시로 정의합니다. 값이 있으면 그 초 수만큼 넘긴 것까지 받습니다. 값이 없으면 나이를 가리지 않고
받습니다. 규격이 하는 일은 낡음을 없애는 것이 아닙니다. 낡은 응답을 누가 언제 내줘도 되는지를
정하는 것입니다.
RFC 9111 §4.2.4 는 캐시가 낡은 응답을 만들어 내면 안 되는 자리를 둘로 적어 둡니다. 하나는
no-cache · must-revalidate · s-maxage · proxy-revalidate 같은 명시적 프로토콜 지시어가
그것을 금지한 경우입니다. 다른 하나는 캐시가 연결이 끊겨 있지도 않고 클라이언트나 원본 서버가
명시적으로 허락하지도 않은 경우입니다. must-revalidate 는 그 허락을 거둬들이는 지시어입니다.
문서는 어떤 상황에서도 캐시가 이 지시어를 무시하면 안 된다고 못 박습니다. 특히 캐시가 끊겨 있으면
낡은 응답을 재사용하는 대신 오류 응답을 만들어야 한다고 적습니다. 허락이 붙어도 값의 상태는
그대로입니다. 달라지는 것은 읽는 쪽이 그 상태를 알고 받았는지뿐입니다.
더티 리드는 이 표제어가 아닙니다. PostgreSQL 문서는 더티 리드를 동시에 도는 커밋되지 않은 트랜잭션이 쓴 데이터를 읽는 것으로 정의합니다. 낡은 데이터는 원본에 이미 반영된 옛 값입니다. 더티 리드는 원본에 반영될지조차 아직 정해지지 않은 값입니다. 시간축에서 반대편입니다.
관련 항목
낡음 여부를 가르는 값
신선도 수명 · 나이 · TTL(Time To Live, 살아 있는 시간) · 명시적 만료 시점 · 휴리스틱 만료 시점
신선도 수명을 정하는 HTTP 지시어
Cache-Control · max-age · Expires
낡은 응답의 처리 방식을 정하는 HTTP 지시어
max-stale · stale-while-revalidate · must-revalidate · no-cache · 재검증
낡은 데이터가 비롯되는 HTTP 캐싱 배경
HTTP · 캐싱 · 오리진 서버 · CDN(Content Delivery Network, 콘텐츠 전송 네트워크)
낡은 데이터가 비롯되는 DynamoDB 읽기 일관성 배경
AWS · 읽기 일관성 · 최종 일관성 · 테이블 · 인덱스 · ConsistentRead · read-your-writes
낡은 데이터가 비롯되는 PostgreSQL 복제 배경
PostgreSQL · 복제 · 복제 지연 · 지연 · 핫 스탠바이
캐싱에서 함께 터지는 문제
캐시 미스 · 캐시 스탬피드 · 캐시 무효화 · request coalescing
복제·격리에서 헷갈리는 이웃 개념
다른 이름: stale data · stale response · 스테일 데이터 · 오래된 데이터