์์ ฏ ์บ์ฑ¶
์์ ฏ์ ์ฑ๋ฅ์ ํฅ์์ํค๊ธฐ ์ํ ๋ค์ํ ์บ์ฑ ์ ๋ต์ ํ์ตํฉ๋๋ค.
์บ์ฑ ๊ธฐ๋ณธ ๊ฐ๋ ¶
์บ์ฑ์ด ํ์ํ ์ด์ ¶
// ์บ์ฑ ์์ด ๋งค๋ฒ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ
function proc($args) {
// ๋งค ์์ฒญ๋ง๋ค ์คํ๋๋ ๋ฌด๊ฑฐ์ด ์ฟผ๋ฆฌ
$output = executeQuery('complex.getHeavyData', $args);
return $this->compileTemplate($args);
}
// ์บ์ฑ ์ ์ฉ ํ
function proc($args) {
$cache_key = $this->getCacheKey($args);
$cached_data = $this->getCache($cache_key);
if ($cached_data) {
Context::set('data', $cached_data);
return $this->compileTemplate($args);
}
// ์บ์๊ฐ ์์ ๋๋ง ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ
$output = executeQuery('complex.getHeavyData', $args);
$this->setCache($cache_key, $output->data, 300); // 5๋ถ ์บ์ฑ
Context::set('data', $output->data);
return $this->compileTemplate($args);
}
ํ์ผ ๊ธฐ๋ฐ ์บ์ฑ¶
๊ธฐ๋ณธ ํ์ผ ์บ์ ๊ตฌํ¶
class FileCache
{
private $cache_dir;
private $default_ttl = 3600; // 1์๊ฐ
public function __construct($cache_dir = null)
{
$this->cache_dir = $cache_dir ?: _XE_PATH_ . 'files/cache/widgets/';
$this->ensureCacheDir();
}
/**
* ์บ์ ๋๋ ํ ๋ฆฌ ์์ฑ
*/
private function ensureCacheDir()
{
if (!is_dir($this->cache_dir)) {
FileHandler::makeDir($this->cache_dir);
}
}
/**
* ์บ์ ํค ์์ฑ
*/
public function generateKey($prefix, $data)
{
return $prefix . '_' . md5(serialize($data));
}
/**
* ์บ์ ๋ฐ์ดํฐ ์กฐํ
*/
public function get($key)
{
$cache_file = $this->cache_dir . $key . '.cache';
if (!file_exists($cache_file)) {
return null;
}
$cache_data = unserialize(file_get_contents($cache_file));
// TTL ํ์ธ
if (isset($cache_data['expires']) && time() > $cache_data['expires']) {
$this->delete($key);
return null;
}
return $cache_data['data'];
}
/**
* ์บ์ ๋ฐ์ดํฐ ์ ์ฅ
*/
public function set($key, $data, $ttl = null)
{
$ttl = $ttl ?: $this->default_ttl;
$cache_file = $this->cache_dir . $key . '.cache';
$cache_data = array(
'data' => $data,
'created' => time(),
'expires' => time() + $ttl
);
file_put_contents($cache_file, serialize($cache_data));
}
/**
* ์บ์ ๋ฐ์ดํฐ ์ญ์
*/
public function delete($key)
{
$cache_file = $this->cache_dir . $key . '.cache';
if (file_exists($cache_file)) {
unlink($cache_file);
}
}
/**
* ๋ง๋ฃ๋ ์บ์ ์ ๋ฆฌ
*/
public function cleanup()
{
$files = glob($this->cache_dir . '*.cache');
$cleaned = 0;
foreach ($files as $file) {
$cache_data = unserialize(file_get_contents($file));
if (isset($cache_data['expires']) && time() > $cache_data['expires']) {
unlink($file);
$cleaned++;
}
}
return $cleaned;
}
/**
* ๋ชจ๋ ์บ์ ์ญ์
*/
public function flush()
{
$files = glob($this->cache_dir . '*.cache');
foreach ($files as $file) {
unlink($file);
}
}
}
์์ ฏ์์ ํ์ผ ์บ์ ์ฌ์ฉ¶
class latest_posts_widget extends WidgetHandler
{
private $cache;
public function __construct()
{
$this->cache = new FileCache();
}
function proc($args)
{
// ์บ์ ํค ์์ฑ
$cache_key = $this->cache->generateKey('latest_posts', array(
'mid_list' => $args->mid_list,
'list_count' => $args->list_count,
'template' => $args->template,
'cache_version' => $this->getCacheVersion()
));
// ์บ์ ํ์ธ
$cached_data = $this->cache->get($cache_key);
if ($cached_data) {
Context::set('document_list', $cached_data);
Context::set('from_cache', true);
return $this->compileTemplate($args);
}
// ๋ฐ์ดํฐ ์กฐํ
$document_list = $this->getDocumentList($args);
// ํ์ฒ๋ฆฌ
$document_list = $this->processDocuments($document_list, $args);
// ์บ์ ์ ์ฅ
$cache_time = max(60, (int)$args->cache_time); // ์ต์ 1๋ถ
$this->cache->set($cache_key, $document_list, $cache_time);
Context::set('document_list', $document_list);
Context::set('from_cache', false);
return $this->compileTemplate($args);
}
/**
* ์บ์ ๋ฒ์ ๊ด๋ฆฌ
*/
private function getCacheVersion()
{
// ๋ฐ์ดํฐ ๊ตฌ์กฐ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ๋ฒ์ ์
๋ฐ์ดํธ
return '1.2.0';
}
}
๋ฉ๋ชจ๋ฆฌ ์บ์ฑ (APCu)¶
APCu ์บ์ ๊ตฌํ¶
class MemoryCache
{
private $prefix;
private $default_ttl = 3600;
public function __construct($prefix = 'widget_')
{
$this->prefix = $prefix;
}
/**
* APCu ์ฌ์ฉ ๊ฐ๋ฅ ์ฌ๋ถ ํ์ธ
*/
public function isAvailable()
{
return extension_loaded('apcu') && apcu_enabled();
}
/**
* ์บ์ ํค ์์ฑ
*/
private function getKey($key)
{
return $this->prefix . $key;
}
/**
* ์บ์ ๋ฐ์ดํฐ ์กฐํ
*/
public function get($key)
{
if (!$this->isAvailable()) {
return null;
}
$cache_key = $this->getKey($key);
$success = false;
$data = apcu_fetch($cache_key, $success);
return $success ? $data : null;
}
/**
* ์บ์ ๋ฐ์ดํฐ ์ ์ฅ
*/
public function set($key, $data, $ttl = null)
{
if (!$this->isAvailable()) {
return false;
}
$cache_key = $this->getKey($key);
$ttl = $ttl ?: $this->default_ttl;
return apcu_store($cache_key, $data, $ttl);
}
/**
* ์บ์ ๋ฐ์ดํฐ ์ญ์
*/
public function delete($key)
{
if (!$this->isAvailable()) {
return false;
}
return apcu_delete($this->getKey($key));
}
/**
* ํจํด์ผ๋ก ์บ์ ์ญ์
*/
public function deleteByPattern($pattern)
{
if (!$this->isAvailable()) {
return false;
}
$iterator = new APCUIterator('/^' . preg_quote($this->prefix . $pattern, '/') . '/');
return apcu_delete($iterator);
}
/**
* ๋ชจ๋ ์บ์ ์ญ์
*/
public function flush()
{
if (!$this->isAvailable()) {
return false;
}
return $this->deleteByPattern('');
}
}
Redis ์บ์ฑ¶
Redis ์บ์ ๊ตฌํ¶
class RedisCache
{
private $redis;
private $prefix;
private $default_ttl = 3600;
private $connected = false;
public function __construct($config = array())
{
$this->prefix = $config['prefix'] ?: 'widget:';
if (class_exists('Redis')) {
$this->redis = new Redis();
$this->connect($config);
}
}
/**
* Redis ์ฐ๊ฒฐ
*/
private function connect($config)
{
try {
$host = $config['host'] ?: '127.0.0.1';
$port = $config['port'] ?: 6379;
$timeout = $config['timeout'] ?: 2.5;
$this->connected = $this->redis->connect($host, $port, $timeout);
if ($this->connected && !empty($config['password'])) {
$this->redis->auth($config['password']);
}
if ($this->connected && !empty($config['database'])) {
$this->redis->select($config['database']);
}
} catch (Exception $e) {
$this->connected = false;
}
}
/**
* Redis ์ฌ์ฉ ๊ฐ๋ฅ ์ฌ๋ถ ํ์ธ
*/
public function isAvailable()
{
return $this->connected;
}
/**
* ์บ์ ํค ์์ฑ
*/
private function getKey($key)
{
return $this->prefix . $key;
}
/**
* ์บ์ ๋ฐ์ดํฐ ์กฐํ
*/
public function get($key)
{
if (!$this->isAvailable()) {
return null;
}
try {
$data = $this->redis->get($this->getKey($key));
return $data ? unserialize($data) : null;
} catch (Exception $e) {
return null;
}
}
/**
* ์บ์ ๋ฐ์ดํฐ ์ ์ฅ
*/
public function set($key, $data, $ttl = null)
{
if (!$this->isAvailable()) {
return false;
}
try {
$cache_key = $this->getKey($key);
$serialized_data = serialize($data);
$ttl = $ttl ?: $this->default_ttl;
return $this->redis->setex($cache_key, $ttl, $serialized_data);
} catch (Exception $e) {
return false;
}
}
/**
* ์บ์ ๋ฐ์ดํฐ ์ญ์
*/
public function delete($key)
{
if (!$this->isAvailable()) {
return false;
}
try {
return $this->redis->del($this->getKey($key));
} catch (Exception $e) {
return false;
}
}
/**
* ํจํด์ผ๋ก ์บ์ ์ญ์
*/
public function deleteByPattern($pattern)
{
if (!$this->isAvailable()) {
return false;
}
try {
$keys = $this->redis->keys($this->prefix . $pattern);
if ($keys) {
return $this->redis->del($keys);
}
return 0;
} catch (Exception $e) {
return false;
}
}
/**
* ์บ์ ํต๊ณ
*/
public function getStats()
{
if (!$this->isAvailable()) {
return array();
}
try {
return $this->redis->info();
} catch (Exception $e) {
return array();
}
}
}
๋ค์ธต ์บ์ฑ ์ ๋ต¶
์บ์ ๊ณ์ธต ๊ด๋ฆฌ์¶
class MultiLevelCache
{
private $caches = array();
private $levels = array('memory', 'file', 'redis');
public function __construct()
{
// ์บ์ ๋ ๋ฒจ๋ณ ์ธ์คํด์ค ์์ฑ
$this->caches['memory'] = new MemoryCache();
$this->caches['file'] = new FileCache();
$this->caches['redis'] = new RedisCache();
}
/**
* ์บ์ ๋ฐ์ดํฐ ์กฐํ (์์ ๋ ๋ฒจ๋ถํฐ)
*/
public function get($key)
{
foreach ($this->levels as $level) {
$cache = $this->caches[$level];
if (!$cache->isAvailable()) {
continue;
}
$data = $cache->get($key);
if ($data !== null) {
// ์์ ๋ ๋ฒจ ์บ์์ ๋ฐ์ดํฐ ๋ณต์ฌ
$this->backfillCache($key, $data, $level);
return $data;
}
}
return null;
}
/**
* ์บ์ ๋ฐ์ดํฐ ์ ์ฅ (๋ชจ๋ ๋ ๋ฒจ์)
*/
public function set($key, $data, $ttl = null)
{
$success = false;
foreach ($this->levels as $level) {
$cache = $this->caches[$level];
if ($cache->isAvailable()) {
// ๋ ๋ฒจ๋ณ TTL ์กฐ์
$level_ttl = $this->adjustTTL($ttl, $level);
if ($cache->set($key, $data, $level_ttl)) {
$success = true;
}
}
}
return $success;
}
/**
* ์์ ๋ ๋ฒจ ์บ์ ๋ฐฑํ
*/
private function backfillCache($key, $data, $current_level)
{
$current_index = array_search($current_level, $this->levels);
// ํ์ฌ ๋ ๋ฒจ๋ณด๋ค ์์ ๋ ๋ฒจ์ ๋ฐ์ดํฐ ์ ์ฅ
for ($i = 0; $i < $current_index; $i++) {
$level = $this->levels[$i];
$cache = $this->caches[$level];
if ($cache->isAvailable()) {
$cache->set($key, $data, $this->adjustTTL(null, $level));
}
}
}
/**
* ๋ ๋ฒจ๋ณ TTL ์กฐ์
*/
private function adjustTTL($ttl, $level)
{
$default_ttl = $ttl ?: 3600;
switch ($level) {
case 'memory':
return min($default_ttl, 300); // ๋ฉ๋ชจ๋ฆฌ๋ 5๋ถ ์ต๋
case 'file':
return $default_ttl;
case 'redis':
return $default_ttl * 2; // Redis๋ ๋ ์ค๋ ๋ณด๊ด
}
return $default_ttl;
}
/**
* ์บ์ ๋ฐ์ดํฐ ์ญ์ (๋ชจ๋ ๋ ๋ฒจ์์)
*/
public function delete($key)
{
foreach ($this->levels as $level) {
$cache = $this->caches[$level];
if ($cache->isAvailable()) {
$cache->delete($key);
}
}
}
/**
* ์บ์ ํต๊ณ
*/
public function getStats()
{
$stats = array();
foreach ($this->levels as $level) {
$cache = $this->caches[$level];
$stats[$level] = array(
'available' => $cache->isAvailable(),
'stats' => method_exists($cache, 'getStats') ? $cache->getStats() : array()
);
}
return $stats;
}
}
์์ ฏ์์ ๋ค์ธต ์บ์ฑ ์ฌ์ฉ¶
class optimized_widget extends WidgetHandler
{
private $cache;
public function __construct()
{
$this->cache = new MultiLevelCache();
}
function proc($args)
{
// ์บ์ ํค ์์ฑ
$cache_key = $this->generateCacheKey($args);
// ์บ์ ํ์ธ
$cached_data = $this->cache->get($cache_key);
if ($cached_data) {
Context::set('data', $cached_data['data']);
Context::set('cache_info', $cached_data['cache_info']);
return $this->compileTemplate($args);
}
// ๋ฐ์ดํฐ ์กฐํ ๋ฐ ์ฒ๋ฆฌ
$data = $this->processData($args);
// ์บ์ ์ ๋ณด ์ถ๊ฐ
$cache_data = array(
'data' => $data,
'cache_info' => array(
'generated_at' => time(),
'version' => $this->getCacheVersion()
)
);
// ์บ์ ์ ์ฅ
$this->cache->set($cache_key, $cache_data, $args->cache_time);
Context::set('data', $data);
Context::set('cache_info', $cache_data['cache_info']);
return $this->compileTemplate($args);
}
private function generateCacheKey($args)
{
$key_data = array(
'widget' => get_class($this),
'args' => $this->normalizeArgs($args),
'version' => $this->getCacheVersion()
);
return 'widget_' . md5(serialize($key_data));
}
private function normalizeArgs($args)
{
// ์บ์ ํค์ ์ํฅ์ ์ฃผ๋ ์ธ์๋ง ์ถ์ถ
return array(
'mid_list' => $args->mid_list,
'list_count' => $args->list_count,
'template' => $args->template,
'show_thumbnail' => $args->show_thumbnail
);
}
}
์บ์ ๋ฌดํจํ ์ ๋ต¶
์ด๋ฒคํธ ๊ธฐ๋ฐ ์บ์ ๋ฌดํจํ¶
class CacheInvalidator
{
private $cache;
private $patterns = array();
public function __construct($cache)
{
$this->cache = $cache;
$this->registerEventHandlers();
}
/**
* ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ฑ๋ก
*/
private function registerEventHandlers()
{
// ๊ฒ์๋ฌผ ๋ฑ๋ก/์์ /์ญ์ ์ ์บ์ ๋ฌดํจํ
EventHandler::addHandler('board.insertDocument', array($this, 'invalidateDocumentCache'));
EventHandler::addHandler('board.updateDocument', array($this, 'invalidateDocumentCache'));
EventHandler::addHandler('board.deleteDocument', array($this, 'invalidateDocumentCache'));
// ๋๊ธ ๋ฑ๋ก/์ญ์ ์ ์บ์ ๋ฌดํจํ
EventHandler::addHandler('board.insertComment', array($this, 'invalidateCommentCache'));
EventHandler::addHandler('board.deleteComment', array($this, 'invalidateCommentCache'));
}
/**
* ๊ฒ์๋ฌผ ๊ด๋ จ ์บ์ ๋ฌดํจํ
*/
public function invalidateDocumentCache($obj)
{
$module_srl = $obj->module_srl;
// ํด๋น ๊ฒ์ํ์ ๋ชจ๋ ์์ ฏ ์บ์ ์ญ์
$patterns = array(
'widget_*_mid_*_' . $module_srl . '_*',
'widget_*_module_' . $module_srl . '_*',
'latest_posts_*_' . $module_srl
);
foreach ($patterns as $pattern) {
$this->cache->deleteByPattern($pattern);
}
}
/**
* ๋๊ธ ๊ด๋ จ ์บ์ ๋ฌดํจํ
*/
public function invalidateCommentCache($obj)
{
// ๋๊ธ ์๊ฐ ๋ณ๊ฒฝ๋๋ฏ๋ก ๊ด๋ จ ์บ์ ๋ฌดํจํ
$this->invalidateDocumentCache($obj);
}
/**
* ํน์ ํจํด์ผ๋ก ์บ์ ๋ฌดํจํ
*/
public function invalidateByPattern($pattern)
{
return $this->cache->deleteByPattern($pattern);
}
/**
* ์๊ฐ ๊ธฐ๋ฐ ์บ์ ์ ๋ฆฌ
*/
public function scheduleCleanup()
{
// ๋งค์ผ ์๋ฒฝ 3์์ ๋ง๋ฃ๋ ์บ์ ์ ๋ฆฌ
if (date('H') == 3 && date('i') < 10) {
$this->cache->cleanup();
}
}
}
์บ์ ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง¶
์บ์ ์ฑ๋ฅ ์ถ์ ¶
class CacheMonitor
{
private static $stats = array(
'hits' => 0,
'misses' => 0,
'sets' => 0,
'deletes' => 0,
'total_time' => 0
);
/**
* ์บ์ ํํธ ๊ธฐ๋ก
*/
public static function recordHit($cache_key, $execution_time = 0)
{
self::$stats['hits']++;
self::$stats['total_time'] += $execution_time;
self::logAccess('HIT', $cache_key, $execution_time);
}
/**
* ์บ์ ๋ฏธ์ค ๊ธฐ๋ก
*/
public static function recordMiss($cache_key, $execution_time = 0)
{
self::$stats['misses']++;
self::$stats['total_time'] += $execution_time;
self::logAccess('MISS', $cache_key, $execution_time);
}
/**
* ์บ์ ์ ์ฅ ๊ธฐ๋ก
*/
public static function recordSet($cache_key)
{
self::$stats['sets']++;
self::logAccess('SET', $cache_key);
}
/**
* ์บ์ ์ญ์ ๊ธฐ๋ก
*/
public static function recordDelete($cache_key)
{
self::$stats['deletes']++;
self::logAccess('DELETE', $cache_key);
}
/**
* ์ก์ธ์ค ๋ก๊ทธ ๊ธฐ๋ก
*/
private static function logAccess($action, $cache_key, $execution_time = 0)
{
if (!__DEBUG__) {
return;
}
$log_entry = array(
'timestamp' => microtime(true),
'action' => $action,
'key' => $cache_key,
'execution_time' => $execution_time,
'memory_usage' => memory_get_usage(true)
);
$log_file = _XE_PATH_ . 'files/cache/cache_access.log';
file_put_contents($log_file, json_encode($log_entry) . "\n", FILE_APPEND);
}
/**
* ์บ์ ํต๊ณ ๋ฐํ
*/
public static function getStats()
{
$total_requests = self::$stats['hits'] + self::$stats['misses'];
$hit_ratio = $total_requests > 0 ? (self::$stats['hits'] / $total_requests) * 100 : 0;
return array(
'hits' => self::$stats['hits'],
'misses' => self::$stats['misses'],
'hit_ratio' => round($hit_ratio, 2),
'total_requests' => $total_requests,
'sets' => self::$stats['sets'],
'deletes' => self::$stats['deletes'],
'average_time' => $total_requests > 0 ? self::$stats['total_time'] / $total_requests : 0
);
}
/**
* ํต๊ณ ๋ฆฌ์
*/
public static function resetStats()
{
self::$stats = array(
'hits' => 0,
'misses' => 0,
'sets' => 0,
'deletes' => 0,
'total_time' => 0
);
}
}
์บ์ ์ค์ ๊ด๋ฆฌ¶
์์ ฏ๋ณ ์บ์ ์ค์ ¶
class WidgetCacheConfig
{
private static $configs = array();
/**
* ์์ ฏ๋ณ ์บ์ ์ค์ ๋ก๋
*/
public static function loadConfig($widget_name)
{
if (isset(self::$configs[$widget_name])) {
return self::$configs[$widget_name];
}
$config_file = _XE_PATH_ . 'files/config/widget_cache/' . $widget_name . '.json';
if (file_exists($config_file)) {
$config = json_decode(file_get_contents($config_file), true);
} else {
$config = self::getDefaultConfig();
}
self::$configs[$widget_name] = $config;
return $config;
}
/**
* ๊ธฐ๋ณธ ์บ์ ์ค์
*/
private static function getDefaultConfig()
{
return array(
'enabled' => true,
'default_ttl' => 300,
'max_ttl' => 3600,
'cache_levels' => array('memory', 'file'),
'invalidation_events' => array(
'board.insertDocument',
'board.updateDocument',
'board.deleteDocument'
),
'cache_key_prefix' => 'widget',
'compression' => false
);
}
/**
* ์บ์ ์ค์ ์ ์ฅ
*/
public static function saveConfig($widget_name, $config)
{
$config_dir = _XE_PATH_ . 'files/config/widget_cache/';
if (!is_dir($config_dir)) {
FileHandler::makeDir($config_dir);
}
$config_file = $config_dir . $widget_name . '.json';
file_put_contents($config_file, json_encode($config, JSON_PRETTY_PRINT));
self::$configs[$widget_name] = $config;
}
}
๋ชจ๋ฒ ์ฌ๋ก¶
- ์ ์ ํ TTL: ๋ฐ์ดํฐ ํน์ฑ์ ๋ง๋ ์บ์ ์ ํจ ์๊ฐ ์ค์
- ๊ณ์ธต์ ์บ์ฑ: ๋น ๋ฅธ ๋ฉ๋ชจ๋ฆฌ ์บ์์ ์๊ตฌ ์ ์ฅ์ ์กฐํฉ
- ๋ฌดํจํ ์ ๋ต: ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ ๊ด๋ จ ์บ์ ์ ์ ํ ์ญ์
- ๋ชจ๋ํฐ๋ง: ์บ์ ํํธ์จ๊ณผ ์ฑ๋ฅ ์งํ ์ถ์
- ์ค์ ๊ด๋ฆฌ: ์์ ฏ๋ณ ์บ์ ์ ์ฑ ์ ์ฐํ๊ฒ ๊ด๋ฆฌ
๋ค์ ๋จ๊ณ¶
์์ ฏ ์บ์ฑ์ ๋ง์คํฐํ๋ค๋ฉด, ํ์ผ ๊ด๋ฆฌ์์ ์ฝํ ์ธ ๊ด๋ฆฌ๋ฅผ ํ์ตํ์ธ์.