refresh-ahead
만료가 오기 전에 캐시가 값을 미리 다시 읽어 두는 방식입니다. 자주 읽히는 항목은 만료되기 전에 캐시가 요청 처리 밖에서 새 값으로 갈아 둡니다. 그래서 그 항목을 읽는 요청은 데이터베이스까지 내려가 기다리지 않습니다.
쉽고 빠른 이해
만료가 가까워진 캐시 항목을 캐시가 미리 다시 읽어 두는 방식입니다. 만료를 20초로 두고, 그중 얼마가 지났을 때 미리 읽을지 정하는 계수를 0.5 로 적으면, 10초가 지난 뒤에 그 항목을 읽은 요청이 미리 읽기를 걸어 둡니다.
이게 없으면 만료된 항목을 처음 읽는 요청이 데이터베이스까지 내려가 그 시간을 다 치릅니다.
어떻게 도나:
- 만료가 가까운 항목이 읽히면 캐시가 지금 들고 있는 값을 곧바로 돌려줍니다
- 값을 다시 읽어 오는 일은 그 응답 뒤에 따로 돕니다
- 만료를 이미 넘긴 뒤에 읽히면 그 요청이 데이터베이스를 기다립니다
대가도 있습니다. 미리 읽어 둔 값이 끝내 안 읽히면 그 요청이 헛것이 됩니다. 그런 요청이 늘면 데이터베이스의 처리율이 나빠집니다.
상세
오라클의 캐시 제품 Coherence 의 캐시 항목에는 만료 간격이 붙습니다. 항목이 캐시에 들어온 뒤 그만큼 지나면 그 항목은 만료됩니다. 값을 가져온 바깥 저장소는 원본이라 부릅니다. 대개 데이터베이스가 원본입니다. refresh-ahead 는 만료가 오기 전에 항목을 다시 읽어 두기로 한 결정입니다. 갈림을 정하는 것은 무엇이 그 읽기를 촉발하느냐입니다. 계기는 접근입니다. 만료에 가까워진 항목을 읽은 요청은 캐시가 지금 들고 있는 값을 받습니다. 값을 다시 읽어 오는 일은 그 응답 뒤에 따로 돕니다.
설정을 걸어 두면 최근 접근된 캐시 항목을 캐시가 만료 전에 스스로 비동기로 다시 읽습니다. 이 이름으로 그 동작을 정의한 것이 Coherence 개발자 가이드입니다. 다시 읽는 주체는 애플리케이션이 아니라 캐시입니다. 대상도 캐시 전체가 아닙니다. 최근에 접근된 항목입니다.
원본을 상대하는 부품이 캐시와 원본 사이에 섭니다. 다시 읽어 오는 곳은 캐시 로더라 부릅니다. 동기 읽기와 타임아웃을 말하는 대목에서는 캐시 스토어라 부릅니다. 두 이름이 가리키는 것은 원본을 읽어 오는 그 부품입니다. 아래에서는 캐시 로더로 적습니다.
언제부터 「만료에 가까운」인지는 계수가 정합니다. 미리 읽기 시점은 항목 만료 간격에 대한 비율로
적습니다. 그 비율로 실제 만료보다 앞선 시점을 계산합니다. 그 시점의 이름이 soft-expiration
입니다. 값은 비율이지만 적는 형식은 1.0 이하의 소수입니다. 그 계산이 식으로 적힌 자리는 없습니다.
다만 실려 있는 예가 가리키는 시점은 하나입니다. 만료 간격에 계수를 곱한 만큼 항목이 나이를 먹은
때입니다. 만료 간격이 1분이고 계수가 0.75이면 45초가 지난 때입니다. 그때가 곧 만료까지 15초 남은
때입니다. 「만료 15초 안에 접근되면」이라는 서술이 가리키는 시점과 같습니다.
그 시점을 지난 뒤의 접근 요청은 그 항목에 대한 비동기 적재 요청을 예약합니다. 그 앞의 접근은 예약을 걸지 않습니다. 만료 간격이 60초이고 계수가 0.5이면 세 갈래로 갈립니다.
flowchart TD
A["항목을 읽는 요청"] --> B{"만료 간격의 계수 배를 지났나"}
B -->|아니다| C["현재 값을 돌려준다"]
B -->|지났다| D{"만료도 지났나"}
D -->|아니다| E["현재 값 + 미리 읽기 예약"]
D -->|지났다| F["동기로 읽어 값을 새로 한다"]
30초가 안 된 항목을 읽으면 캐시의 현재 값이 돌아갑니다. 그것으로 끝납니다. 30초는 넘었지만 60초는 안 된 항목에 요청이 오면 캐시의 현재 값이 돌아갑니다. 그리고 미리 읽기가 예약됩니다. 돌아간 값은 만료된 값이 아닙니다. 아직 만료 전인 그 시점의 값입니다. 응답을 돌려주기 전에 캐시 전체를 새로 하는 일은 아닙니다. 갱신은 요청 처리 밖에서 일어납니다. 60초가 지난 뒤에 접근되면 캐시가 캐시 로더에서 동기로 읽어 값을 새로 합니다.
가운데 갈래에서는 요청이 먼저 현재 값을 받습니다. 미리 읽기는 그 뒤에 놓입니다.
sequenceDiagram
autonumber
participant Q as 요청
participant C as 캐시
participant L as 캐시 로더
participant S as 원본
Q->>C: 만료에 가까운 항목을 읽는다
C-->>Q: 만료 전인 현재 값
Note over C: 미리 읽기를 예약한다
C->>L: 비동기로 값을 다시 읽는다
L->>S: 값 읽기
S-->>L: 새 값
L-->>C: 새 값으로 갈아 둠
원본까지 내려가는 읽기는 응답 뒤에 따로 돕니다. 요청은 그것을 기다리지 않습니다. 예약이 잡힌 뒤 언제 실제로 도는지는 두 문서에 없습니다(미확인).
이 갈림의 맨 아래는 refresh-ahead 가 손대지 않는 쪽입니다. 만료를 넘긴 접근은 캐시가 원본을 읽어 채우는 동기 읽기로 떨어집니다. read-through 의 읽기와 같습니다. refresh-ahead 를 켜도 이 동기 읽기는 그대로 남습니다. refresh-ahead 가 더하는 것은 만료 전에 도는 비동기 읽기 하나입니다.
이것이 돌려면 미리 서 있어야 하는 조건이 둘입니다. 계수 원소는 내부 캐시가 local-scheme 이고
<expiry-delay> 하위 원소로 설정된 경우에만 적용됩니다. refresh-ahead 는 캐시 항목에 만료 간격을
함께 설정해 두었다고 전제합니다. 둘은 선행조건입니다. 골랐을 때 치르는 값이 아니라, 안 갖추면
아예 안 도는 조건입니다.
대가
얻는 것
- 만료 때문에 값을 다시 실을 때 그 시간을 요청이 안 치릅니다. 자주 접근되는 항목이 캐시에 들어온 뒤로는, 느릴 수도 있는 캐시 스토어 읽기의 영향을 애플리케이션이 느끼지 않습니다.
- 여럿이 같이 읽는 항목의 값이 캐시 안에서 신선하게 유지됩니다. 원본을 거듭 읽느라 생길 수 있는 지연도 피합니다.
- 예측이 완전히 정확하면 지연이 줄고 덧붙는 부담이 없습니다. read-through 에 비해 줄어드는 지연이 이 방식이 얻는 것입니다. 다만 캐시가 앞으로 필요해질 항목을 정확히 예측할 수 있을 때만 그렇습니다.
내주는 것
- 예측이 틀리는 만큼 원본에 쓸데없는 요청이 갑니다. 다시 안 읽힐 값까지 미리 읽어 두므로, 원본은 읽히지 않을 값을 읽어 주게 됩니다. 예측이 틀리는 비율이 높아질수록 처리율에 미치는 영향이 커집니다.
- 원본이 밀리기 시작하면 지연까지 나빠질 수 있습니다. 지연을 줄이려고 켠 것이 반대쪽으로 돌아서는 자리입니다.
- 계수를 작게 잡을 때 무엇이 나빠지는지는 확인하지 못했습니다(미확인). 계수를 넓게 잡을 때의 대가만 자료에 이름 붙어 있습니다.
예시
이 한 벌은 분류 목록을 담는 캐시의 설정입니다. 스킴 이름과 로더 클래스 이름이 모두 분류를
가리킵니다. Coherence 개발자 가이드의 Specifying a Refresh-Ahead Factor 예제입니다.
<distributed-scheme>
<scheme-name>categories-cache-all-scheme</scheme-name>
<service-name>DistributedCache</service-name>
<backing-map-scheme>
<read-write-backing-map-scheme>
<scheme-name>categoriesLoaderScheme</scheme-name>
<internal-cache-scheme>
<local-scheme>
<scheme-ref>categories-eviction</scheme-ref>
</local-scheme>
</internal-cache-scheme>
<cachestore-scheme>
<class-scheme>
<class-name>
com.demo.cache.coherence.categories.CategoryCacheLoader
</class-name>
</class-scheme>
</cachestore-scheme>
<refresh-ahead-factor>0.5</refresh-ahead-factor>
</read-write-backing-map-scheme>
</backing-map-scheme>
<autostart>true</autostart>
</distributed-scheme>
<local-scheme>
<scheme-name>categories-eviction</scheme-name>
<expiry-delay>20s</expiry-delay>
</local-scheme>
계수는 <refresh-ahead-factor> 에 0.5 로 적혀 있습니다. 만료 간격은 <expiry-delay> 에 20s 로
적혀 있습니다. 두 값은 같은 집에 없습니다. 계수는 분산 캐시를 정의하는 스킴 안에 있습니다. 만료
간격은 따로 선 local-scheme 안에 있습니다. 내부 캐시 쪽에는 그 스킴을 이름으로 가리키는
<scheme-ref> 만 있습니다.
flowchart TD
subgraph S1["distributed-scheme"]
B["read-write-backing-map-scheme"]
B --> F["refresh-ahead-factor 0.5"]
B --> E["cachestore-scheme<br/>CategoryCacheLoader"]
B --> C["internal-cache-scheme"]
C --> R["local-scheme<br/>scheme-ref categories-eviction"]
end
subgraph S2["local-scheme · categories-eviction"]
X["expiry-delay 20s"]
end
R -.->|이름을 맞춰 찾아간다| X
점선이 두 스킴을 잇는 고리입니다. 안쪽 <scheme-ref> 의 값과 바깥 스킴의 <scheme-name> 이
같은 이름이라 이어집니다. 이 설정은 지역 캐시 항목에 계수 0.5 와 만료 간격 20초를 정합니다.
항목이 만료 10초 안에 접근되면 캐시 로더에서 비동기로 다시 읽도록 예약됩니다. 개발자 가이드가
이 예제에 붙인 설명입니다. <cachestore-scheme> 에 적힌 클래스가 원본을 읽는 캐시 로더입니다.
미리 읽기는 그 클래스를 거쳐 돕니다.
실패
발동 조건이 좁아서 값을 적어 두고도 안 도는 때가 있습니다.
| 조건 | 그때 벌어지는 일 |
|---|---|
| 만료를 넘긴 뒤에 항목이 접근된다 | 미리 읽기가 안 돕니다. 캐시가 캐시 로더에서 동기로 읽어 값을 새로 하고, 그 요청은 원본을 기다립니다 |
| 계수가 0 이다 | 미리 읽기 예약이 비활성됩니다. 기본값이 0 이라 아무 값도 적지 않으면 이 상태입니다 |
| 계수가 1.0 이다 | 모든 조회가 곧바로 비동기 미리 읽기를 겁니다 |
내부 캐시가 local-scheme 이 아니거나 <expiry-delay> 가 없다 |
계수 원소가 적용되지 않습니다 |
| 비동기로 도는 캐시 로더 작업이 타임아웃된다 | 캐시 서비스 종료로 이어지지 않습니다. 동기 작업이 타임아웃되면 실행 스레드가 인터럽트되고 끝내 캐시 서비스 종료로 이어질 수 있습니다 |
미리 읽기가 예외로 실패한 뒤 그 항목의 상태는 두 문서 어디에도 적혀 있지 않습니다(미확인).
운영
켜는 데 필요한 값은 둘입니다. 계수와 만료 간격입니다.
<refresh-ahead-factor> 는 선택 원소입니다. 이 원소는 soft-expiration 시각을 계산하는 데
쓰입니다. 값은 내부 지역 캐시의 만료 간격에 대한 비율입니다. 기본값은 0 입니다. 0 이면 미리
읽기 예약이 비활성됩니다. 1.0 이면 모든 조회가 곧바로 비동기로 값을 다시 읽게 만듭니다.
허용되는 값은 1.0 이하의 음이 아닌 실수입니다. 그 사이의 어떤 값을 골라야 하는지에 대한 지침은
없습니다(미확인).
<expiry-delay> 는 내부 캐시의 만료 간격입니다. 위 예시의 값은 20s 입니다. 이 원소가 없으면
계수는 적용되지 않습니다.
캐시 구성에는 한정이 걸립니다. 개발자 가이드는 read-through·write-through 캐싱과 그 변종이 Partitioned(Distributed) 캐시 토폴로지에서만 쓰이도록 의도되었다고 적습니다. 확장으로 Near cache 가 포함됩니다. 지역 캐시는 이 기능의 일부만 지원합니다. Replicated 캐시와 Optimistic 캐시는 쓰지 않아야 한다고 적습니다.
이 이름들은 Coherence 가 캐시를 어떻게 배치하는지를 가리킵니다. Partitioned 는 항목을 여러
노드에 나눠 담는 구성입니다. 개발자 가이드는 Distributed 라고도 적습니다. Near cache 는 그 앞에
지역 캐시를 한 겹 더 두는 구성입니다. Replicated 는 같은 항목을 노드마다 복제해 두는 구성입니다.
local-scheme 은 한 노드 안의 지역 캐시를 정의하는 설정 원소입니다. Optimistic 캐시의 뜻은 이
대목의 두 문서가 풀지 않습니다(미확인).
<cachestore-timeout> 은 캐시 로더의 읽기·쓰기 작업에 걸리는 타임아웃 간격을 정하는 선택
원소입니다. 설정 레퍼런스는 비동기 작업의 예로 refresh-ahead 를 듭니다. 그 간격이 미리 읽기에도
걸립니다.
관련 항목
이것이 속하는 상위 분류와 같은 분류에서 함께 고르는 전략
캐싱 · cache-aside · read-through · write-through · write-behind · write-around · cache-as-sor
항목이 언제 낡고 언제 걷히는지 정하는 값
미리 읽기가 줄이려는 지연과 그 지연이 터지는 방식
캐시 미스 · 캐시 스탬피드 · 썬더링 허드 · 낡은 데이터
같은 지연을 다른 수단으로 줄이는 지시자와 기법
stale-while-revalidate · Cache-Control · request collapsing
이 이름을 정의한 제품과 그 캐시 구성
Oracle Coherence · CacheStore · Near cache · Partitioned cache
다른 이름: refresh ahead · Refresh-Ahead · refresh-ahead caching · 리프레시 어헤드