OP캐쉬 사용하면 괜찮아 보다는 저 같은 경우는 사용하지 않아도 괜찮아를 더 좋아 합니다.
솔리드 캐시는 간단히 말해 "비싼 RAM(REDIS) 대신 저렴하고 넉넉한 디스크(DB)에 캐시를 저장하는 전략"으로 원래 루비 온 레일즈(RUBY ON RAILS) 커뮤니티에서 제안된 방식이지만 본질은 어떤 언어에서든 적용 가능한 실용적인 캐싱 철학임.
과거에는 디스크가 너무 느려서 무조건 데이터를 ram(REDIS MEMCACHED)에 올려야 했지만 지금은 nvme ssd 같은 초고속 저장 장치가 보편화되었습니다. 굳이 복잡하게 별도의 메모리 서버를 관리하느니 이미 쓰고 있는 데이터베이스의 테이블 하나를 캐시 저장소로 쓰자는 것이 솔리드 캐시의 핵심입니다.
여기서 속도가 중요한데 간혹 DB 쿼리 슬로우 쿼리로 밖에 안되는 것들이 있을 수 있는데 이런 경우는 SSD가 아니라 HDD도 좋은 선택 입니다.
그리고 URL을 캐쉬의 키로 사용하는 방법도 있기도 합니다.
여기서 중요한 것은 이미 처리 한것을 데이터가 변경될 사항이 아니라면 미리 처리 한것을 사용 하는것이 더 좋은 선택 이라는 것이죠.
- 해시 생성: 실행할 sql 쿼리문을 MD5 같은 함수로 변환해 고유한 id(kEY)를 만듭니다.
- 문고리 확인: 실제 db를 뒤지기 전에 캐시 전용 테이블(SOLID_CACHE)에 이 id가 있는지 확인합니다.
- 지름길 반환 (HIT): 데이터가 있고 만료 시간이 지나지 않았다면 복잡한 로직 없이 바로 결과를 가져옵니다.
- 실제 작업 후 저장 (MISS): 캐시가 없다면 원래대로 쿼리를 실행하고 그 결과를 나중을 위해 캐시 테이블에 저장합니다.
- 단순함 (NO CLASSES NO CHAOS): 복잡한 상속이나 외부 라이브러리 없이 우리가 만든 db 클래스의 메소드 하나만 고쳐서 바로 구현할 수 있습니다.
- 직관적 구조: "쿼리 → 해시 → db 저장"이라는 흐름이 눈에 뻔히 보입니다. 나중에 문제가 생겨도 부모 클래스를 찾아 헤맬 필요가 없죠.
- 가성비: 수십 기가바이트의 캐시를 쌓아도 추가 비용이 거의 들지 않습니다. 낡은 서버에서도 nvme만 달려있다면 날아다닙니다.
솔리드 캐시는 디스크를 쓰기 때문에 데이터가 무한정 쌓일 수 있습니다. 그래서 유통기한이 지난 데이터를 지워주는 청소 로직(GARBAGE COLLECTION)이 반드시 필요합니다.
CREATE TABLE IF NOT EXISTS `solid_cache` (
`cache_key` varchar(32) NOT NULL,
`content` longtext NOT NULL,
`expires_at` datetime NOT NULL,
PRIMARY KEY (`cache_key`),
KEY `idx_expires` (`expires_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
class Mysqldb
{
private $host = 'host';
private $user = '';
private $passwd = '';
private $oMysqli= '';
public function __construct($host='', $user='', $passwd='', $dbname='', $port='3306')
{
if ($host && $user && $passwd) {
$this->connection($host, $user, $passwd, $dbname, $port);
}
}
public function connection($host, $user, $passwd, $dbname, $port)
{
$oMysqli = mysqli_connect($host, $user, $passwd, $dbname, $port);
mysqli_set_charset($oMysqli, 'utf8');
$this->oMysqli = $oMysqli;
return ($oMysqli) ? true : mysqli_error($oMysqli);
}
/**
* $ttl: 0이면 캐시 미사용, 0보다 크면 해당 초(second)만큼 캐시 유지
*/
public function query($query, $type='assoc', $ttl = 0)
{
$oMysqli = $this->oMysqli;
// 1. SELECT 문이고 $ttl이 설정된 경우에만 캐시 로직 작동
$isSelect = (stripos(trim($query), 'select') === 0);
$cacheKey = '';
if ($isSelect && $ttl > 0) {
$cacheKey = md5($query); // 쿼리문 자체를 해시화
// 캐시 테이블에서 유효한 데이터가 있는지 조회
$cacheSql = "SELECT content FROM solid_cache WHERE cache_key = '$cacheKey' AND expires_at > NOW()";
$cacheRes = mysqli_query($oMysqli, $cacheSql);
$cacheRow = mysqli_fetch_assoc($cacheRes);
if ($cacheRow) {
// 캐시 적중! 데이터를 다시 배열로 되돌려 반환
return unserialize($cacheRow['content']);
}
}
// 2. 캐시가 없거나 $ttl이 0이면 실제 쿼리 실행
$oResult = mysqli_query($oMysqli, $query);
if ($type == "result" || $type == 'rSelect') {
return $oResult;
}
$aRs = array();
if ($type == 'assoc') {
$aRs = @mysqli_fetch_assoc($oResult);
} elseif ($type == 'array') {
$aRs = @mysqli_fetch_array($oResult);
}
// 3. 캐시 저장 (결과가 있고 SELECT 문인 경우에만)
if ($isSelect && $ttl > 0 && !empty($aRs)) {
$safeContent = mysqli_real_escape_string($oMysqli, serialize($aRs));
$saveSql = "REPLACE INTO solid_cache (cache_key, content, expires_at)
VALUES ('$cacheKey', '$safeContent', DATE_ADD(NOW(), INTERVAL $ttl SECOND))";
mysqli_query($oMysqli, $saveSql);
}
return $aRs;
}
/**
* 만료된 캐시 수동 청소
*/
public function clean_cache() {
return mysqli_query($this->oMysqli, "DELETE FROM solid_cache WHERE expires_at < NOW()");
}
}