์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ PHP ์บ์‹ฑ ์ „๋žต ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์™€ ํŒŒ์ผ ์บ์‹œ๋กœ ์„ฑ๋Šฅ ํญ๋ฐœ์‹œํ‚ค๊ธฐ

๐Ÿš€ PHP ์บ์‹ฑ ์ „๋žต ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์™€ ํŒŒ์ผ ์บ์‹œ๋กœ ์„ฑ๋Šฅ ํญ๋ฐœ์‹œํ‚ค๊ธฐ

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์†๋„๋ฅผ 10๋ฐฐ ๋น ๋ฅด๊ฒŒ ๋งŒ๋“œ๋Š” ์‹ค์ „ ์บ์‹ฑ ํ…Œํฌ๋‹‰

๐Ÿ’ฌ ์นœ๊ตฌ์•ผ, ํ˜น์‹œ ๋„ˆ์˜ PHP ์›น์‚ฌ์ดํŠธ๊ฐ€ ๊ฑฐ๋ถ์ด์ฒ˜๋Ÿผ ๋А๋ฆฌ๊ฒŒ ์›€์ง์ด๋Š” ๊ฑฐ ๊ฐ™์ง€ ์•Š์•„? ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ํด๋ฆญํ–ˆ๋Š”๋ฐ 3์ดˆ, 5์ดˆ์”ฉ ๊ธฐ๋‹ค๋ ค์•ผ ํ•œ๋‹ค๋ฉด ๊ทธ๊ฑด ์ •๋ง ํฐ ๋ฌธ์ œ์•ผ! ์˜ค๋Š˜์€ ๋„ˆ์˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋กœ์ผ“์ฒ˜๋Ÿผ ๋น ๋ฅด๊ฒŒ ๋งŒ๋“ค์–ด์ค„ ์บ์‹ฑ ์ „๋žต์— ๋Œ€ํ•ด ๊นŠ์ด ์žˆ๊ฒŒ ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์•ผ. ๐ŸŽฏ


์บ์‹ฑ์€ ๋‹จ์ˆœํžˆ "๋น ๋ฅด๊ฒŒ ๋งŒ๋“œ๋Š” ๊ธฐ์ˆ "์ด ์•„๋‹ˆ๋ผ, ์„œ๋ฒ„ ์ž์›์„ ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ทน๋Œ€ํ™”ํ•˜๋Š” ํ•ต์‹ฌ ์ „๋žต์ด์•ผ. ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์™€ ํŒŒ์ผ ์บ์‹œ, ์ด ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ํ™œ์šฉํ•˜๋ฉด ๋„ˆ์˜ ๊ฐœ๋ฐœ ์‹ค๋ ฅ๋„ ํ•œ ๋‹จ๊ณ„ ์—…๊ทธ๋ ˆ์ด๋“œ๋  ๊ฑฐ์•ผ!

๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ โšก ์ดˆ๊ณ ์† ํŒŒ์ผ ์บ์‹œ ๐Ÿ’พ ์•ˆ์ •์  VS ์ตœ์ ์˜ ์„ ํƒ์€? ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋‹ค๋ฅด๋‹ค!

๐ŸŽฏ ์บ์‹ฑ์ด ๋ญ๊ธธ๋ž˜ ์ด๋ ‡๊ฒŒ ์ค‘์š”ํ• ๊นŒ?


์ž, ๋จผ์ € ๊ธฐ๋ณธ๋ถ€ํ„ฐ ํ™•์‹คํ•˜๊ฒŒ ์งš๊ณ  ๋„˜์–ด๊ฐ€์ž! ์บ์‹ฑ(Caching)์€ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ์ž„์‹œ ์ €์žฅ์†Œ์— ๋ณด๊ด€ํ•˜๋Š” ๊ธฐ์ˆ ์ด์•ผ. ๋งˆ์น˜ ๋„ˆ๊ฐ€ ์ž์ฃผ ์“ฐ๋Š” ๋ฌผ๊ฑด์„ ์ฑ…์ƒ ์œ„์— ์˜ฌ๋ ค๋‘๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ง์ด์•ผ. ์„œ๋ž ๊นŠ์ˆ™์ด ๋„ฃ์–ด๋‘๋ฉด ๊บผ๋‚ด๋Š” ๋ฐ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ์ง€๋งŒ, ์ฑ…์ƒ ์œ„์— ์žˆ์œผ๋ฉด ๋ฐ”๋กœ ์ง‘์„ ์ˆ˜ ์žˆ์ž–์•„? ๐Ÿ˜Š


PHP ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์บ์‹ฑ์ด ํ•„์š”ํ•œ ์ด์œ ๋Š” ๋ช…ํ™•ํ•ด:


๐Ÿ”ฅ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ๋ถ€ํ•˜ ๊ฐ์†Œ
๋งค๋ฒˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•˜๋Š” ๊ฑด ์„œ๋ฒ„์—๊ฒŒ ์—„์ฒญ๋‚œ ์ŠคํŠธ๋ ˆ์Šค์•ผ. ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ์กฐํšŒํ•œ๋‹ค๋ฉด? ๊ทธ๊ฑด ์ž์› ๋‚ญ๋น„์ง€!


โšก ์‘๋‹ต ์†๋„ ํ–ฅ์ƒ
์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ–ˆ์„ ๋•Œ 0.1์ดˆ ๋งŒ์— ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๊ณผ 3์ดˆ ํ›„์— ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ, ์–ด๋А ์ชฝ์ด ์ข‹์„๊นŒ? ๋‹น์—ฐํžˆ ๋น ๋ฅผ์ˆ˜๋ก ์ข‹์ง€!


๐Ÿ’ฐ ์„œ๋ฒ„ ๋น„์šฉ ์ ˆ๊ฐ
ํšจ์œจ์ ์ธ ์บ์‹ฑ์€ ์„œ๋ฒ„ ์ž์›์„ ์•„๊ปด์ฃผ๊ณ , ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ธํ”„๋ผ ๋น„์šฉ๋„ ์ค„์—ฌ์ค˜.


๐ŸŽจ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ 
๋น ๋ฅธ ์›น์‚ฌ์ดํŠธ๋Š” ์‚ฌ์šฉ์ž๋ฅผ ํ–‰๋ณตํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ , ์ดํƒˆ๋ฅ ์„ ๋‚ฎ์ถฐ์ค˜. ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰ ์ˆœ์œ„์—๋„ ๊ธ์ •์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€!

๐Ÿง  ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ: ๋ฒˆ๊ฐœ์ฒ˜๋Ÿผ ๋น ๋ฅธ ์†๋„์˜ ๋น„๋ฐ€


๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ๋Š” RAM(Random Access Memory)์„ ํ™œ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์ด์•ผ. RAM์€ ์ปดํ“จํ„ฐ์—์„œ ๊ฐ€์žฅ ๋น ๋ฅธ ์ €์žฅ ๋งค์ฒด ์ค‘ ํ•˜๋‚˜๊ฑฐ๋“ ! ํ•˜๋“œ๋””์Šคํฌ๋‚˜ SSD๋ณด๋‹ค ์ˆ˜์‹ญ ๋ฐฐ์—์„œ ์ˆ˜๋ฐฑ ๋ฐฐ ๋น ๋ฅด๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ผ. ๐Ÿš€

๐Ÿ“Œ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์˜ ์ฃผ์š” ํŠน์ง•


โœ… ์žฅ์ ๋“ค


1. ์••๋„์ ์ธ ์†๋„
๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ ์†๋„๋Š” ๋‚˜๋…ธ์ดˆ(ns) ๋‹จ์œ„์•ผ. ๋””์Šคํฌ I/O๊ฐ€ ๋ฐ€๋ฆฌ์ดˆ(ms) ๋‹จ์œ„์ธ ๊ฒƒ๊ณผ ๋น„๊ตํ•˜๋ฉด ์—„์ฒญ๋‚œ ์ฐจ์ด์ง€!


2. ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ง€์›
๋‹จ์ˆœ ๋ฌธ์ž์—ด๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ฐฐ์—ด, ๊ฐ์ฒด, ๋ฆฌ์ŠคํŠธ, ํ•ด์‹œ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์–ด.


3. ์›์ž์  ์—ฐ์‚ฐ ์ง€์›
์ฆ๊ฐ€(increment), ๊ฐ์†Œ(decrement) ๊ฐ™์€ ์—ฐ์‚ฐ์„ ๋™์‹œ์„ฑ ๋ฌธ์ œ ์—†์ด ์•ˆ์ „ํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด.


4. ๋ถ„์‚ฐ ํ™˜๊ฒฝ ์ง€์›
์—ฌ๋Ÿฌ ์„œ๋ฒ„๊ฐ€ ํ•˜๋‚˜์˜ ์บ์‹œ ์„œ๋ฒ„๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์–ด์„œ ํ™•์žฅ์„ฑ์ด ๋›ฐ์–ด๋‚˜.

โš ๏ธ ๋‹จ์ ๊ณผ ์ฃผ์˜์‚ฌํ•ญ


1. ํœ˜๋ฐœ์„ฑ
์„œ๋ฒ„๊ฐ€ ์žฌ์‹œ์ž‘๋˜๊ฑฐ๋‚˜ ์ „์›์ด ๊บผ์ง€๋ฉด ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ๋ผ์ ธ. ์˜๊ตฌ ๋ณด๊ด€์ด ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” ์ ํ•ฉํ•˜์ง€ ์•Š์•„.


2. ๋ฉ”๋ชจ๋ฆฌ ์šฉ๋Ÿ‰ ์ œํ•œ
RAM์€ ๋น„์‹ธ๊ณ  ์šฉ๋Ÿ‰์ด ์ œํ•œ์ ์ด์•ผ. ๋ฌดํ•œ์ • ์ €์žฅํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ ์„ ๋ช…์‹ฌํ•ด์•ผ ํ•ด.


3. ์ถ”๊ฐ€ ์ธํ”„๋ผ ํ•„์š”
Redis๋‚˜ Memcached ๊ฐ™์€ ๋ณ„๋„์˜ ์บ์‹œ ์„œ๋ฒ„๋ฅผ ์„ค์น˜ํ•˜๊ณ  ๊ด€๋ฆฌํ•ด์•ผ ํ•ด.

๐Ÿ› ๏ธ PHP์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ฃผ์š” ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ ์†”๋ฃจ์…˜


1๏ธโƒฃ Redis (Remote Dictionary Server)


Redis๋Š” ํ˜„์žฌ ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ์ธ๋ฉ”๋ชจ๋ฆฌ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ์•ผ. ๋‹จ์ˆœํ•œ ์บ์‹œ๋ฅผ ๋„˜์–ด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค๋กœ๋„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์ง€. ๐ŸŽฏ


Key-Value ์ €์žฅ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์„œ๋ฒ„Pub/Sub ์ง€์›์˜์†์„ฑ ์˜ต์…˜


Redis์˜ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ๋“ค:


โ€ข ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž…: String, List, Set, Sorted Set, Hash, Bitmap, HyperLogLog ๋“ฑ
โ€ข TTL(Time To Live): ์ž๋™์œผ๋กœ ๋งŒ๋ฃŒ๋˜๋Š” ํ‚ค ์„ค์ • ๊ฐ€๋Šฅ
โ€ข ํŠธ๋žœ์žญ์…˜: ์—ฌ๋Ÿฌ ๋ช…๋ น์„ ์›์ž์ ์œผ๋กœ ์‹คํ–‰
โ€ข Lua ์Šคํฌ๋ฆฝํŒ…: ๋ณต์žกํ•œ ๋กœ์ง์„ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ์‹คํ–‰
โ€ข ๋ณต์ œ์™€ ํด๋Ÿฌ์Šคํ„ฐ๋ง: ๊ณ ๊ฐ€์šฉ์„ฑ๊ณผ ํ™•์žฅ์„ฑ ์ œ๊ณต

๐Ÿ“ Redis ๊ธฐ๋ณธ ์‚ฌ์šฉ ์˜ˆ์ œ
<?php
// Redis ์—ฐ๊ฒฐ
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// ๊ฐ„๋‹จํ•œ ๋ฌธ์ž์—ด ์ €์žฅ (10๋ถ„ ๋™์•ˆ ์œ ํšจ)
$redis->setex('user:1000:name', 600, 'John Doe');

// ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
$userName = $redis->get('user:1000:name');
echo $userName; // "John Doe"

// ํ•ด์‹œ ๋ฐ์ดํ„ฐ ์ €์žฅ
$redis->hMSet('user:1000', [
    'name' => 'John Doe',
    'email' => 'john@example.com',
    'age' => 30
]);

// ํ•ด์‹œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
$userInfo = $redis->hGetAll('user:1000');
print_r($userInfo);

// ๋ฆฌ์ŠคํŠธ์— ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
$redis->rPush('recent_posts', 'post_id_1');
$redis->rPush('recent_posts', 'post_id_2');
$redis->rPush('recent_posts', 'post_id_3');

// ๋ฆฌ์ŠคํŠธ์—์„œ ์ตœ๊ทผ 5๊ฐœ ๊ฐ€์ ธ์˜ค๊ธฐ
$recentPosts = $redis->lRange('recent_posts', 0, 4);

// ์นด์šดํ„ฐ ์ฆ๊ฐ€ (์กฐํšŒ์ˆ˜ ๋“ฑ)
$redis->incr('post:1000:views');
$views = $redis->get('post:1000:views');

// ํ‚ค ์‚ญ์ œ
$redis->del('user:1000:name');
?>

2๏ธโƒฃ Memcached


Memcached๋Š” Redis๋ณด๋‹ค ๋จผ์ € ๋‚˜์˜จ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ ์‹œ์Šคํ…œ์ด์•ผ. ๋‹จ์ˆœํ•˜๊ณ  ๊ฐ€๋ณ์ง€๋งŒ ๊ทธ๋งŒํผ ๊ฐ•๋ ฅํ•ด! ํŠนํžˆ ๋Œ€๊ทœ๋ชจ ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ ๋น›์„ ๋ฐœํ•˜์ง€. ๐Ÿ’ช


์ดˆ๊ณ ์†๋‹จ์ˆœํ•จ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ๋ถ„์‚ฐ ์บ์‹ฑ


Memcached์˜ ํŠน์ง•:


โ€ข ๋‹จ์ˆœํ•œ Key-Value ์ €์žฅ: ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋Š” ์ง€์›ํ•˜์ง€ ์•Š์ง€๋งŒ ๊ทธ๋งŒํผ ๋น ๋ฆ„
โ€ข ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์•„ํ‚คํ…์ฒ˜: ๋ฉ€ํ‹ฐ์ฝ”์–ด CPU๋ฅผ ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉ
โ€ข LRU(Least Recently Used) ์•Œ๊ณ ๋ฆฌ์ฆ˜: ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ ์ž๋™ ์‚ญ์ œ
โ€ข ๋ถ„์‚ฐ ํ•ด์‹ฑ: ์—ฌ๋Ÿฌ ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ๋ถ„์‚ฐ ์ €์žฅ

๐Ÿ“ Memcached ๊ธฐ๋ณธ ์‚ฌ์šฉ ์˜ˆ์ œ
<?php
// Memcached ์—ฐ๊ฒฐ
$memcached = new Memcached();
$memcached->addServer('127.0.0.1', 11211);

// ๋ฐ์ดํ„ฐ ์ €์žฅ (600์ดˆ = 10๋ถ„)
$memcached->set('user_1000', [
    'name' => 'Jane Smith',
    'email' => 'jane@example.com'
], 600);

// ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
$user = $memcached->get('user_1000');
if ($user !== false) {
    echo "User found: " . $user['name'];
} else {
    echo "User not in cache";
}

// ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ํ•œ ๋ฒˆ์— ์ €์žฅ
$memcached->setMulti([
    'product_100' => ['name' => 'Laptop', 'price' => 1200],
    'product_101' => ['name' => 'Mouse', 'price' => 25],
    'product_102' => ['name' => 'Keyboard', 'price' => 80]
], 600);

// ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๊ธฐ
$products = $memcached->getMulti(['product_100', 'product_101', 'product_102']);

// ์นด์šดํ„ฐ ์ฆ๊ฐ€
$memcached->increment('page_views', 1);

// ๋ฐ์ดํ„ฐ ์‚ญ์ œ
$memcached->delete('user_1000');

// ๋ชจ๋“  ์บ์‹œ ์‚ญ์ œ (์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉ!)
$memcached->flush();
?>

3๏ธโƒฃ APCu (Alternative PHP Cache - User Cache)


APCu๋Š” PHP ํ”„๋กœ์„ธ์Šค ๋‚ด๋ถ€์—์„œ ๋™์ž‘ํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์•ผ. ๋ณ„๋„์˜ ์„œ๋ฒ„ ์„ค์น˜ ์—†์ด PHP ํ™•์žฅ ๋ชจ๋“ˆ๋งŒ ์„ค์น˜ํ•˜๋ฉด ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์„œ ๊ฐ„ํŽธํ•˜์ง€! ๐ŸŽˆ


์„ค์น˜ ๊ฐ„ํŽธ์ถ”๊ฐ€ ์„œ๋ฒ„ ๋ถˆํ•„์š”๋น ๋ฅธ ๋กœ์ปฌ ์บ์‹œ


APCu์˜ ํŠน์ง•:


โ€ข ํ”„๋กœ์„ธ์Šค ๋ ˆ๋ฒจ ์บ์‹œ: ๊ฐ PHP ํ”„๋กœ์„ธ์Šค๊ฐ€ ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ
โ€ข ์„ค์ • ๊ฐ„๋‹จ: php.ini์—์„œ ๋ช‡ ๊ฐ€์ง€ ์„ค์ •๋งŒ ํ•˜๋ฉด ๋
โ€ข ๋‹จ์ผ ์„œ๋ฒ„ ํ™˜๊ฒฝ์— ์ตœ์ : ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ๋Š” ๊ฐ ์„œ๋ฒ„๊ฐ€ ๋…๋ฆฝ์ ์ธ ์บ์‹œ ๋ณด์œ 
โ€ข OPcache์™€ ๋ณ„๊ฐœ: OPcache๋Š” ์ฝ”๋“œ ์บ์‹ฑ, APCu๋Š” ๋ฐ์ดํ„ฐ ์บ์‹ฑ

๐Ÿ“ APCu ๊ธฐ๋ณธ ์‚ฌ์šฉ ์˜ˆ์ œ
<?php
// APCu ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ
if (!function_exists('apcu_fetch')) {
    die('APCu is not available');
}

// ๋ฐ์ดํ„ฐ ์ €์žฅ (600์ดˆ ๋™์•ˆ ์œ ํšจ)
apcu_store('config_settings', [
    'site_name' => 'My Awesome Site',
    'theme' => 'dark',
    'language' => 'ko'
], 600);

// ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
$config = apcu_fetch('config_settings', $success);
if ($success) {
    echo "Site name: " . $config['site_name'];
} else {
    echo "Config not in cache";
}

// ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ํ•œ ๋ฒˆ์— ์ €์žฅ
apcu_store([
    'cache_key_1' => 'value_1',
    'cache_key_2' => 'value_2',
    'cache_key_3' => 'value_3'
]);

// ์นด์šดํ„ฐ ์ฆ๊ฐ€
apcu_inc('visitor_count', 1);
$visitors = apcu_fetch('visitor_count');

// ๋ฐ์ดํ„ฐ ์‚ญ์ œ
apcu_delete('config_settings');

// ์ „์ฒด ์บ์‹œ ์‚ญ์ œ
apcu_clear_cache();

// ์บ์‹œ ์ •๋ณด ํ™•์ธ
$cacheInfo = apcu_cache_info();
echo "Cached entries: " . $cacheInfo['num_entries'];
?>

๐Ÿ’พ ํŒŒ์ผ ์บ์‹œ: ์•ˆ์ •์ ์ด๊ณ  ์˜๊ตฌ์ ์ธ ์ €์žฅ์†Œ


ํŒŒ์ผ ์บ์‹œ๋Š” ๋ง ๊ทธ๋Œ€๋กœ ํŒŒ์ผ ์‹œ์Šคํ…œ์„ ํ™œ์šฉํ•ด์„œ ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์ด์•ผ. ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ๋ณด๋‹ค๋Š” ๋А๋ฆฌ์ง€๋งŒ, ์„œ๋ฒ„๊ฐ€ ์žฌ์‹œ์ž‘๋˜์–ด๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ์ง€๋˜๊ณ  ๋ณ„๋„์˜ ์ธํ”„๋ผ๊ฐ€ ํ•„์š” ์—†๋‹ค๋Š” ํฐ ์žฅ์ ์ด ์žˆ์ง€! ๐Ÿ“

๐Ÿ“Œ ํŒŒ์ผ ์บ์‹œ์˜ ์ฃผ์š” ํŠน์ง•


โœ… ์žฅ์ ๋“ค


1. ์˜์†์„ฑ
์„œ๋ฒ„๊ฐ€ ์žฌ์‹œ์ž‘๋˜์–ด๋„ ์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋ผ. ์ค‘์š”ํ•œ ์„ค์ •์ด๋‚˜ ์ž์ฃผ ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ์— ์ ํ•ฉํ•ด.


2. ์ถ”๊ฐ€ ์ธํ”„๋ผ ๋ถˆํ•„์š”
Redis๋‚˜ Memcached ๊ฐ™์€ ๋ณ„๋„์˜ ์„œ๋ฒ„๋ฅผ ์„ค์น˜ํ•˜๊ณ  ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์–ด. ํŒŒ์ผ ์‹œ์Šคํ…œ๋งŒ ์žˆ์œผ๋ฉด ๋ผ!


3. ์šฉ๋Ÿ‰ ์ œํ•œ ์™„ํ™”
๋””์Šคํฌ ๊ณต๊ฐ„์ด ํ—ˆ์šฉํ•˜๋Š” ํ•œ ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์–ด. RAM๋ณด๋‹ค ํ›จ์”ฌ ์ €๋ ดํ•˜๊ณ  ์šฉ๋Ÿ‰๋„ ํฌ์ง€.


4. ๋””๋ฒ„๊น… ์šฉ์ด
ํŒŒ์ผ๋กœ ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ง์ ‘ ์—ด์–ด์„œ ๋‚ด์šฉ์„ ํ™•์ธํ•˜๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์–ด.


5. ๊ฐ„๋‹จํ•œ ๊ตฌํ˜„
๋ณต์žกํ•œ ์„ค์ • ์—†์ด PHP์˜ ๊ธฐ๋ณธ ํŒŒ์ผ ํ•จ์ˆ˜๋งŒ์œผ๋กœ๋„ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•ด.

โš ๏ธ ๋‹จ์ ๊ณผ ์ฃผ์˜์‚ฌํ•ญ


1. ์ƒ๋Œ€์ ์œผ๋กœ ๋А๋ฆฐ ์†๋„
๋””์Šคํฌ I/O๋Š” ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ๋ณด๋‹ค ํ›จ์”ฌ ๋А๋ ค. ํŠนํžˆ HDD๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋”์šฑ ๊ทธ๋ž˜.


2. ๋™์‹œ์„ฑ ๋ฌธ์ œ
์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ํŒŒ์ผ์— ์ ‘๊ทผํ•˜๋ฉด ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด. ํŒŒ์ผ ์ž ๊ธˆ(locking) ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ํ•„์š”ํ•ด.


3. ํŒŒ์ผ ์‹œ์Šคํ…œ ์˜ค๋ฒ„ํ—ค๋“œ
์ˆ˜๋งŽ์€ ์ž‘์€ ํŒŒ์ผ๋“ค์ด ์ƒ์„ฑ๋˜๋ฉด ํŒŒ์ผ ์‹œ์Šคํ…œ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ์–ด.


4. ์บ์‹œ ๋ฌดํšจํ™” ๊ด€๋ฆฌ
์˜ค๋ž˜๋œ ์บ์‹œ ํŒŒ์ผ์„ ์ •๋ฆฌํ•˜๋Š” ๋กœ์ง์„ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•ด.


5. ๋ถ„์‚ฐ ํ™˜๊ฒฝ ์ œ์•ฝ
์—ฌ๋Ÿฌ ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ๋Š” ๊ฐ ์„œ๋ฒ„๊ฐ€ ๋…๋ฆฝ์ ์ธ ์บ์‹œ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋ผ. ๊ณต์œ  ํŒŒ์ผ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํ•œ ๋™๊ธฐํ™”๊ฐ€ ์–ด๋ ค์›Œ.

๐Ÿ› ๏ธ PHP์—์„œ ํŒŒ์ผ ์บ์‹œ ๊ตฌํ˜„ํ•˜๊ธฐ


ํŒŒ์ผ ์บ์‹œ๋Š” ์ง์ ‘ ๊ตฌํ˜„ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ํ”„๋ ˆ์ž„์›Œํฌ๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜๋„ ์žˆ์–ด. ๋จผ์ € ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•๋ถ€ํ„ฐ ์‚ดํŽด๋ณด์ž! ๐Ÿ’ป

๐Ÿ“ ๊ฐ„๋‹จํ•œ ํŒŒ์ผ ์บ์‹œ ํด๋ž˜์Šค ๊ตฌํ˜„
<?php
class SimpleFileCache {
    private $cacheDir;
    private $defaultTTL;
    
    public function __construct($cacheDir = './cache', $defaultTTL = 3600) {
        $this->cacheDir = rtrim($cacheDir, '/');
        $this->defaultTTL = $defaultTTL;
        
        // ์บ์‹œ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์—†์œผ๋ฉด ์ƒ์„ฑ
        if (!is_dir($this->cacheDir)) {
            mkdir($this->cacheDir, 0755, true);
        }
    }
    
    /**
     * ์บ์‹œ ํ‚ค๋ฅผ ํŒŒ์ผ ๊ฒฝ๋กœ๋กœ ๋ณ€ํ™˜
     */
    private function getFilePath($key) {
        $hash = md5($key);
        // ๋””๋ ‰ํ† ๋ฆฌ ๋ถ„์‚ฐ (์ฒซ 2์ž๋ฆฌ๋กœ ์„œ๋ธŒ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ)
        $subDir = substr($hash, 0, 2);
        $dir = $this->cacheDir . '/' . $subDir;
        
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
        
        return $dir . '/' . $hash . '.cache';
    }
    
    /**
     * ์บ์‹œ์— ๋ฐ์ดํ„ฐ ์ €์žฅ
     */
    public function set($key, $value, $ttl = null) {
        $ttl = $ttl ?? $this->defaultTTL;
        $filePath = $this->getFilePath($key);
        
        $data = [
            'expires' => time() + $ttl,
            'value' => $value
        ];
        
        // ์ง๋ ฌํ™”ํ•˜์—ฌ ์ €์žฅ
        $serialized = serialize($data);
        
        // ํŒŒ์ผ ์ž ๊ธˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ์•ˆ์ „ํ•˜๊ฒŒ ์“ฐ๊ธฐ
        $fp = fopen($filePath, 'w');
        if (flock($fp, LOCK_EX)) {
            fwrite($fp, $serialized);
            flock($fp, LOCK_UN);
        }
        fclose($fp);
        
        return true;
    }
    
    /**
     * ์บ์‹œ์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function get($key, $default = null) {
        $filePath = $this->getFilePath($key);
        
        if (!file_exists($filePath)) {
            return $default;
        }
        
        // ํŒŒ์ผ ์ž ๊ธˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ์•ˆ์ „ํ•˜๊ฒŒ ์ฝ๊ธฐ
        $fp = fopen($filePath, 'r');
        if (flock($fp, LOCK_SH)) {
            $content = fread($fp, filesize($filePath));
            flock($fp, LOCK_UN);
        }
        fclose($fp);
        
        $data = unserialize($content);
        
        // ๋งŒ๋ฃŒ ํ™•์ธ
        if ($data['expires'] < time()) {
            $this->delete($key);
            return $default;
        }
        
        return $data['value'];
    }
    
    /**
     * ์บ์‹œ ๋ฐ์ดํ„ฐ ์‚ญ์ œ
     */
    public function delete($key) {
        $filePath = $this->getFilePath($key);
        
        if (file_exists($filePath)) {
            return unlink($filePath);
        }
        
        return false;
    }
    
    /**
     * ๋ชจ๋“  ์บ์‹œ ์‚ญ์ œ
     */
    public function clear() {
        $this->deleteDirectory($this->cacheDir);
        mkdir($this->cacheDir, 0755, true);
        return true;
    }
    
    /**
     * ๋งŒ๋ฃŒ๋œ ์บ์‹œ ์ •๋ฆฌ
     */
    public function cleanup() {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($this->cacheDir),
            RecursiveIteratorIterator::SELF_FIRST
        );
        
        $deletedCount = 0;
        foreach ($iterator as $file) {
            if ($file->isFile() && $file->getExtension() === 'cache') {
                $content = file_get_contents($file->getPathname());
                $data = unserialize($content);
                
                if ($data['expires'] < time()) {
                    unlink($file->getPathname());
                    $deletedCount++;
                }
            }
        }
        
        return $deletedCount;
    }
    
    /**
     * ๋””๋ ‰ํ† ๋ฆฌ ์žฌ๊ท€์  ์‚ญ์ œ
     */
    private function deleteDirectory($dir) {
        if (!is_dir($dir)) {
            return false;
        }
        
        $items = scandir($dir);
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            
            $path = $dir . '/' . $item;
            if (is_dir($path)) {
                $this->deleteDirectory($path);
            } else {
                unlink($path);
            }
        }
        
        return rmdir($dir);
    }
}

// ์‚ฌ์šฉ ์˜ˆ์ œ
$cache = new SimpleFileCache('./cache', 600); // 10๋ถ„ TTL

// ๋ฐ์ดํ„ฐ ์ €์žฅ
$cache->set('user_profile_1000', [
    'name' => 'Alice',
    'email' => 'alice@example.com',
    'role' => 'admin'
]);

// ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
$profile = $cache->get('user_profile_1000');
if ($profile) {
    echo "User: " . $profile['name'];
} else {
    echo "Cache miss!";
}

// ๋งŒ๋ฃŒ๋œ ์บ์‹œ ์ •๋ฆฌ
$deleted = $cache->cleanup();
echo "Deleted {$deleted} expired cache files";
?>

๐ŸŽจ ํ”„๋ ˆ์ž„์›Œํฌ์˜ ํŒŒ์ผ ์บ์‹œ ํ™œ์šฉ


๋Œ€๋ถ€๋ถ„์˜ PHP ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์ด๋ฏธ ์ž˜ ๋งŒ๋“ค์–ด์ง„ ์บ์‹œ ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•ด. ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ํ”„๋ ˆ์ž„์›Œํฌ์˜ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ํ›จ์”ฌ ์•ˆ์ „ํ•˜๊ณ  ํšจ์œจ์ ์ด์•ผ! ๐Ÿš€

๐Ÿ“ Laravel์˜ ํŒŒ์ผ ์บ์‹œ ์‚ฌ์šฉ
<?php
use Illuminate\Support\Facades\Cache;

// ๋ฐ์ดํ„ฐ ์ €์žฅ (10๋ถ„)
Cache::put('products', $products, 600);

// ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
$products = Cache::get('products');

// ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
$products = Cache::get('products', []);

// ์˜๊ตฌ ์ €์žฅ
Cache::forever('site_settings', $settings);

// ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ
if (Cache::has('products')) {
    echo "Cache exists!";
}

// ์—†์œผ๋ฉด ๊ฐ€์ ธ์™€์„œ ์ €์žฅ (ํด๋กœ์ € ์‚ฌ์šฉ)
$products = Cache::remember('products', 600, function() {
    return DB::table('products')->get();
});

// ์‚ญ์ œ
Cache::forget('products');

// ์ „์ฒด ์‚ญ์ œ
Cache::flush();

// ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•œ ๊ทธ๋ฃน ๊ด€๋ฆฌ (Redis/Memcached๋งŒ ์ง€์›)
Cache::tags(['products', 'featured'])->put('featured_products', $data, 600);
Cache::tags(['products'])->flush(); // products ํƒœ๊ทธ์˜ ๋ชจ๋“  ์บ์‹œ ์‚ญ์ œ
?>
๐Ÿ“ Symfony์˜ ํŒŒ์ผ ์บ์‹œ ์‚ฌ์šฉ
<?php
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

// ํŒŒ์ผ ์บ์‹œ ์–ด๋Œ‘ํ„ฐ ์ƒ์„ฑ
$cache = new FilesystemAdapter(
    'app_cache',        // ๋„ค์ž„์ŠคํŽ˜์ด์Šค
    3600,               // ๊ธฐ๋ณธ TTL
    __DIR__ . '/cache'  // ์บ์‹œ ๋””๋ ‰ํ† ๋ฆฌ
);

// ์บ์‹œ ์•„์ดํ…œ ๊ฐ€์ ธ์˜ค๊ธฐ
$productsCache = $cache->getItem('products');

if (!$productsCache->isHit()) {
    // ์บ์‹œ ๋ฏธ์Šค - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ
    $products = fetchProductsFromDatabase();
    
    // ์บ์‹œ์— ์ €์žฅ
    $productsCache->set($products);
    $productsCache->expiresAfter(600); // 10๋ถ„
    $cache->save($productsCache);
} else {
    // ์บ์‹œ ํžˆํŠธ
    $products = $productsCache->get();
}

// ์—ฌ๋Ÿฌ ์•„์ดํ…œ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌ
$items = $cache->getItems(['product_1', 'product_2', 'product_3']);
foreach ($items as $key => $item) {
    if (!$item->isHit()) {
        $item->set(fetchProduct($key));
        $cache->save($item);
    }
}

// ์บ์‹œ ์‚ญ์ œ
$cache->deleteItem('products');

// ์ „์ฒด ์บ์‹œ ์‚ญ์ œ
$cache->clear();
?>

โš–๏ธ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ vs ํŒŒ์ผ ์บ์‹œ: ์–ธ์ œ ๋ฌด์—‡์„ ์„ ํƒํ• ๊นŒ?


์ž, ์ด์ œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์งˆ๋ฌธ์ด์•ผ! ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์™€ ํŒŒ์ผ ์บ์‹œ ์ค‘์—์„œ ์–ด๋–ค ๊ฑธ ์„ ํƒํ•ด์•ผ ํ• ๊นŒ? ์ •๋‹ต์€ "์ƒํ™ฉ์— ๋”ฐ๋ผ ๋‹ค๋ฅด๋‹ค"์•ผ. ๊ฐ๊ฐ์˜ ์žฅ๋‹จ์ ์„ ์ดํ•ดํ•˜๊ณ  ํ”„๋กœ์ ํŠธ์˜ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๊ฒŒ ์„ ํƒํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด! ๐Ÿค”

๐Ÿง  ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ๋ฅผ ์„ ํƒํ•ด์•ผ ํ•  ๋•Œ


โœ… ์ดˆ๊ณ ์† ์‘๋‹ต์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…, ๊ฒŒ์ž„ ์„œ๋ฒ„, ๊ธˆ์œต ๊ฑฐ๋ž˜ ๋“ฑ ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„์˜ ์‘๋‹ต ์‹œ๊ฐ„์ด ์ค‘์š”ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜


โœ… ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ๋ฐ์ดํ„ฐ
์„ธ์…˜ ์ •๋ณด, ์‹ค์‹œ๊ฐ„ ํ†ต๊ณ„, ์ž„์‹œ ํ† ํฐ ๋“ฑ ํœ˜๋ฐœ์„ฑ์ด ๋ฌธ์ œ๋˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ


โœ… ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ
๋ฆฌ์ŠคํŠธ, ์…‹, ํ•ด์‹œ ๋“ฑ์˜ ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ํ™œ์šฉํ•œ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•  ๋•Œ


โœ… ๋ถ„์‚ฐ ํ™˜๊ฒฝ
์—ฌ๋Ÿฌ ์›น ์„œ๋ฒ„๊ฐ€ ํ•˜๋‚˜์˜ ์บ์‹œ๋ฅผ ๊ณต์œ ํ•ด์•ผ ํ•˜๋Š” ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ ํ™˜๊ฒฝ


โœ… ๋™์‹œ์„ฑ ์ œ์–ด๊ฐ€ ์ค‘์š”ํ•œ ๊ฒฝ์šฐ
์›์ž์  ์—ฐ์‚ฐ(atomic operations)์ด ํ•„์š”ํ•œ ์นด์šดํ„ฐ, ์žฌ๊ณ  ๊ด€๋ฆฌ ๋“ฑ

๐Ÿ’พ ํŒŒ์ผ ์บ์‹œ๋ฅผ ์„ ํƒํ•ด์•ผ ํ•  ๋•Œ


โœ… ์˜์†์„ฑ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ
์„ค์ • ํŒŒ์ผ, ์ปดํŒŒ์ผ๋œ ํ…œํ”Œ๋ฆฟ, ์ •์  ์ฝ˜ํ…์ธ  ๋“ฑ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ํ›„์—๋„ ์œ ์ง€๋˜์–ด์•ผ ํ•˜๋Š” ๋ฐ์ดํ„ฐ


โœ… ์ธํ”„๋ผ ์ œ์•ฝ์ด ์žˆ๋Š” ๊ฒฝ์šฐ
๋ณ„๋„์˜ ์บ์‹œ ์„œ๋ฒ„๋ฅผ ์šด์˜ํ•  ์ˆ˜ ์—†๊ฑฐ๋‚˜, ์˜ˆ์‚ฐ์ด ์ œํ•œ์ ์ธ ์†Œ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ


โœ… ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ
์ด๋ฏธ์ง€ ์ธ๋„ค์ผ, ๋Œ€์šฉ๋Ÿ‰ ๋ณด๊ณ ์„œ ๋“ฑ ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฌ๊ธฐ์—” ํฐ ๋ฐ์ดํ„ฐ


โœ… ๋ณ€๊ฒฝ ๋นˆ๋„๊ฐ€ ๋‚ฎ์€ ๋ฐ์ดํ„ฐ
์ƒํ’ˆ ์นดํƒˆ๋กœ๊ทธ, ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด, ์ •์  ํŽ˜์ด์ง€ ๋“ฑ ์ž์ฃผ ๋ฐ”๋€Œ์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ


โœ… ๋‹จ์ผ ์„œ๋ฒ„ ํ™˜๊ฒฝ
ํŠธ๋ž˜ํ”ฝ์ด ์ ๊ณ  ํ•œ ๋Œ€์˜ ์„œ๋ฒ„๋กœ ์ถฉ๋ถ„ํ•œ ์†Œ๊ทœ๋ชจ ์›น์‚ฌ์ดํŠธ

๐ŸŽฏ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ „๋žต: ๋‘ ๊ฐ€์ง€๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ


์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์™€ ํŒŒ์ผ ์บ์‹œ๋ฅผ ๊ณ„์ธต์ ์œผ๋กœ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„! ์ด๋ฅผ ๋‹ค๋‹จ๊ณ„ ์บ์‹ฑ(Multi-tier Caching)์ด๋ผ๊ณ  ํ•ด. ๐ŸŽจ


์ „ํ˜•์ ์ธ 3๋‹จ๊ณ„ ์บ์‹ฑ ๊ตฌ์กฐ:


1๏ธโƒฃ L1 ์บ์‹œ (APCu): ํ”„๋กœ์„ธ์Šค ๋ ˆ๋ฒจ์˜ ์ดˆ๊ณ ์† ์บ์‹œ
2๏ธโƒฃ L2 ์บ์‹œ (Redis/Memcached): ์„œ๋ฒ„ ๊ฐ„ ๊ณต์œ ๋˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ
3๏ธโƒฃ L3 ์บ์‹œ (ํŒŒ์ผ ์บ์‹œ): ์˜์†์ ์ธ ๋ฐฑ์—… ์บ์‹œ


๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ๋•Œ๋Š” L1 โ†’ L2 โ†’ L3 โ†’ ์›๋ณธ ์ˆœ์„œ๋กœ ํ™•์ธํ•˜๊ณ , ์ €์žฅํ•  ๋•Œ๋Š” ๋ชจ๋“  ๋ ˆ๋ฒจ์— ๋™์‹œ์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์ด์•ผ!

๐Ÿ“ ๋‹ค๋‹จ๊ณ„ ์บ์‹ฑ ๊ตฌํ˜„ ์˜ˆ์ œ
<?php
class MultiTierCache {
    private $l1Cache; // APCu
    private $l2Cache; // Redis
    private $l3Cache; // File Cache
    
    public function __construct($redis, $fileCache) {
        $this->l2Cache = $redis;
        $this->l3Cache = $fileCache;
    }
    
    public function get($key) {
        // L1 ์บ์‹œ ํ™•์ธ (APCu)
        if (function_exists('apcu_fetch')) {
            $value = apcu_fetch($key, $success);
            if ($success) {
                return $value;
            }
        }
        
        // L2 ์บ์‹œ ํ™•์ธ (Redis)
        $value = $this->l2Cache->get($key);
        if ($value !== false) {
            // L1์— ์ €์žฅ
            if (function_exists('apcu_store')) {
                apcu_store($key, $value, 300); // 5๋ถ„
            }
            return $value;
        }
        
        // L3 ์บ์‹œ ํ™•์ธ (File)
        $value = $this->l3Cache->get($key);
        if ($value !== null) {
            // L1, L2์— ์ €์žฅ
            if (function_exists('apcu_store')) {
                apcu_store($key, $value, 300);
            }
            $this->l2Cache->setex($key, 600, $value); // 10๋ถ„
            return $value;
        }
        
        // ์บ์‹œ ๋ฏธ์Šค
        return null;
    }
    
    public function set($key, $value, $ttl = 3600) {
        // ๋ชจ๋“  ๋ ˆ๋ฒจ์— ์ €์žฅ
        if (function_exists('apcu_store')) {
            apcu_store($key, $value, min($ttl, 300)); // L1์€ ์ตœ๋Œ€ 5๋ถ„
        }
        
        $this->l2Cache->setex($key, min($ttl, 3600), $value); // L2๋Š” ์ตœ๋Œ€ 1์‹œ๊ฐ„
        $this->l3Cache->set($key, $value, $ttl); // L3๋Š” ์ „์ฒด TTL
        
        return true;
    }
    
    public function delete($key) {
        // ๋ชจ๋“  ๋ ˆ๋ฒจ์—์„œ ์‚ญ์ œ
        if (function_exists('apcu_delete')) {
            apcu_delete($key);
        }
        
        $this->l2Cache->del($key);
        $this->l3Cache->delete($key);
        
        return true;
    }
}

// ์‚ฌ์šฉ ์˜ˆ์ œ
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$fileCache = new SimpleFileCache('./cache');
$cache = new MultiTierCache($redis, $fileCache);

// ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ (์ž๋™์œผ๋กœ L1 โ†’ L2 โ†’ L3 ์ˆœ์„œ๋กœ ํ™•์ธ)
$userData = $cache->get('user_1000');

if ($userData === null) {
    // ์บ์‹œ ๋ฏธ์Šค - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ
    $userData = fetchUserFromDatabase(1000);
    
    // ๋ชจ๋“  ์บ์‹œ ๋ ˆ๋ฒจ์— ์ €์žฅ
    $cache->set('user_1000', $userData, 3600);
}

echo "User: " . $userData['name'];
?>

๐ŸŽ“ ์‹ค์ „ ์บ์‹ฑ ์ „๋žต๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค


์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ „์—์„œ ๋ฐ”๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์ฒด์ ์ธ ์ „๋žต๋“ค์„ ์•Œ์•„๋ณด์ž! ์ด ํŒ๋“ค์€ ๋‚ด๊ฐ€ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ๊ฒฝํ—˜ํ•˜๊ณ  ๊ฒ€์ฆํ•œ ๊ฒƒ๋“ค์ด์•ผ. ๐Ÿ’ก

1๏ธโƒฃ ์บ์‹œ ํ‚ค ๋„ค์ด๋ฐ ์ „๋žต


์บ์‹œ ํ‚ค๋ฅผ ์ž˜ ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ์€ ์ •๋ง ์ค‘์š”ํ•ด! ๋‚˜์ค‘์— ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฝ๊ณ , ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐Ÿ”‘


์ถ”์ฒœํ•˜๋Š” ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜:


โ€ข ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์‚ฌ์šฉ: app:module:entity:id ํ˜•์‹
โ€ข ๋ฒ„์ „ ํฌํ•จ: ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์‹œ ์ž๋™ ๋ฌดํšจํ™”๋ฅผ ์œ„ํ•ด v1:user:1000
โ€ข ๋ช…ํ™•ํ•œ ์˜๋ฏธ: user_profile_1000๋ณด๋‹ค๋Š” user:profile:1000
โ€ข ์ผ๊ด€์„ฑ ์œ ์ง€: ํ”„๋กœ์ ํŠธ ์ „์ฒด์—์„œ ๋™์ผํ•œ ๊ทœ์น™ ์ ์šฉ

๐Ÿ“ ์บ์‹œ ํ‚ค ์ƒ์„ฑ ํ—ฌํผ ํ•จ์ˆ˜
<?php
class CacheKeyBuilder {
    private $namespace;
    private $version;
    
    public function __construct($namespace = 'app', $version = 'v1') {
        $this->namespace = $namespace;
        $this->version = $version;
    }
    
    public function build($entity, $id = null, $suffix = null) {
        $parts = [$this->namespace, $this->version, $entity];
        
        if ($id !== null) {
            $parts[] = $id;
        }
        
        if ($suffix !== null) {
            $parts[] = $suffix;
        }
        
        return implode(':', $parts);
    }
    
    public function buildQuery($entity, array $params) {
        $queryString = http_build_query($params);
        $hash = md5($queryString);
        return $this->build($entity, null, 'query:' . $hash);
    }
}

// ์‚ฌ์šฉ ์˜ˆ์ œ
$keyBuilder = new CacheKeyBuilder('myapp', 'v2');

$userKey = $keyBuilder->build('user', 1000);
// ๊ฒฐ๊ณผ: "myapp:v2:user:1000"

$userProfileKey = $keyBuilder->build('user', 1000, 'profile');
// ๊ฒฐ๊ณผ: "myapp:v2:user:1000:profile"

$searchKey = $keyBuilder->buildQuery('products', [
    'category' => 'electronics',
    'price_min' => 100,
    'price_max' => 500
]);
// ๊ฒฐ๊ณผ: "myapp:v2:products:query:a1b2c3d4..."
?>

2๏ธโƒฃ ์บ์‹œ ์›Œ๋ฐ(Cache Warming)


์„œ๋ฒ„๋ฅผ ์žฌ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜ ์บ์‹œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•œ ์งํ›„์—๋Š” ๋ชจ๋“  ์š”์ฒญ์ด ์บ์‹œ ๋ฏธ์Šค๊ฐ€ ๋˜์–ด ์„ฑ๋Šฅ์ด ๊ธ‰๊ฒฉํžˆ ๋–จ์–ด์ ธ. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋ฏธ๋ฆฌ ์บ์‹œ๋ฅผ ์ฑ„์›Œ๋‘๋Š” ๊ฒƒ์„ ์บ์‹œ ์›Œ๋ฐ์ด๋ผ๊ณ  ํ•ด! ๐Ÿ”ฅ

๐Ÿ“ ์บ์‹œ ์›Œ๋ฐ ์Šคํฌ๋ฆฝํŠธ
<?php
class CacheWarmer {
    private $cache;
    private $db;
    
    public function __construct($cache, $db) {
        $this->cache = $cache;
        $this->db = $db;
    }
    
    public function warmPopularProducts() {
        echo "Warming popular products cache...\n";
        
        // ์ธ๊ธฐ ์ƒํ’ˆ 100๊ฐœ ๊ฐ€์ ธ์˜ค๊ธฐ
        $products = $this->db->query(
            "SELECT * FROM products ORDER BY views DESC LIMIT 100"
        )->fetchAll();
        
        foreach ($products as $product) {
            $key = "product:{$product['id']}";
            $this->cache->set($key, $product, 3600);
            echo "Cached: {$key}\n";
        }
        
        echo "Done!\n";
    }
    
    public function warmCategories() {
        echo "Warming categories cache...\n";
        
        $categories = $this->db->query(
            "SELECT * FROM categories WHERE active = 1"
        )->fetchAll();
        
        $this->cache->set('categories:all', $categories, 7200);
        
        foreach ($categories as $category) {
            $key = "category:{$category['id']}";
            $this->cache->set($key, $category, 7200);
        }
        
        echo "Done!\n";
    }
    
    public function warmAll() {
        $this->warmPopularProducts();
        $this->warmCategories();
        // ํ•„์š”ํ•œ ๋‹ค๋ฅธ ์บ์‹œ๋“ค๋„ ์ถ”๊ฐ€...
    }
}

// CLI์—์„œ ์‹คํ–‰
if (php_sapi_name() === 'cli') {
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    
    $pdo = new PDO('mysql:host=localhost;dbname=myapp', 'user', 'pass');
    
    $warmer = new CacheWarmer($redis, $pdo);
    $warmer->warmAll();
}
?>

3๏ธโƒฃ ์บ์‹œ ์Šคํƒฌํ”ผ๋“œ(Cache Stampede) ๋ฐฉ์ง€


์ธ๊ธฐ ์žˆ๋Š” ์บ์‹œ๊ฐ€ ๋งŒ๋ฃŒ๋˜๋Š” ์ˆœ๊ฐ„, ์ˆ˜๋งŽ์€ ์š”์ฒญ์ด ๋™์‹œ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๋ชฐ๋ ค๊ฐ€๋Š” ํ˜„์ƒ์„ ์บ์‹œ ์Šคํƒฌํ”ผ๋“œ๋ผ๊ณ  ํ•ด. ์ด๋Š” ์„œ๋ฒ„๋ฅผ ๋‹ค์šด์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ์•ผ! ๐Ÿ˜ฑ


ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:


โ€ข ํ™•๋ฅ ์  ์กฐ๊ธฐ ๋งŒ๋ฃŒ: TTL์ด ๋๋‚˜๊ธฐ ์ „์— ํ™•๋ฅ ์ ์œผ๋กœ ๊ฐฑ์‹ 
โ€ข ๋ฝ(Lock) ๋ฉ”์ปค๋‹ˆ์ฆ˜: ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ๋‚˜๋จธ์ง€๋Š” ๋Œ€๊ธฐ
โ€ข ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๊ฐฑ์‹ : ๋งŒ๋ฃŒ ์ „์— ๋น„๋™๊ธฐ๋กœ ๋ฏธ๋ฆฌ ๊ฐฑ์‹ 

๐Ÿ“ ์บ์‹œ ์Šคํƒฌํ”ผ๋“œ ๋ฐฉ์ง€ ๊ตฌํ˜„
<?php
class StampedeProofCache {
    private $cache;
    private $lockTimeout = 10; // ๋ฝ ํƒ€์ž„์•„์›ƒ (์ดˆ)
    
    public function __construct($cache) {
        $this->cache = $cache;
    }
    
    /**
     * ๋ฝ์„ ์‚ฌ์šฉํ•œ ์•ˆ์ „ํ•œ ์บ์‹œ ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function remember($key, $ttl, callable $callback) {
        // ์บ์‹œ ํ™•์ธ
        $value = $this->cache->get($key);
        if ($value !== null) {
            return $value;
        }
        
        // ๋ฝ ํ‚ค
        $lockKey = $key . ':lock';
        
        // ๋ฝ ํš๋“ ์‹œ๋„
        $lockAcquired = $this->acquireLock($lockKey);
        
        if ($lockAcquired) {
            try {
                // ๋‹ค์‹œ ํ•œ ๋ฒˆ ์บ์‹œ ํ™•์ธ (๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ด๋ฏธ ์ €์žฅํ–ˆ์„ ์ˆ˜ ์žˆ์Œ)
                $value = $this->cache->get($key);
                if ($value !== null) {
                    return $value;
                }
                
                // ์ฝœ๋ฐฑ ์‹คํ–‰ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
                $value = $callback();
                
                // ์บ์‹œ์— ์ €์žฅ
                $this->cache->set($key, $value, $ttl);
                
                return $value;
            } finally {
                // ๋ฝ ํ•ด์ œ
                $this->releaseLock($lockKey);
            }
        } else {
            // ๋ฝ ํš๋“ ์‹คํŒจ - ๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘
            // ์งง์€ ์‹œ๊ฐ„ ๋Œ€๊ธฐ ํ›„ ์žฌ์‹œ๋„
            $maxRetries = 20;
            $retryDelay = 100000; // 0.1์ดˆ (๋งˆ์ดํฌ๋กœ์ดˆ)
            
            for ($i = 0; $i < $maxRetries; $i++) {
                usleep($retryDelay);
                
                $value = $this->cache->get($key);
                if ($value !== null) {
                    return $value;
                }
            }
            
            // ํƒ€์ž„์•„์›ƒ - ์–ด์ฉ” ์ˆ˜ ์—†์ด ์ง์ ‘ ๊ฐ€์ ธ์˜ค๊ธฐ
            return $callback();
        }
    }
    
    /**
     * ๋ฝ ํš๋“
     */
    private function acquireLock($lockKey) {
        // Redis์˜ SETNX๋ฅผ ์‚ฌ์šฉํ•œ ๋ฝ (SET if Not eXists)
        return $this->cache->set(
            $lockKey,
            1,
            ['nx', 'ex' => $this->lockTimeout]
        );
    }
    
    /**
     * ๋ฝ ํ•ด์ œ
     */
    private function releaseLock($lockKey) {
        $this->cache->del($lockKey);
    }
    
    /**
     * ํ™•๋ฅ ์  ์กฐ๊ธฐ ๋งŒ๋ฃŒ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐฉ๋ฒ•
     */
    public function rememberWithProbabilisticExpiry($key, $ttl, callable $callback) {
        $data = $this->cache->get($key . ':data');
        
        if ($data !== null) {
            $stored = unserialize($data);
            $value = $stored['value'];
            $createdAt = $stored['created_at'];
            $age = time() - $createdAt;
            
            // ํ™•๋ฅ ์  ์กฐ๊ธฐ ๋งŒ๋ฃŒ ๊ณ„์‚ฐ
            // TTL์˜ 80%๊ฐ€ ์ง€๋‚˜๋ฉด ๊ฐฑ์‹  ํ™•๋ฅ ์ด ์ฆ๊ฐ€
            if ($age > $ttl * 0.8) {
                $probability = ($age - $ttl * 0.8) / ($ttl * 0.2);
                
                if (mt_rand() / mt_getrandmax() < $probability) {
                    // ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ฐฑ์‹  (๋น„๋™๊ธฐ)
                    $this->refreshInBackground($key, $ttl, $callback);
                }
            }
            
            return $value;
        }
        
        // ์บ์‹œ ๋ฏธ์Šค - ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
        $value = $callback();
        
        $data = serialize([
            'value' => $value,
            'created_at' => time()
        ]);
        
        $this->cache->set($key . ':data', $data, $ttl);
        
        return $value;
    }
    
    /**
     * ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๊ฐฑ์‹  (๊ฐ„๋‹จํ•œ ๊ตฌํ˜„)
     */
    private function refreshInBackground($key, $ttl, callable $callback) {
        // ์‹ค์ œ๋กœ๋Š” ํ ์‹œ์Šคํ…œ์ด๋‚˜ ๋น„๋™๊ธฐ ์ž‘์—…์ž๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•จ
        // ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํžˆ ์ฆ‰์‹œ ๊ฐฑ์‹ 
        $value = $callback();
        
        $data = serialize([
            'value' => $value,
            'created_at' => time()
        ]);
        
        $this->cache->set($key . ':data', $data, $ttl);
    }
}

// ์‚ฌ์šฉ ์˜ˆ์ œ
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$cache = new StampedeProofCache($redis);

// ์•ˆ์ „ํ•˜๊ฒŒ ์บ์‹œ ์‚ฌ์šฉ
$products = $cache->remember('popular_products', 600, function() {
    // ๋ฌด๊ฑฐ์šด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ
    return fetchPopularProductsFromDatabase();
});
?>

4๏ธโƒฃ ์บ์‹œ ๋ฌดํšจํ™” ์ „๋žต


์บ์‹œ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฒƒ๋งŒํผ ์ค‘์š”ํ•œ ๊ฒŒ ์ ์ ˆํ•œ ์‹œ์ ์— ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•˜๋Š” ๊ฑฐ์•ผ. ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ๋‚˜๋น ์ง€๊ฑฐ๋“ ! ๐Ÿ”„


์ฃผ์š” ๋ฌดํšจํ™” ์ „๋žต:


โ€ข TTL ๊ธฐ๋ฐ˜: ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ์ž๋™์œผ๋กœ ๋งŒ๋ฃŒ
โ€ข ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜: ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์ฆ‰์‹œ ์บ์‹œ ์‚ญ์ œ
โ€ข ํƒœ๊ทธ ๊ธฐ๋ฐ˜: ๊ด€๋ จ๋œ ์บ์‹œ๋“ค์„ ๊ทธ๋ฃน์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์ผ๊ด„ ์‚ญ์ œ
โ€ข ๋ฒ„์ „ ๊ธฐ๋ฐ˜: ์บ์‹œ ํ‚ค์— ๋ฒ„์ „์„ ํฌํ•จ์‹œ์ผœ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์‹œ ์ž๋™ ๋ฌดํšจํ™”

๐Ÿ“ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์บ์‹œ ๋ฌดํšจํ™”
<?php
class CacheInvalidator {
    private $cache;
    
    public function __construct($cache) {
        $this->cache = $cache;
    }
    
    /**
     * ์‚ฌ์šฉ์ž ์ •๋ณด ์—…๋ฐ์ดํŠธ ์‹œ ๊ด€๋ จ ์บ์‹œ ๋ฌดํšจํ™”
     */
    public function onUserUpdated($userId) {
        $patterns = [
            "user:{$userId}",
            "user:{$userId}:profile",
            "user:{$userId}:posts",
            "user:{$userId}:comments",
            "user:{$userId}:followers",
            "user:{$userId}:following"
        ];
        
        foreach ($patterns as $key) {
            $this->cache->del($key);
        }
        
        // ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์บ์‹œ๋„ ๋ฌดํšจํ™”
        $this->cache->del('users:list');
        $this->cache->del('users:active');
    }
    
    /**
     * ์ƒํ’ˆ ์ •๋ณด ์—…๋ฐ์ดํŠธ ์‹œ ๊ด€๋ จ ์บ์‹œ ๋ฌดํšจํ™”
     */
    public function onProductUpdated($productId) {
        $product = $this->getProduct($productId);
        
        $patterns = [
            "product:{$productId}",
            "product:{$productId}:details",
            "category:{$product['category_id']}:products",
            "products:featured",
            "products:new"
        ];
        
        foreach ($patterns as $key) {
            $this->cache->del($key);
        }
    }
    
    /**
     * ์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ ์‹œ ๊ด€๋ จ ์บ์‹œ ๋ฌดํšจํ™”
     */
    public function onCategoryUpdated($categoryId) {
        // ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋ชจ๋“  ์ƒํ’ˆ ์บ์‹œ ๋ฌดํšจํ™”
        $keys = $this->cache->keys("category:{$categoryId}:*");
        
        if (!empty($keys)) {
            $this->cache->del(...$keys);
        }
        
        // ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ์บ์‹œ ๋ฌดํšจํ™”
        $this->cache->del('categories:all');
        $this->cache->del('categories:tree');
    }
    
    /**
     * ์™€์ผ๋“œ์นด๋“œ ํŒจํ„ด์œผ๋กœ ์บ์‹œ ์‚ญ์ œ
     */
    public function deleteByPattern($pattern) {
        $keys = $this->cache->keys($pattern);
        
        if (!empty($keys)) {
            return $this->cache->del(...$keys);
        }
        
        return 0;
    }
    
    private function getProduct($productId) {
        // ์‹ค์ œ๋กœ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜ด
        return ['category_id' => 10];
    }
}

// ์‚ฌ์šฉ ์˜ˆ์ œ - ๋ชจ๋ธ์— ํ†ตํ•ฉ
class UserModel {
    private $db;
    private $cache;
    private $invalidator;
    
    public function __construct($db, $cache) {
        $this->db = $db;
        $this->cache = $cache;
        $this->invalidator = new CacheInvalidator($cache);
    }
    
    public function updateUser($userId, array $data) {
        // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—…๋ฐ์ดํŠธ
        $stmt = $this->db->prepare(
            "UPDATE users SET name = ?, email = ? WHERE id = ?"
        );
        $stmt->execute([$data['name'], $data['email'], $userId]);
        
        // ์บ์‹œ ๋ฌดํšจํ™”
        $this->invalidator->onUserUpdated($userId);
        
        return true;
    }
}
?>

๐Ÿ’ก ํ”„๋กœ ํŒ: ์บ์‹œ ๋ฌดํšจํ™”๋Š” ๋ณด์ˆ˜์ ์œผ๋กœ!


ํ™•์‹คํ•˜์ง€ ์•Š์œผ๋ฉด ๊ด€๋ จ๋œ ์บ์‹œ๋ฅผ ๋ชจ๋‘ ์‚ญ์ œํ•˜๋Š” ๊ฒŒ ์•ˆ์ „ํ•ด. ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๋ณด๋‹ค ์บ์‹œ ๋ฏธ์Šค๊ฐ€ ๋‚˜์€ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๊ฑฐ๋“ . ํ•˜์ง€๋งŒ ๋„ˆ๋ฌด ๋งŽ์€ ์บ์‹œ๋ฅผ ์‚ญ์ œํ•˜๋ฉด ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์œผ๋‹ˆ ๊ท ํ˜•์„ ์ž˜ ๋งž์ถฐ์•ผ ํ•ด!

๐Ÿ“Š ์บ์‹œ ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์ตœ์ ํ™”


์บ์‹œ๋ฅผ ๊ตฌ์ถ•ํ–ˆ๋‹ค๋ฉด ์ด์ œ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด! ์บ์‹œ ํžˆํŠธ์œจ, ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰, ์‘๋‹ต ์‹œ๊ฐ„ ๋“ฑ์„ ์ถ”์ ํ•ด์•ผ ํ•ด. ๐Ÿ“ˆ

๐ŸŽฏ ์ฃผ์š” ๋ชจ๋‹ˆํ„ฐ๋ง ์ง€ํ‘œ


1. ์บ์‹œ ํžˆํŠธ์œจ (Cache Hit Ratio)
์ „์ฒด ์š”์ฒญ ์ค‘ ์บ์‹œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์€ ๋น„์œจ. ์ผ๋ฐ˜์ ์œผ๋กœ 80% ์ด์ƒ์ด๋ฉด ์ข‹์•„!


๊ณ„์‚ฐ ๊ณต์‹: (์บ์‹œ ํžˆํŠธ ์ˆ˜ / ์ „์ฒด ์š”์ฒญ ์ˆ˜) ร— 100


2. ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„
์บ์‹œ ํžˆํŠธ ์‹œ์™€ ์บ์‹œ ๋ฏธ์Šค ์‹œ์˜ ์‘๋‹ต ์‹œ๊ฐ„ ์ฐจ์ด๋ฅผ ์ธก์ •ํ•ด.


3. ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋ฅ 
์บ์‹œ ์„œ๋ฒ„์˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•ด์„œ ์ ์ ˆํ•œ ์šฉ๋Ÿ‰์„ ์œ ์ง€ํ•ด.


4. ๋งŒ๋ฃŒ/์‚ญ์ œ ๋น„์œจ
TTL๋กœ ๋งŒ๋ฃŒ๋˜๋Š” ํ‚ค์™€ ๋ช…์‹œ์ ์œผ๋กœ ์‚ญ์ œ๋˜๋Š” ํ‚ค์˜ ๋น„์œจ์„ ํ™•์ธํ•ด.

๐Ÿ“ ์บ์‹œ ํ†ต๊ณ„ ์ˆ˜์ง‘ ํด๋ž˜์Šค
<?php
class CacheStatistics {
    private $cache;
    private $statsKey = 'cache:stats';
    
    public function __construct($cache) {
        $this->cache = $cache;
    }
    
    /**
     * ์บ์‹œ ํžˆํŠธ ๊ธฐ๋ก
     */
    public function recordHit($key) {
        $this->cache->hIncrBy($this->statsKey, 'hits', 1);
        $this->cache->hIncrBy($this->statsKey, 'total', 1);
        
        // ์ตœ๊ทผ ํžˆํŠธ ํ‚ค ๊ธฐ๋ก
        $this->cache->lPush('cache:recent_hits', $key);
        $this->cache->lTrim('cache:recent_hits', 0, 99); // ์ตœ๊ทผ 100๊ฐœ๋งŒ ์œ ์ง€
    }
    
    /**
     * ์บ์‹œ ๋ฏธ์Šค ๊ธฐ๋ก
     */
    public function recordMiss($key) {
        $this->cache->hIncrBy($this->statsKey, 'misses', 1);
        $this->cache->hIncrBy($this->statsKey, 'total', 1);
        
        // ์ตœ๊ทผ ๋ฏธ์Šค ํ‚ค ๊ธฐ๋ก
        $this->cache->lPush('cache:recent_misses', $key);
        $this->cache->lTrim('cache:recent_misses', 0, 99);
    }
    
    /**
     * ์‘๋‹ต ์‹œ๊ฐ„ ๊ธฐ๋ก
     */
    public function recordResponseTime($key, $milliseconds, $isHit) {
        $type = $isHit ? 'hit' : 'miss';
        $this->cache->lPush("cache:response_times:{$type}", $milliseconds);
        $this->cache->lTrim("cache:response_times:{$type}", 0, 999); // ์ตœ๊ทผ 1000๊ฐœ
    }
    
    /**
     * ํ†ต๊ณ„ ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function getStats() {
        $stats = $this->cache->hGetAll($this->statsKey);
        
        $hits = (int)($stats['hits'] ?? 0);
        $misses = (int)($stats['misses'] ?? 0);
        $total = (int)($stats['total'] ?? 0);
        
        $hitRatio = $total > 0 ? ($hits / $total) * 100 : 0;
        
        // ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„ ๊ณ„์‚ฐ
        $hitTimes = $this->cache->lRange('cache:response_times:hit', 0, -1);
        $missTimes = $this->cache->lRange('cache:response_times:miss', 0, -1);
        
        $avgHitTime = !empty($hitTimes) ? array_sum($hitTimes) / count($hitTimes) : 0;
        $avgMissTime = !empty($missTimes) ? array_sum($missTimes) / count($missTimes) : 0;
        
        return [
            'hits' => $hits,
            'misses' => $misses,
            'total' => $total,
            'hit_ratio' => round($hitRatio, 2),
            'avg_hit_time_ms' => round($avgHitTime, 2),
            'avg_miss_time_ms' => round($avgMissTime, 2),
            'performance_gain' => $avgMissTime > 0 ? 
                round(($avgMissTime - $avgHitTime) / $avgMissTime * 100, 2) : 0
        ];
    }
    
    /**
     * ํ†ต๊ณ„ ์ดˆ๊ธฐํ™”
     */
    public function reset() {
        $this->cache->del($this->statsKey);
        $this->cache->del('cache:recent_hits');
        $this->cache->del('cache:recent_misses');
        $this->cache->del('cache:response_times:hit');
        $this->cache->del('cache:response_times:miss');
    }
    
    /**
     * ๊ฐ€์žฅ ๋งŽ์ด ๋ฏธ์Šค๋œ ํ‚ค ๋ถ„์„
     */
    public function getTopMissedKeys($limit = 10) {
        $recentMisses = $this->cache->lRange('cache:recent_misses', 0, -1);
        $counts = array_count_values($recentMisses);
        arsort($counts);
        
        return array_slice($counts, 0, $limit, true);
    }
}

// ์บ์‹œ ๋ž˜ํผ ํด๋ž˜์Šค (ํ†ต๊ณ„ ์ˆ˜์ง‘ ํฌํ•จ)
class MonitoredCache {
    private $cache;
    private $stats;
    
    public function __construct($cache) {
        $this->cache = $cache;
        $this->stats = new CacheStatistics($cache);
    }
    
    public function get($key) {
        $startTime = microtime(true);
        $value = $this->cache->get($key);
        $endTime = microtime(true);
        
        $responseTime = ($endTime - $startTime) * 1000; // ๋ฐ€๋ฆฌ์ดˆ
        
        if ($value !== false && $value !== null) {
            $this->stats->recordHit($key);
            $this->stats->recordResponseTime($key, $responseTime, true);
        } else {
            $this->stats->recordMiss($key);
            $this->stats->recordResponseTime($key, $responseTime, false);
        }
        
        return $value;
    }
    
    public function set($key, $value, $ttl = 3600) {
        return $this->cache->setex($key, $ttl, $value);
    }
    
    public function getStatistics() {
        return $this->stats->getStats();
    }
}

// ์‚ฌ์šฉ ์˜ˆ์ œ
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$cache = new MonitoredCache($redis);

// ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ
$userData = $cache->get('user:1000');

// ํ†ต๊ณ„ ํ™•์ธ
$stats = $cache->getStatistics();
echo "Cache Hit Ratio: {$stats['hit_ratio']}%\n";
echo "Average Hit Time: {$stats['avg_hit_time_ms']}ms\n";
echo "Average Miss Time: {$stats['avg_miss_time_ms']}ms\n";
echo "Performance Gain: {$stats['performance_gain']}%\n";
?>

๐Ÿš€ ์‹ค์ „ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•˜๊ธฐ


์ด๋ก ๊ณผ ์˜ˆ์ œ๋ฅผ ์ถฉ๋ถ„ํžˆ ๋ดค์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์–ด๋–ป๊ฒŒ ์ ์šฉํ•˜๋Š”์ง€ ๊ตฌ์ฒด์ ์ธ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์‚ดํŽด๋ณด์ž! ๐ŸŽฏ

์‹œ๋‚˜๋ฆฌ์˜ค 1: ์ „์ž์ƒ๊ฑฐ๋ž˜ ์‚ฌ์ดํŠธ ๐Ÿ›’


์š”๊ตฌ์‚ฌํ•ญ:

โ€ข ์ˆ˜์ฒœ ๊ฐœ์˜ ์ƒํ’ˆ ์ •๋ณด
โ€ข ์‹ค์‹œ๊ฐ„ ์žฌ๊ณ  ๊ด€๋ฆฌ
โ€ข ๊ฐœ์ธํ™”๋œ ์ถ”์ฒœ
โ€ข ๋†’์€ ํŠธ๋ž˜ํ”ฝ


์บ์‹ฑ ์ „๋žต:


1๏ธโƒฃ ์ƒํ’ˆ ์ •๋ณด (Redis): ์ž์ฃผ ์กฐํšŒ๋˜๋Š” ์ƒํ’ˆ์€ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์— ์ €์žฅ (TTL: 1์‹œ๊ฐ„)
2๏ธโƒฃ ์นดํ…Œ๊ณ ๋ฆฌ ํŠธ๋ฆฌ (ํŒŒ์ผ ์บ์‹œ): ๊ฑฐ์˜ ๋ณ€ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ํŒŒ์ผ๋กœ ์ €์žฅ (TTL: 24์‹œ๊ฐ„)
3๏ธโƒฃ ์žฌ๊ณ  ์ •๋ณด (Redis): ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•˜๋ฏ€๋กœ ์งง์€ TTL (TTL: 1๋ถ„)
4๏ธโƒฃ ์‚ฌ์šฉ์ž ์žฅ๋ฐ”๊ตฌ๋‹ˆ (Redis): ์„ธ์…˜ ๊ธฐ๋ฐ˜์œผ๋กœ ๋น ๋ฅธ ์ ‘๊ทผ ํ•„์š” (TTL: ์„ธ์…˜ ์œ ์ง€ ์‹œ๊ฐ„)
5๏ธโƒฃ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ (Redis): ๋™์ผํ•œ ๊ฒ€์ƒ‰์–ด๋Š” ์บ์‹ฑ (TTL: 10๋ถ„)

๐Ÿ“ ์ „์ž์ƒ๊ฑฐ๋ž˜ ์บ์‹œ ๊ตฌํ˜„
<?php
class EcommerceCache {
    private $redis;
    private $fileCache;
    
    public function __construct($redis, $fileCache) {
        $this->redis = $redis;
        $this->fileCache = $fileCache;
    }
    
    /**
     * ์ƒํ’ˆ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function getProduct($productId) {
        $key = "product:{$productId}";
        
        // Redis์—์„œ ํ™•์ธ
        $product = $this->redis->get($key);
        if ($product !== false) {
            return json_decode($product, true);
        }
        
        // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ
        $product = $this->fetchProductFromDB($productId);
        
        // Redis์— ์ €์žฅ (1์‹œ๊ฐ„)
        $this->redis->setex($key, 3600, json_encode($product));
        
        return $product;
    }
    
    /**
     * ์นดํ…Œ๊ณ ๋ฆฌ ํŠธ๋ฆฌ ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function getCategoryTree() {
        $key = 'categories:tree';
        
        // ํŒŒ์ผ ์บ์‹œ์—์„œ ํ™•์ธ
        $tree = $this->fileCache->get($key);
        if ($tree !== null) {
            return $tree;
        }
        
        // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ
        $tree = $this->buildCategoryTree();
        
        // ํŒŒ์ผ ์บ์‹œ์— ์ €์žฅ (24์‹œ๊ฐ„)
        $this->fileCache->set($key, $tree, 86400);
        
        return $tree;
    }
    
    /**
     * ์žฌ๊ณ  ํ™•์ธ
     */
    public function getStock($productId) {
        $key = "stock:{$productId}";
        
        // Redis์—์„œ ํ™•์ธ (์งง์€ TTL)
        $stock = $this->redis->get($key);
        if ($stock !== false) {
            return (int)$stock;
        }
        
        // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ
        $stock = $this->fetchStockFromDB($productId);
        
        // Redis์— ์ €์žฅ (1๋ถ„)
        $this->redis->setex($key, 60, $stock);
        
        return $stock;
    }
    
    /**
     * ์žฌ๊ณ  ๊ฐ์†Œ (์›์ž์  ์—ฐ์‚ฐ)
     */
    public function decrementStock($productId, $quantity = 1) {
        $key = "stock:{$productId}";
        
        // Redis์—์„œ ์›์ž์ ์œผ๋กœ ๊ฐ์†Œ
        $newStock = $this->redis->decrBy($key, $quantity);
        
        // ์Œ์ˆ˜๊ฐ€ ๋˜๋ฉด ๋กค๋ฐฑ
        if ($newStock < 0) {
            $this->redis->incrBy($key, $quantity);
            return false;
        }
        
        // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋„ ์—…๋ฐ์ดํŠธ (๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒŒ ์ข‹์Œ)
        $this->updateStockInDB($productId, $newStock);
        
        return true;
    }
    
    /**
     * ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function getCart($userId) {
        $key = "cart:{$userId}";
        
        $cart = $this->redis->get($key);
        if ($cart !== false) {
            return json_decode($cart, true);
        }
        
        return [];
    }
    
    /**
     * ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์ƒํ’ˆ ์ถ”๊ฐ€
     */
    public function addToCart($userId, $productId, $quantity) {
        $key = "cart:{$userId}";
        
        $cart = $this->getCart($userId);
        
        if (isset($cart[$productId])) {
            $cart[$productId]['quantity'] += $quantity;
        } else {
            $cart[$productId] = [
                'product_id' => $productId,
                'quantity' => $quantity,
                'added_at' => time()
            ];
        }
        
        // Redis์— ์ €์žฅ (2์‹œ๊ฐ„)
        $this->redis->setex($key, 7200, json_encode($cart));
        
        return $cart;
    }
    
    /**
     * ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์บ์‹ฑ
     */
    public function searchProducts($query, $filters = []) {
        $cacheKey = 'search:' . md5($query . serialize($filters));
        
        // Redis์—์„œ ํ™•์ธ
        $results = $this->redis->get($cacheKey);
        if ($results !== false) {
            return json_decode($results, true);
        }
        
        // ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰
        $results = $this->performSearch($query, $filters);
        
        // Redis์— ์ €์žฅ (10๋ถ„)
        $this->redis->setex($cacheKey, 600, json_encode($results));
        
        return $results;
    }
    
    // ํ—ฌํผ ๋ฉ”์„œ๋“œ๋“ค (์‹ค์ œ๋กœ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ)
    private function fetchProductFromDB($productId) {
        // ์‹ค์ œ ๊ตฌํ˜„...
        return ['id' => $productId, 'name' => 'Sample Product'];
    }
    
    private function buildCategoryTree() {
        // ์‹ค์ œ ๊ตฌํ˜„...
        return [];
    }
    
    private function fetchStockFromDB($productId) {
        // ์‹ค์ œ ๊ตฌํ˜„...
        return 100;
    }
    
    private function updateStockInDB($productId, $stock) {
        // ์‹ค์ œ ๊ตฌํ˜„...
    }
    
    private function performSearch($query, $filters) {
        // ์‹ค์ œ ๊ตฌํ˜„...
        return [];
    }
}
?>

์‹œ๋‚˜๋ฆฌ์˜ค 2: ์†Œ์…œ ๋ฏธ๋””์–ด ํ”Œ๋žซํผ ๐Ÿ“ฑ


์š”๊ตฌ์‚ฌํ•ญ:

โ€ข ์‹ค์‹œ๊ฐ„ ํ”ผ๋“œ
โ€ข ์ข‹์•„์š”/๋Œ“๊ธ€ ์นด์šดํ„ฐ
โ€ข ์‚ฌ์šฉ์ž ํ”„๋กœํ•„
โ€ข ์•Œ๋ฆผ ์‹œ์Šคํ…œ


์บ์‹ฑ ์ „๋žต:


1๏ธโƒฃ ์‚ฌ์šฉ์ž ํ”ผ๋“œ (Redis List): ์ตœ๊ทผ ๊ฒŒ์‹œ๋ฌผ ID ๋ชฉ๋ก ์ €์žฅ (TTL: 5๋ถ„)
2๏ธโƒฃ ์ข‹์•„์š” ์นด์šดํ„ฐ (Redis): ์›์ž์  ์ฆ๊ฐ€/๊ฐ์†Œ ์—ฐ์‚ฐ ํ™œ์šฉ
3๏ธโƒฃ ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ (Redis Hash): ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ ์ €์žฅ (TTL: 30๋ถ„)
4๏ธโƒฃ ์•Œ๋ฆผ (Redis Sorted Set): ํƒ€์ž„์Šคํƒฌํ”„ ๊ธฐ๋ฐ˜ ์ •๋ ฌ
5๏ธโƒฃ ์ธ๊ธฐ ๊ฒŒ์‹œ๋ฌผ (Redis Sorted Set): ์ ์ˆ˜ ๊ธฐ๋ฐ˜ ๋žญํ‚น

๐Ÿ“ ์†Œ์…œ ๋ฏธ๋””์–ด ์บ์‹œ ๊ตฌํ˜„
<?php
class SocialMediaCache {
    private $redis;
    
    public function __construct($redis) {
        $this->redis = $redis;
    }
    
    /**
     * ์‚ฌ์šฉ์ž ํ”ผ๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function getUserFeed($userId, $limit = 20) {
        $key = "feed:{$userId}";
        
        // Redis List์—์„œ ์ตœ๊ทผ ๊ฒŒ์‹œ๋ฌผ ID ๊ฐ€์ ธ์˜ค๊ธฐ
        $postIds = $this->redis->lRange($key, 0, $limit - 1);
        
        if (empty($postIds)) {
            // ํ”ผ๋“œ ์ƒ์„ฑ
            $postIds = $this->generateFeed($userId);
            
            // Redis์— ์ €์žฅ
            foreach ($postIds as $postId) {
                $this->redis->rPush($key, $postId);
            }
            $this->redis->expire($key, 300); // 5๋ถ„
        }
        
        // ๊ฒŒ์‹œ๋ฌผ ์ƒ์„ธ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
        $posts = [];
        foreach ($postIds as $postId) {
            $posts[] = $this->getPost($postId);
        }
        
        return $posts;
    }
    
    /**
     * ์ƒˆ ๊ฒŒ์‹œ๋ฌผ์„ ํŒ”๋กœ์›Œ๋“ค์˜ ํ”ผ๋“œ์— ์ถ”๊ฐ€
     */
    public function fanoutPost($authorId, $postId) {
        // ํŒ”๋กœ์›Œ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
        $followers = $this->getFollowers($authorId);
        
        // ๊ฐ ํŒ”๋กœ์›Œ์˜ ํ”ผ๋“œ์— ์ถ”๊ฐ€
        foreach ($followers as $followerId) {
            $key = "feed:{$followerId}";
            $this->redis->lPush($key, $postId);
            $this->redis->lTrim($key, 0, 99); // ์ตœ๊ทผ 100๊ฐœ๋งŒ ์œ ์ง€
            $this->redis->expire($key, 300);
        }
    }
    
    /**
     * ์ข‹์•„์š” ์ถ”๊ฐ€
     */
    public function likePost($postId, $userId) {
        $likesKey = "post:{$postId}:likes";
        $userLikesKey = "post:{$postId}:liked_users";
        
        // ์ด๋ฏธ ์ข‹์•„์š”๋ฅผ ๋ˆŒ๋ €๋Š”์ง€ ํ™•์ธ
        if ($this->redis->sIsMember($userLikesKey, $userId)) {
            return false;
        }
        
        // ์ข‹์•„์š” ์นด์šดํ„ฐ ์ฆ๊ฐ€
        $this->redis->incr($likesKey);
        
        // ์ข‹์•„์š” ๋ˆ„๋ฅธ ์‚ฌ์šฉ์ž ์ถ”๊ฐ€
        $this->redis->sAdd($userLikesKey, $userId);
        
        // ์ธ๊ธฐ ๊ฒŒ์‹œ๋ฌผ ์ ์ˆ˜ ์—…๋ฐ์ดํŠธ
        $this->redis->zIncrBy('trending_posts', 1, $postId);
        
        return true;
    }
    
    /**
     * ์ข‹์•„์š” ์ทจ์†Œ
     */
    public function unlikePost($postId, $userId) {
        $likesKey = "post:{$postId}:likes";
        $userLikesKey = "post:{$postId}:liked_users";
        
        // ์ข‹์•„์š”๋ฅผ ๋ˆ„๋ฅด์ง€ ์•Š์•˜์œผ๋ฉด ๋ฌด์‹œ
        if (!$this->redis->sIsMember($userLikesKey, $userId)) {
            return false;
        }
        
        // ์ข‹์•„์š” ์นด์šดํ„ฐ ๊ฐ์†Œ
        $this->redis->decr($likesKey);
        
        // ์ข‹์•„์š” ๋ˆ„๋ฅธ ์‚ฌ์šฉ์ž ์ œ๊ฑฐ
        $this->redis->sRem($userLikesKey, $userId);
        
        // ์ธ๊ธฐ ๊ฒŒ์‹œ๋ฌผ ์ ์ˆ˜ ์—…๋ฐ์ดํŠธ
        $this->redis->zIncrBy('trending_posts', -1, $postId);
        
        return true;
    }
    
    /**
     * ๊ฒŒ์‹œ๋ฌผ ์ข‹์•„์š” ์ˆ˜ ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function getLikesCount($postId) {
        $key = "post:{$postId}:likes";
        $count = $this->redis->get($key);
        return $count !== false ? (int)$count : 0;
    }
    
    /**
     * ์ธ๊ธฐ ๊ฒŒ์‹œ๋ฌผ ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function getTrendingPosts($limit = 10) {
        // Sorted Set์—์„œ ์ ์ˆ˜๊ฐ€ ๋†’์€ ์ˆœ์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ
        $postIds = $this->redis->zRevRange('trending_posts', 0, $limit - 1);
        
        $posts = [];
        foreach ($postIds as $postId) {
            $post = $this->getPost($postId);
            $post['score'] = $this->redis->zScore('trending_posts', $postId);
            $posts[] = $post;
        }
        
        return $posts;
    }
    
    /**
     * ์•Œ๋ฆผ ์ถ”๊ฐ€
     */
    public function addNotification($userId, $notification) {
        $key = "notifications:{$userId}";
        $timestamp = microtime(true);
        
        // Sorted Set์— ์ถ”๊ฐ€ (ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ์ ์ˆ˜๋กœ ์‚ฌ์šฉ)
        $this->redis->zAdd($key, $timestamp, json_encode($notification));
        
        // ์ตœ๊ทผ 100๊ฐœ๋งŒ ์œ ์ง€
        $this->redis->zRemRangeByRank($key, 0, -101);
        
        // ์ฝ์ง€ ์•Š์€ ์•Œ๋ฆผ ์นด์šดํ„ฐ ์ฆ๊ฐ€
        $this->redis->incr("notifications:{$userId}:unread");
    }
    
    /**
     * ์•Œ๋ฆผ ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function getNotifications($userId, $limit = 20) {
        $key = "notifications:{$userId}";
        
        // ์ตœ๊ทผ ์•Œ๋ฆผ๋ถ€ํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
        $notifications = $this->redis->zRevRange($key, 0, $limit - 1);
        
        return array_map(function($item) {
            return json_decode($item, true);
        }, $notifications);
    }
    
    /**
     * ์•Œ๋ฆผ ์ฝ์Œ ์ฒ˜๋ฆฌ
     */
    public function markNotificationsAsRead($userId) {
        $this->redis->set("notifications:{$userId}:unread", 0);
    }
    
    /**
     * ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๊ฐ€์ ธ์˜ค๊ธฐ
     */
    public function getUserProfile($userId) {
        $key = "user:{$userId}:profile";
        
        // Redis Hash์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ
        $profile = $this->redis->hGetAll($key);
        
        if (empty($profile)) {
            // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ
            $profile = $this->fetchUserProfileFromDB($userId);
            
            // Redis์— ์ €์žฅ (30๋ถ„)
            $this->redis->hMSet($key, $profile);
            $this->redis->expire($key, 1800);
        }
        
        return $profile;
    }
    
    // ํ—ฌํผ ๋ฉ”์„œ๋“œ๋“ค
    private function generateFeed($userId) {
        // ์‹ค์ œ ๊ตฌํ˜„...
        return [];
    }
    
    private function getFollowers($userId) {
        // ์‹ค์ œ ๊ตฌํ˜„...
        return [];
    }
    
    private function getPost($postId) {
        // ์‹ค์ œ ๊ตฌํ˜„...
        return ['id' => $postId];
    }
    
    private function fetchUserProfileFromDB($userId) {
        // ์‹ค์ œ ๊ตฌํ˜„...
        return [];
    }
}
?>

๐ŸŽ“ ์žฌ๋Šฅ๋„ท์—์„œ PHP ๊ฐœ๋ฐœ ์‹ค๋ ฅ ํ‚ค์šฐ๊ธฐ


์—ฌ๊ธฐ๊นŒ์ง€ PHP ์บ์‹ฑ ์ „๋žต์— ๋Œ€ํ•ด ๊นŠ์ด ์žˆ๊ฒŒ ์•Œ์•„๋ดค์–ด! ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์™€ ํŒŒ์ผ ์บ์‹œ์˜ ์ฐจ์ด์ , ๊ฐ๊ฐ์˜ ์žฅ๋‹จ์ , ๊ทธ๋ฆฌ๊ณ  ์‹ค์ „์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€๊นŒ์ง€ ๋ฐฐ์› ์ง€. ๐ŸŽ‰


ํ•˜์ง€๋งŒ ์ด๋ก ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•ด! ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด๋ฉด์„œ ๊ฒฝํ—˜์„ ์Œ“๋Š” ๊ฒŒ ๊ฐ€์žฅ ์ค‘์š”ํ•˜๊ฑฐ๋“ . ํ˜น์‹œ PHP ๊ฐœ๋ฐœ์ด๋‚˜ ์บ์‹ฑ ์ „๋žต์— ๋Œ€ํ•ด ๋” ๊นŠ์ด ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€๋“ค์˜ ๋„์›€์„ ๋ฐ›์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ! ๐Ÿ’ก


์žฌ๋Šฅ๋„ท์—๋Š” PHP ๊ฐœ๋ฐœ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ตœ์ ํ™”, ์„œ๋ฒ„ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์˜ ์ „๋ฌธ๊ฐ€๋“ค์ด ์žˆ์–ด. 1:1 ๋ฉ˜ํ† ๋ง์„ ๋ฐ›๊ฑฐ๋‚˜, ํ”„๋กœ์ ํŠธ๋ฅผ ์˜๋ขฐํ•  ์ˆ˜๋„ ์žˆ์ง€. ๋˜ํ•œ ๋„ˆ์˜ ๊ฐœ๋ฐœ ์‹ค๋ ฅ์„ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ๊ณต์œ ํ•˜๋ฉด์„œ ์ˆ˜์ต๋„ ์ฐฝ์ถœํ•  ์ˆ˜ ์žˆ์–ด!

๐Ÿš€ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๊ธฐ


์บ์‹ฑ์„ ๋งˆ์Šคํ„ฐํ–ˆ๋‹ค๋ฉด ์ด์ œ ๋‹ค์Œ ์ฃผ์ œ๋“ค๋„ ๊ณต๋ถ€ํ•ด๋ณด์ž:


โ€ข ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ์ตœ์ ํ™”: ์ธ๋ฑ์Šค, ์ฟผ๋ฆฌ ํŠœ๋‹, EXPLAIN ๋ถ„์„
โ€ข ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ: ์—ฌ๋Ÿฌ ์„œ๋ฒ„์— ํŠธ๋ž˜ํ”ฝ ๋ถ„์‚ฐ
โ€ข CDN ํ™œ์šฉ: ์ •์  ํŒŒ์ผ ๋ฐฐํฌ ์ตœ์ ํ™”
โ€ข ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ: ํ ์‹œ์Šคํ…œ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…
โ€ข ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜: ์„œ๋น„์Šค ๋ถ„๋ฆฌ์™€ ํ™•์žฅ์„ฑ

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ


์˜ค๋Š˜ ์šฐ๋ฆฌ๋Š” PHP ์บ์‹ฑ ์ „๋žต์— ๋Œ€ํ•ด ์ •๋ง ๋งŽ์€ ๊ฒƒ์„ ๋ฐฐ์› ์–ด! ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ(Redis, Memcached, APCu)์™€ ํŒŒ์ผ ์บ์‹œ์˜ ํŠน์ง•, ์žฅ๋‹จ์ , ๊ทธ๋ฆฌ๊ณ  ์‹ค์ „ ํ™œ์šฉ ๋ฐฉ๋ฒ•๊นŒ์ง€ ์‚ดํŽด๋ดค์ง€. ๐ŸŽฏ


ํ•ต์‹ฌ ์š”์•ฝ:


โœ… ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ๋Š” ์ดˆ๊ณ ์†์ด์ง€๋งŒ ํœ˜๋ฐœ์„ฑ์ด๊ณ  ์ธํ”„๋ผ ๋น„์šฉ์ด ๋“ ๋‹ค
โœ… ํŒŒ์ผ ์บ์‹œ๋Š” ์˜์†์ ์ด๊ณ  ๊ฐ„ํŽธํ•˜์ง€๋งŒ ์ƒ๋Œ€์ ์œผ๋กœ ๋А๋ฆฌ๋‹ค
โœ… ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ „๋žต์œผ๋กœ ๋‘ ๊ฐ€์ง€๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ๊ณ ์˜ ํšจ๊ณผ
โœ… ์บ์‹œ ๋ฌดํšจํ™”์™€ ๋ชจ๋‹ˆํ„ฐ๋ง์€ ํ•„์ˆ˜!
โœ… ์‹ค์ „ ํ”„๋กœ์ ํŠธ์— ๋งž๋Š” ์ „๋žต์„ ์„ ํƒํ•˜๋Š” ๊ฒŒ ์ค‘์š”


์บ์‹ฑ์€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ ์ตœ์ ํ™”์˜ ํ•ต์‹ฌ์ด์•ผ. ์ œ๋Œ€๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ๊ทน์ ์œผ๋กœ ๊ฐœ์„ ๋˜๊ณ , ์„œ๋ฒ„ ๋น„์šฉ๋„ ์ ˆ๊ฐํ•  ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ์˜คํžˆ๋ ค ๋ณต์žก๋„๋งŒ ์ฆ๊ฐ€ํ•˜๊ณ  ๋ฒ„๊ทธ์˜ ์›์ธ์ด ๋  ์ˆ˜ ์žˆ์œผ๋‹ˆ ํ•ญ์ƒ ์‹ ์ค‘ํ•˜๊ฒŒ ์ ‘๊ทผํ•ด์•ผ ํ•ด! ๐ŸŽจ


์ด ๊ธ€์ด ๋„ˆ์˜ PHP ๊ฐœ๋ฐœ ์—ฌ์ •์— ๋„์›€์ด ๋˜์—ˆ๊ธฐ๋ฅผ ๋ฐ”๋ผ! ๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๊ฑฐ๋‚˜ ๋” ๊นŠ์ด ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด ์žฌ๋Šฅ๋„ท์„ ๋ฐฉ๋ฌธํ•ด๋ด. ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ๊ฐ€ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์–ด! ๐Ÿ’ช


๊ทธ๋Ÿผ ์ฆ๊ฑฐ์šด ์ฝ”๋”ฉ ๋˜๊ธธ ๋ฐ”๋ผ! Happy Caching! ๐Ÿš€โœจ

๐Ÿš€ ๐Ÿ’ก โœจ Happy Caching with PHP!
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0