Caffeine์ Java์์ ์ฌ์ฉ๋๋ ๊ณ ์ฑ๋ฅ ๋ก์ปฌ ์บ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, Google์ Guava Cache์ ๋์ฒด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ง๋ค์ด์ก์ผ๋ฉฐ, ์ฑ๋ฅ(ํํธ์จ๊ณผ ์ฒ๋ฆฌ๋) ๋ฉด์์ ์ฐ์ํ ์บ์์ด๋ค. ๋ถ์ฐ ์๋ฒ ๋ฑ์ผ๋ก ์ธํด Redis ์ฌ์ฉ์ด ํ์ํ ๊ฒฝ์ฐ๋ฅผ ์ ์ธํ๋ฉด, ๋ก์ปฌ ์บ์๋ก ์ ํฉํ๋ค๊ณ ํ๋ค.
Caffeine Cache ํน์ง
์นดํ์ธ ์บ์๋ ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๊ฐ๋๋ค. ๋๋ ์์ธํ ๋ชจ๋ ๊ฑธ ์์ง ๋ชปํ์ง๋ง, Java ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋ก์ปฌ ์บ์๊ฐ ํ์ํ ๊ฒฝ์ฐ ๋ง์ด ์ฌ์ฉํ๋ค๊ณ ํ์ฌ ์ด๋ฒ ๊ฐ๋ฐ ๊ฑด์ ์ ์ฉํด ๋ณด๋ฉด์ ์์๋ณธ ๋ด์ฉ์ ๊ธฐ์ ํ์๋ค.
- ๋์ ์ฑ๋ฅ : ConcurrentHashMap๋ณด๋ค ๋น ๋ฅด๊ณ , Guava๋ณด๋ค 3~10๋ฐฐ ๋น ๋ฅธ ๊ฒฝ์ฐ๋ ์์
- ์๋ ๋ง๋ฃ : TTL(Time To Live), TTI(Time To Idle) ์ง์
- ์ต์ ์ ๊ทผ ๊ธฐ๋ฐ ์ญ์ : LRU ๋์ W-TinyLFU ๊ธฐ๋ฐ ์๊ณ ๋ฆฌ์ฆ ์ฌ์ฉ
- ๋น๋๊ธฐ ๋ก๋ฉ ์ง์ : AsyncLoadingCache ์ฌ์ฉ
- Soft/Weak ์ฐธ์กฐ : ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ GC์ ์ํด ์ ๊ฑฐ๋๋ ์บ์ ์ค์ ๊ฐ๋ฅ
- ํต๊ณ ์ ๊ณต : ํํธ์จ, ๋ก๋ฉ ์๊ฐ ๋ฑ ํต๊ณ ์์ง ๊ฐ๋ฅ
Caffeine Cache ์ฌ์ฉ ๋ฐฉ๋ฒ
์์กด์ฑ (Maven)
pom.xml ํ์ผ์ ๋ค์ ๋ด์ฉ์ ์ถ๊ฐํ ๋ค ๋น๋ํ๋ค.
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
์ฃผ์ Functions
1) Cache(K, V) - ๊ธฐ๋ณธ ์บ์
put(K key, V value)
์บ์์ ๊ฐ์ ์๋์ผ๋ก ์ ์ฅํ๋ค.
cache.put("user1", "ํ๊ธธ๋");
getIfPresent(K key)
์บ์์ ํค๊ฐ ์์ ๊ฒฝ์ฐ ๊ฐ์ ๋ฐํํ๊ณ , ์์ผ๋ฉด null์ ๋ฐํํ๋ค.
String name = cache.getIfPresent("user1"); // "ํ๊ธธ๋"
get(K key, Function<? super K, ? extends V> mappingFunction)
๊ฐ์ด ์์ผ๋ฉด ํจ์๋ก๋ถํฐ ๊ฐ์ ์์ฑํ๊ณ ์ ์ฅ ํ ๋ฐํํ๋ค.
String name = cache.get("user2", k -> "๊ธฐ๋ณธ๊ฐ");
invalidate(K key)
ํน์ ํค์ ์บ์ ํญ๋ชฉ์ ์ญ์ ํ๋ค.
cache.invalidate("user1");
invalidateAll()
๋ชจ๋ ์บ์ ํญ๋ชฉ์ ์ญ์ ํ๋ค.
cache.invalidateAll();
asMap()
์บ์๋ฅผ ConcurrentMap์ฒ๋ผ ์ฌ์ฉํ๋ค.
cache.asMap().forEach((key, value) -> {
System.out.println(key + ": " + value);
});
2) LoadingCache(K, V) - ์๋ ๋ก๋ฉ ์บ์
get(K key)
ํค๊ฐ ์์ผ๋ฉด ๋ฑ๋ก๋ ๋ก๋ฉ ํจ์๋ก ๊ฐ์ ์๋ ์์ฑ ๋ฐ ์ ์ฅํ๋ค.
LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
.build(key -> "User_" + key);
String user = loadingCache.get("123"); // "User_123"
getAll(Iterable<? extends K> keys)
์ฌ๋ฌ ํค๋ฅผ ํ ๋ฒ์ ๋ก๋ฉํ๋ค.
Map<String, String> users = loadingCache.getAll(List.of("1", "2"));
refresh(K key)
ํด๋น ํค์ ๊ฐ์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฌ๋ก๋ฉํ๋ค.
loadingCache.refresh("123");
3) AsyncLoadingCache<K, V> – ๋น๋๊ธฐ ์บ์
get(K key)
๊ฐ์ด ์์ ๊ฒฝ์ฐ ๋น๋๊ธฐ ๋ก๋ฉ ํ CompletableFuture๋ก ๋ฐํํ๋ค.
AsyncLoadingCache<String, String> asyncCache = Caffeine.newBuilder()
.buildAsync(key -> CompletableFuture.supplyAsync(() -> "AsyncUser_" + key));
CompletableFuture<String> future = asyncCache.get("123");
future.thenAccept(System.out::println);=
getAll(Iterable<? extends K> keys)
์ฌ๋ฌ ํค๋ฅผ ํ ๋ฒ์ ๋ก๋ฉํ๋ค.
CompletableFuture<Map<String, String>> allUsers = asyncCache.getAll(List.of("1", "2"));
allUsers.thenAccept(map -> {
map.forEach((k, v) -> System.out.println(k + ": " + v));
});
synchronous()
๋น๋๊ธฐ ์บ์๋ฅผ ๋๊ธฐ ์บ์์ฒ๋ผ ์ฌ์ฉํ ์ ์๋ View์ด๋ค.
Cache<String, String> syncView = asyncCache.synchronous();
String value = syncView.getIfPresent("123");
4) ์ค์ ๊ด๋ จ
maximumSize(long size)
์ต๋ ์บ์ ํญ๋ชฉ ๊ฐ์๋ฅผ ์ง์ ํ๋ค.
Caffeine.newBuilder().maximumSize(1000);
expireAfterWrite(long duration, TimeUnit unit)
ํญ๋ชฉ์ ์ ์ฅํ ํ ์ผ์ ์๊ฐ์ด ์ง๋๋ฉด ๋ง๋ฃ์ํจ๋ค.
Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES);
expireAfterAccess(long duration, TimeUnit unit)
ํญ๋ชฉ์ ์ ๊ทผํ ํ ์ผ์ ์๊ฐ์ด ์ง๋๋ฉด ๋ง๋ฃ์ํจ๋ค.
Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES);
refreshAfterWrite(long duration, TimeUnit unit)
์ผ์ ์๊ฐ์ด ์ง๋๋ฉด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์๋ก๊ณ ์นจํ๋ค. (LoadingCache์๋ง ํด๋น).
Caffeine.newBuilder().refreshAfterWrite(5, TimeUnit.MINUTES);
recordStats()
ํต๊ณ ์์ง (ํํธ์จ, ๋ฏธ์ค์จ ๋ฑ)
Cache<String, String> statsCache = Caffeine.newBuilder()
.recordStats()
.build();
CacheStats stats = statsCache.stats();
System.out.println("Hit Rate: " + stats.hitRate());
์บ์ ๋ฐ์ดํฐ ์กฐํ(get) ๊ด๋ จ ์ฐจ์ด์
์บ์ ๋ฏธ์ค๋ฅผ ๋ฌด์ํ๋ ๊ฒฝ์ฐ getIfPresent๋ฅผ ์ฌ์ฉํ๊ณ , ์กฐํ ์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด DB์ Accessํ๋ ๋ฑ์ ์์ ์ ํตํด ์บ์ฑํ๋ ํ๋ก์ธ์ค๋ฅผ ์ถ๊ฐํ๊ธธ ์ํ๋ ๊ฒฝ์ฐ get์ ์ฌ์ฉํด์ผ ํ๋ค.
getIfPresent(K Key)
๋จ์ ์กฐํ์ฉ ๋ฉ์๋๋ก, ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด null์ ๋ฐํํ๋ค.
String val = cache.getIfPresent("id"); // ์์ผ๋ฉด null
get(K key, Function<K,V> mappingFunction)
์บ์์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ์์ฑ ํ ์ ์ฅํ๋ค. (Lazy loading)
String val = cache.get("id", k -> "default_" + k);
์บ์ ๋ง๋ฃ ์ ์ฑ
์นดํ์ธ ์บ์์ ๋ง๋ฃ ์ ์ฑ ์ ์์ฝํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
- expireAfterWrite : ์บ์์ ์ฐ์ธ ํ ์ง์ ์๊ฐ ์ง๋๋ฉด ๋ง๋ฃ
- expireAfterAccess : ๋ง์ง๋ง ์ ๊ทผ(์ฝ๊ธฐ/์ฐ๊ธฐ) ํ ์ง์ ์๊ฐ ์ง๋๋ฉด ๋ง๋ฃ
- refreshAfterWrite : ์ง์ ์๊ฐ ํ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์๋ก๊ณ ์นจ
API ์ฌ์ฉ ์์
1) ๊ธฐ๋ณธ ์บ์ ์์ฑ
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1000) // ์ต๋ 1000๊ฐ ์ ์ฅ
.expireAfterWrite(10, TimeUnit.MINUTES) // ์ฐ๊ธฐ ํ 10๋ถ ๊ฒฝ๊ณผํ๋ฉด ๋ง๋ฃ
.build();
2) ๊ฐ ์ ์ฅ ๋ฐ ์กฐํ
cache.put("key", "value");
String value = cache.getIfPresent("key"); // ์์ผ๋ฉด ๋ฐํ
3) ๊ฐ ์๋ ๋ก๋ฉ
LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterAccess(5, TimeUnit.MINUTES)
.build(key -> loadFromDB(key)); // ์์ ๋ ์๋ ๋ก๋ฉ
String result = loadingCache.get("user123");
4) ๋น๋๊ธฐ ์บ์
AsyncLoadingCache<String, String> asyncCache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.buildAsync(key -> loadFromDBAsync(key));
CompletableFuture<String> future = asyncCache.get("user123");
5) ํต๊ณ ํ์ฑํ
Cache<String, String> cache = Caffeine.newBuilder()
.recordStats()
.build();
CacheStats stats = cache.stats();
System.out.println(stats.hitRate());
'๐ป Language > Java : ์๋ฐ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [E] java: warning: source release 17 requires target release 17 (0) | 2025.04.17 |
|---|---|
| [E] Unchecked cast: 'java.lang.Object' to 'java.util.Map<java.lang.String,java.lang.Object>' (0) | 2025.04.15 |
| [Java/JPA] QueryDSL์ด๋? (0) | 2025.03.25 |
| [Java] JDK ์ค์น (Windows 10, JDK 17) (0) | 2025.03.12 |
| [Java] ๋ฐฑ์ค ์จ๋ผ์ธ ์ ์ง ๋จ๊ณ๋ณ๋ก ํ์ด๋ณด๊ธฐ - ์ ์ถ๋ ฅ๊ณผ ์ฌ์น์ฐ์ฐ (0) | 2024.11.20 |