본문 바로가기

캐시

업데이트됨

번역일: 2026년 9월 29일

이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.

원문 수정
2026년 9월 29일
번역 갱신
2026년 9월 29일

캐시

소개

애플리케이션에서 수행하는 데이터 조회나 처리 작업 중에는 CPU를 많이 사용하거나 완료까지 몇 초씩 걸리는 작업들이 있습니다. 이런 경우 조회한 데이터를 일정 시간 동안 캐싱해두면, 동일한 데이터를 요청할 때마다 매번 무거운 연산을 반복하지 않고 빠르게 응답할 수 있습니다. 보통 이런 캐시 데이터는 Memcached나 Redis처럼 매우 빠른 조회 속도를 제공하는 데이터 저장소에 보관합니다.

다행히 라라벨은 다양한 캐시 백엔드에 대해 표현력 있고 통일된 API를 제공하므로, 각 백엔드가 제공하는 빠른 데이터 조회 성능을 활용하면서도 애플리케이션 성능을 끌어올릴 수 있습니다.

설정

애플리케이션의 캐시 설정 파일은 config/cache.php에 있습니다. 이 파일에서 애플리케이션 전역적으로 기본으로 사용할 캐시 스토어를 지정할 수 있습니다. 라라벨은 Memcached, Redis, DynamoDB, 그리고 관계형 데이터베이스와 같은 인기 있는 캐싱 백엔드를 기본으로 지원합니다. 이외에도 파일 기반 캐시 드라이버가 있으며, array 및 null 캐시 드라이버는 테스트를 위한 편리한 캐시 백엔드를 제공합니다.

캐시 설정 파일에는 이 외에도 여러 옵션이 문서화되어 있으니, 반드시 한 번씩 살펴보시기 바랍니다. 기본적으로 라라벨은 database 캐시 드라이버를 사용하도록 설정되어 있는데, 이 드라이버는 캐시된 객체를 애플리케이션의 데이터베이스에 직렬화된 형태로 저장합니다.

드라이버 사전 준비 사항

데이터베이스

database 캐시 드라이버를 사용할 때는 캐시 데이터를 담을 데이터베이스 테이블이 필요합니다. 일반적으로 이 테이블은 라라벨의 기본 0001_01_01_000001_create_cache_table.php 데이터베이스 마이그레이션에 이미 포함되어 있습니다. 하지만 애플리케이션에 이 마이그레이션이 없다면, make:cache-table Artisan 명령어를 사용해서 생성할 수 있습니다.

php artisan make:cache-tablephp artisan migrate

Memcached

Memcached 드라이버를 사용하려면 Memcached PECL 패키지가 설치되어 있어야 합니다. config/cache.php 설정 파일에 모든 Memcached 서버를 나열할 수 있습니다. 이 파일에는 이미 시작하는 데 필요한 memcached.servers 항목이 준비되어 있습니다.

'memcached' => [ // ... 'servers' => [ [ 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 'port' => env('MEMCACHED_PORT', 11211), 'weight' => 100, ], ], ],

필요한 경우 host 옵션을 UNIX 소켓 경로로 지정할 수도 있습니다. 이 경우 port 옵션은 0으로 설정해야 합니다.

'memcached' => [ // ... 'servers' => [ [ 'host' => '/var/run/memcached/memcached.sock', 'port' => 0, 'weight' => 100, ], ], ],

Redis

라라벨에서 Redis 캐시를 사용하기 전에, PECL을 통해 PhpRedis 확장(extension)을 설치하거나 Composer를 통해 predis/predis 패키지(~1.0)를 설치해야 합니다. Laravel Sail에는 이미 이 확장이 포함되어 있습니다. 또한, Laravel Cloud나 Laravel Forge 같은 공식 라라벨 배포 플랫폼에는 PhpRedis 확장이 기본으로 설치되어 있습니다.

Redis 설정에 대한 자세한 내용은 라라벨 문서의 Redis 페이지를 참고하세요.

DynamoDB

DynamoDB 캐시 드라이버를 사용하기 전에, 캐시 데이터를 저장할 DynamoDB 테이블을 먼저 생성해야 합니다. 일반적으로 이 테이블의 이름은 cache로 지정하지만, 애플리케이션의 cache 설정 파일 안에 있는 stores.dynamodb.table 설정 값에 따라 정해지므로, 이 값을 기준으로 테이블명을 지정하는 것이 좋습니다.

또한, 이 테이블에는 stores.dynamodb.attributes.key 설정 값에 지정된 이름과 동일한 문자열 파티션 키(partition key)가 있어야 합니다. 기본적으로 파티션 키의 이름은 key로 지정되어 있습니다.

일반적으로 DynamoDB는 만료된 항목을 테이블에서 자동으로 제거하지 않으므로, 테이블에 Time to Live(TTL)를 설정해 두는 것이 좋습니다. 이때 TTL 설정에서는 expires_at을 TTL 속성 이름으로 지정하면 됩니다.

다음으로, AWS SDK를 라라벨 애플리케이션에서 사용할 수 있도록 Composer 패키지 관리자를 통해 설치합니다.

composer require aws/aws-sdk-php

또한 DynamoDB 캐시 스토어 설정 옵션에 값을 지정해야 합니다. 일반적으로 이 옵션들(예: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)은 애플리케이션의 .env 설정 파일에 정의해야 합니다.

'dynamodb' => [ 'driver' => 'dynamodb', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 'endpoint' => env('DYNAMODB_ENDPOINT'), ],

캐시 사용법

캐시 인스턴스 얻기

캐시 스토어 인스턴스를 얻으려면 Cache 파사드를 사용합니다. 이 문서 전반에서 이 파사드를 사용할 예정입니다. Cache 파사드는 라라벨 캐시 계약의 기본 구현체에 간결하고 편리하게 접근할 수 있도록 해줍니다.

<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Cache; class UserController extends Controller { /** * 애플리케이션의 모든 사용자 목록을 조회합니다. */ public function index(): array { $value = Cache::get('key'); return [ // ... ]; } }

여러 캐시 스토어에 접근하기

Cache 파사드를 통해 store 메서드를 사용하면 다양한 캐시 스토어에 접근할 수 있습니다. store 메서드에 전달하는 키는 cache 설정 파일의 stores 설정 배열에 나열된 스토어 이름 중 하나와 일치해야 합니다.

$value = Cache::store('file')->get('foo'); Cache::store('redis')->put('bar', 'baz', 600); // 10분

캐시에서 항목 조회하기

Cache 파사드의 get 메서드는 캐시에서 항목을 조회하는 데 사용됩니다. 해당 항목이 캐시에 존재하지 않으면 null이 반환됩니다. 원한다면 get 메서드에 두 번째 인자로 기본값을 전달할 수 있으며, 지정한 항목이 캐시에 존재하지 않을 경우 이 기본값이 반환됩니다.

$value = Cache::get('key'); $value = Cache::get('key', 'default');

기본값으로 클로저(closure)를 전달할 수도 있습니다. 지정한 항목이 캐시에 존재하지 않으면 클로저의 결과값이 반환됩니다. 클로저를 전달하면 데이터베이스나 외부 서비스에서 기본값을 조회하는 과정을 지연(defer)시킬 수 있습니다.

$value = Cache::get('key', function () { return DB::table(/* ... */)->get(); });

항목 존재 여부 확인하기

has 메서드를 사용하면 캐시에 항목이 존재하는지 확인할 수 있습니다. 이 메서드는 항목이 존재하지만 값이 null인 경우에도 false를 반환합니다.

if (Cache::has('key')) { // ... }

값 증가 및 감소시키기

increment와 decrement 메서드는 캐시에 저장된 정수 값을 조정하는 데 사용됩니다. 이 두 메서드는 모두 두 번째 인자로 증가시키거나 감소시킬 값을 지정할 수 있습니다.

// 값이 존재하지 않으면 초기화합니다... Cache::add('key', 0, now()->addHours(4)); // 값을 증가 또는 감소시킵니다... Cache::increment('key'); Cache::increment('key', $amount); Cache::decrement('key'); Cache::decrement('key', $amount);

조회 후 저장하기

캐시에서 항목을 조회하되, 해당 항목이 존재하지 않을 경우 기본값을 저장하고 싶을 때가 있습니다. 예를 들어, 캐시에 모든 사용자 목록을 조회하려고 하는데 존재하지 않으면 데이터베이스에서 조회한 후 캐시에 추가하고 싶은 경우입니다. 이럴 때는 Cache::remember 메서드를 사용할 수 있습니다.

$value = Cache::remember('users', $seconds, function () { return DB::table('users')->get(); });

지정한 키가 캐시에 존재하지 않으면, remember 메서드에 전달한 클로저가 실행되고 그 결과가 캐시에 저장됩니다.

remember 메서드 대신 rememberForever 메서드를 사용하면 캐시에 존재하지 않는 항목을 영구적으로 조회하고 저장할 수 있습니다.

$value = Cache::rememberForever('users', function () { return DB::table('users')->get(); });

Stale While Revalidate

Cache::remember 메서드를 사용할 때, 캐시 값이 만료되면 일부 사용자는 오래 걸리는 콜백이 실행되는 동안 응답 지연을 경험할 수 있습니다. 특정 데이터의 경우 데이터가 재계산되는 동안 오래된(stale) 데이터를 일부 제공하면서, 백그라운드에서 캐시 값을 다시 계산하는 것이 사용자 경험 측면에서 더 나을 수 있습니다. 이는 흔히 "stale-while-revalidate" 패턴이라고 불리며, Cache::flexible 메서드가 이 패턴을 구현한 방법을 제공합니다.

flexible 메서드는 캐시 값이 "신선함(fresh)"으로 간주되는 기간과 "오래됨(stale)"으로 간주되어 재검증이 필요해지는 시점을 배열로 지정할 수 있게 해줍니다. 배열의 첫 번째 값은 캐시가 신선하게 유지되는 시간(초)이고, 두 번째 값은 재검증이 필요해지기 전까지 오래된 데이터로 제공될 수 있는 시간을 정의합니다.

만약 신선한 기간 내에 요청이 이루어지면 즉시 캐시가 반환되며 별도의 재계산은 발생하지 않습니다. 만약 오래된(stale) 기간 내에 요청이 이루어지면, 오래된 값이 사용자에게 제공되고, terminating 콜백을 등록하여 사용자가 응답을 받은 이후에 캐시 값을 다시 계산합니다. 만약 두 번째 값 이후에 요청이 이루어지면, 캐시가 즉시 만료된 것으로 간주되고 값이 동기적으로 다시 계산되므로, 요청이 지연될 수 있습니다.

$value = Cache::flexible('users', [5, 10], function () { return DB::table('users')->get(); });

조회 후 삭제하기

캐시에서 항목을 조회한 후 즉시 삭제해야 한다면 pull 메서드를 사용할 수 있습니다. get 메서드와 마찬가지로, 해당 항목이 캐시에 존재하지 않으면 null이 반환됩니다.

$value = Cache::pull('key'); $value = Cache::pull('key', 'default');

캐시에 항목 저장하기

Cache 파사드의 put 메서드를 사용해 캐시에 항목을 저장할 수 있습니다.

Cache::put('key', 'value', $seconds = 10);

put 메서드에 만료 시간을 전달하지 않으면 해당 항목은 무기한으로 저장됩니다.

Cache::put('key', 'value');

정수형 초 단위를 사용하는 대신, 캐시된 항목의 만료 시점을 나타내는 DateTime 인스턴스를 전달할 수도 있습니다.

Cache::put('key', 'value', now()->addMinutes(10));

존재하지 않을 경우에만 저장하기

add 메서드는 아직 캐시 스토어에 존재하지 않는 항목만 추가합니다. 실제로 항목이 추가되면 true를 반환하고, 그렇지 않으면 false를 반환합니다. add 메서드는 원자적(atomic) 연산입니다.

Cache::add('key', 'value', $seconds);

항목을 영구적으로 저장하기

forever 메서드는 만료 없이 항목을 캐시에 영구적으로 저장하는 데 사용할 수 있습니다. 이런 항목들은 만료되지 않으므로 수동으로 forget 메서드를 사용해 캐시에서 삭제해야 합니다.

Cache::forever('key', 'value');

NOTE

Memcached 드라이버를 사용하는 경우, "영구적으로" 저장된 항목이 캐시가 최대 크기에 도달하면 삭제될 수도 있습니다.

항목 유효 기간 연장하기

touch 메서드를 사용하면 캐시 항목의 만료 시간을 연장할 수 있습니다. 이는 특히 캐시된 파일을 최근에 접근했을 때만 유지하고 싶은 파일 캐시 상황에서 유용합니다.

Cache::touch('key');

또한 만료 시간을 추가로 지정할 수도 있는데, 이는 초 단위의 정수 값이나 DateTime 인스턴스일 수 있습니다.

Cache::touch('key', 10);

지정한 키가 캐시에 존재하지 않으면 touch 메서드는 false를 반환합니다.

if (! Cache::touch('key')) { // ... }

캐시에서 항목 제거하기

forget 메서드를 사용해 캐시에서 항목을 제거할 수 있습니다.

Cache::forget('key');

만료 시간을 0 또는 음수로 지정해서 항목을 제거할 수도 있습니다.

Cache::put('key', 'value', 0); Cache::put('key', 'value', -5);

전체 캐시를 초기화하려면 flush 메서드를 사용할 수 있습니다.

Cache::flush();

WARNING

캐시 초기화(flushing)는 설정된 캐시 "접두사(prefix)"를 고려하지 않고 캐시의 모든 항목을 제거합니다. 다른 애플리케이션과 캐시를 공유하는 경우 이 점을 신중히 고려하세요.

캐시 메모이제이션

라라벨의 Cache::memo 메서드를 사용하면 단일 요청 또는 Job 실행 중에 캐시 값을 메모리에 캐싱한 상태로 조회하고 저장할 수 있습니다. 이렇게 하면 이후 동일한 캐시 키를 조회할 때 캐시 드라이버로 다시 요청을 보내지 않고, 처음 조회한 값을 메모리에서 즉시 반환합니다. 이는 특히 요청 처리 도중 동일한 캐시 값을 여러 번 조회해야 하는 상황에서 큰 성능 향상을 가져다줄 수 있습니다.

use Illuminate\Support\Facades\Cache; Cache::memo()->get('key');

캐시 스토어를 지정하고 싶다면 memo 메서드에 원하는 스토어 이름을 전달하면 됩니다.

// Redis 캐시 스토어를 사용합니다... Cache::memo('redis')->get('key');

get처럼 캐시에서 값을 조회하는 메서드를 호출하면, 메모이제이션된 캐시 인스턴스는 동일한 키에 대해 최초 호출 시점에만 실제 캐시 스토어에 접근합니다. 이후 동일한 요청 사이클(또는 Job 처리 사이클) 내에서 같은 키를 다시 조회할 경우에는 메모리에 저장된 값을 즉시 반환하며, 실제로 캐시 드라이버에 접근하지는 않습니다.

// 캐시 스토어에 접근합니다... Cache::memo()->get('key'); // 캐시 스토어에 접근하지 않고 메모이제이션된 값을 반환합니다... Cache::memo()->get('key');

반면, 캐시에 값을 저장하는 메서드(예: Cache::memo()->put())를 호출하면 실제 캐시 스토어가 즉시 갱신됩니다. 동시에 메모리에 저장된 메모이제이션 값도 이후 조회를 위해 함께 갱신됩니다.

Cache::memo()->put('key', 'value');

캐시 헬퍼

Cache 파사드 외에도, 전역 cache 함수를 사용해 캐시에서 데이터를 조회하고 저장할 수 있습니다. cache 함수에 문자열 인자 하나만 전달하면 해당 키에 대응하는 값을 반환합니다.

$value = cache('key');

키/값 쌍의 배열과 만료 시간을 함수에 전달하면, 지정한 기간 동안 캐시에 값을 저장합니다.

cache(['key' => 'value'], $seconds); cache(['key' => 'value'], now()->addMinutes(10));

cache 함수를 아무 인자 없이 호출하면 Illuminate\Contracts\Cache\Factory 구현체 인스턴스가 반환되어, 다른 캐싱 메서드를 호출할 수 있습니다.

cache()->remember('users', $seconds, function () { return DB::table('users')->get(); });

NOTE

전역 cache 함수 호출을 테스트할 때는, 파사드 테스트와 마찬가지로 Cache::shouldReceive 메서드를 사용할 수 있습니다.

캐시 태그

WARNING

캐시 태그는 dynamodb, database, file, array 캐시 드라이버를 사용할 때는 지원되지 않습니다. 게다가 여러 태그를 사용하는 캐시를 "영구적으로" 저장하면서 오래된 태그를 자동으로 정리하는 드라이버(예: memcached)를 사용하는 경우, 성능이 저하될 수 있습니다. 이런 드라이버를 사용할 때는 만료 시간이 있는 태그된 캐시만 저장하는 것이 좋습니다.

태그된 캐시 항목 저장하기

캐시 태그를 사용하면 캐시 내에서 연관된 항목들을 태그로 묶고, 특정 태그가 지정된 캐시 값을 통째로 초기화할 수 있습니다. 태그된 캐시에 접근하려면 정렬된 태그 이름의 배열을 전달합니다. 예를 들어, 태그가 지정된 캐시에 접근한 후 값을 put으로 저장해봅시다.

Cache::tags(['people', 'artists'])->put('John', $john, $seconds); Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);

태그된 캐시 항목 접근하기

태그가 지정된 캐시 항목을 조회하려면, 마찬가지로 동일한 태그 목록을 tags 메서드에 전달하면 됩니다. 그 후 조회하려는 키에 대해 get 메서드를 호출합니다.

$john = Cache::tags(['people', 'artists'])->get('John'); $anne = Cache::tags(['people', 'authors'])->get('Anne');

태그된 캐시 항목 제거하기

특정 태그 또는 태그 목록이 지정된 모든 항목을 초기화할 수 있습니다. 예를 들어, 다음 문장은 people, authors, 혹은 둘 다에 태그가 지정된 모든 캐시를 제거합니다. 따라서 Anne과 John 모두 캐시에서 제거됩니다.

Cache::tags(['people', 'authors'])->flush();

반면, 다음 문장은 authors에만 태그가 지정된 캐시를 제거하므로, Anne만 제거되고 John은 제거되지 않습니다.

Cache::tags('authors')->flush();

원자적 락(Atomic Locks)

WARNING

이 기능을 사용하려면 애플리케이션의 기본 캐시 드라이버가 memcached, redis, dynamodb, database, file, 또는 array 캐시 드라이버 중 하나여야 합니다. 또한 모든 서버가 동일한 중앙 캐시 서버와 통신하고 있어야 합니다.

락 관리하기

원자적 락(atomic lock)은 경쟁 조건(race condition)을 걱정하지 않고 분산 락을 다룰 수 있게 해줍니다. 예를 들어 Laravel Cloud는 원자적 락을 사용해서, 한 번에 하나의 원격 작업만 서버에서 실행되도록 보장합니다. Cache::lock 메서드를 사용해서 락을 생성하고 관리할 수 있습니다.

use Illuminate\Support\Facades\Cache; $lock = Cache::lock('foo', 10); if ($lock->get()) { // 10초 동안 락을 획득함... $lock->release(); }

get 메서드는 클로저도 전달받을 수 있습니다. 클로저가 실행된 이후에는 라라벨이 자동으로 락을 해제합니다.

Cache::lock('foo', 10)->get(function () { // 10초 동안 락을 획득한 상태로 자동 해제됩니다... });

락을 요청한 시점에 다른 곳에서 이미 사용 중이라면, 라라벨이 지정한 시간만큼 대기하도록 지시할 수 있습니다. 지정한 시간 제한 내에 락을 획득하지 못하면 Illuminate\Contracts\Cache\LockTimeoutException 예외가 발생합니다.

use Illuminate\Contracts\Cache\LockTimeoutException; $lock = Cache::lock('foo', 10); try { $lock->block(5); // 최대 5초를 대기한 후 락을 획득함... } catch (LockTimeoutException $e) { // 락을 획득하지 못함... } finally { $lock->release(); }

위 예제는 block 메서드에 클로저를 전달함으로써 더 단순화할 수 있습니다. 이 경우 라라벨은 지정한 시간(초) 동안 락 획득을 시도하고, 클로저가 실행된 이후에는 자동으로 락을 해제합니다.

Cache::lock('foo', 10)->block(5, function () { // 최대 5초를 대기한 후 락을 획득함... });

프로세스 간 락 관리하기

한 프로세스에서 락을 획득한 뒤, 다른 프로세스에서 그 락을 해제하고 싶을 때가 있습니다. 예를 들어 웹 요청 중에 락을 획득하고, 해당 요청으로 트리거된 큐 Job이 끝날 때 락을 해제하고 싶은 경우가 있을 수 있습니다. 이런 상황에서는 락의 범위가 지정된 "소유자 토큰(owner token)"을 큐 Job에 전달하여, Job이 전달받은 토큰을 사용해 락을 다시 인스턴스화할 수 있도록 해야 합니다.

아래 예제에서는 락이 성공적으로 획득되면 Job을 디스패치합니다. 이때 락의 owner 메서드를 통해 락의 소유자 토큰을 Job에 전달합니다.

$podcast = Podcast::find($id); $lock = Cache::lock('processing_' . $podcast->id, 120); if ($lock->get()) { ProcessPodcast::dispatch($podcast, $lock->owner()); }

애플리케이션의 ProcessPodcast Job 내부에서는 전달받은 소유자 토큰을 사용해 락을 다시 복원한 후 해제할 수 있습니다.

Cache::restoreLock('processing_' . $this->podcast->id, $this->owner)->release();

현재 소유자에 관계없이 락을 해제하고 싶다면, forceRelease 메서드를 사용할 수 있습니다.

Cache::lock('processing_' . $podcast->id)->forceRelease();

락 갱신하기

락이 확보된 시간이 최초로 획득할 때 지정된 시간보다 더 오래 필요할 수도 있습니다. 이 경우, 락 인스턴스의 refresh 메서드를 통해 락을 계속 유지되도록 갱신할 수 있습니다.

$podcast = Podcast::find($id); $lock = Cache::lock('processing_'.$podcast->id, 120); if ($lock->get()) { $podcast->update(['status' => 'processing']); // 락을 3분 더 유지합니다... $lock->refresh(180); $podcast->update(['status' => 'processed']); $lock->release(); }

Cache::restoreLock을 사용해 락을 재구성하면 락을 쉽게 갱신할 수 있습니다. 예를 들어, 큐 Job 내부에서 락 처리 중간에 갱신이 필요할 때 사용할 수 있습니다.

Cache::restoreLock('processing_'.$this->podcast->id, $this->owner)->refresh(180);

특정 시간을 초 단위로 추가하고 싶다면 extend 메서드를 사용할 수도 있습니다.

$podcast = Podcast::find($id); $lock = Cache::lock('processing_'.$podcast->id, 120); if ($lock->get()) { $podcast->update(['status' => 'processing']); // 락 만료 시간에 3분을 추가합니다... $lock->extend(180); $podcast->update(['status' => 'processed']); $lock->release(); }

동시성 제한

경쟁 조건을 방지하기 위한 것뿐만 아니라, 원자적 락은 리소스에 대한 동시 접근을 제한하는 데도 사용할 수 있습니다. 예를 들어 애플리케이션에서 동시에 세 개의 프로세스만 특정 외부 서비스와 상호작용하도록 제한하고 싶을 수 있습니다. 이런 상황에서는 Cache::sharedLock 메서드를 사용할 수 있습니다.

sharedLock 메서드는 지정한 개수까지 여러 프로세스가 동시에 같은 락을 획득할 수 있게 해줍니다. 이 락은 배타적 락과 함께 조합해서 사용할 수도 있습니다. 예를 들어, 외부 서비스와의 동시 상호작용을 최대 세 개로 제한하고 싶다면 다음처럼 작성할 수 있습니다.

use Illuminate\Support\Facades\Cache; $lock = Cache::sharedLock('services.external', 10, 3); if ($lock->get()) { // 서비스와 상호작용을 수행함... $lock->release(); }

동시 접근이 허용된 범위를 초과한 경우, block 메서드를 사용해서 락이 사용 가능해질 때까지 대기하도록 만들 수 있습니다.

Cache::sharedLock('services.external', 10, 3)->block(5, function () { // 서비스와 상호작용을 수행함... });

캐시 페일오버

라라벨은 기본 캐시 드라이버에서 장애가 발생했을 때 대체 캐시 드라이버로 자동 전환되는 페일오버 기능을 지원합니다. 이 기능은 Redis 서버 다운이나 일시적인 네트워크 문제처럼 일시적인 장애 상황에서도 애플리케이션이 계속 안정적으로 캐시를 사용할 수 있도록 도와줍니다.

페일오버를 설정하려면 failover 캐시 드라이버를 사용하고, 이 드라이버에 사용할 캐시 스토어들을 우선순위(fallback) 순서로 나열합니다.

'stores' => [ 'redis-with-failover' => [ 'driver' => 'failover', 'stores' => ['redis', 'database'], ], // ... ]

위 예제에서 redis-with-failover 스토어는 먼저 redis 스토어를 사용해서 캐시 작업을 시도합니다. redis 스토어를 사용할 수 없는 경우(예: 연결 예외가 발생하는 경우), 라라벨은 자동으로 database 스토어로 전환합니다.

기본적으로 라라벨은 캐시 작업이 실패할 때 발생하는 예외를 잡아냅니다. 이 동작을 커스터마이징하고 싶다면, catchExceptions 설정 옵션을 사용할 수 있습니다.

'stores' => [ 'redis-with-failover' => [ 'driver' => 'failover', 'stores' => ['redis', 'database'], 'catchExceptions' => false, ], // ... ]

또한, 다음과 같이 애플리케이션의 bootstrap/app.php 파일에서 catchExceptions 메서드를 사용해 전역적으로 이 동작을 활성화할 수도 있습니다.

->withExceptions(function (Exceptions $exceptions) { $exceptions->catchExceptions(); })

커스텀 캐시 드라이버 추가하기

드라이버 작성하기

커스텀 캐시 드라이버를 만들려면, 먼저 Illuminate\Contracts\Cache\Store 계약을 구현해야 합니다. 예를 들어, MongoDB 캐시 구현은 이렇게 작성할 수 있습니다.

<?php namespace App\Extensions; use Illuminate\Contracts\Cache\Store; class MongoStore implements Store { public function get($key) {} public function many(array $keys) {} public function put($key, $value, $seconds) {} public function putMany(array $values, $seconds) {} public function increment($key, $value = 1) {} public function decrement($key, $value = 1) {} public function forever($key, $value) {} public function forget($key) {} public function flush() {} public function getPrefix() {} }

이 메서드들은 각각 MongoDB 커넥션을 사용해 구현하면 됩니다. 구현 예시를 참고하려면 라라벨 프레임워크 소스 코드에서 Illuminate\Cache\MemcachedStore를 살펴보면, 이 메서드들을 어떻게 구현하는지 알 수 있습니다. 구현이 끝나면, Cache 파사드의 extend 메서드를 호출해서 커스텀 드라이버 등록을 마무리할 수 있습니다.

NOTE

커스텀 캐시 드라이버 코드를 어디에 두어야 할지 고민된다면, app 디렉토리 안에 Extensions 네임스페이스를 만들어 사용하는 것도 좋은 방법입니다. 다만, 라라벨에는 엄격하게 정해진 애플리케이션 구조가 없으므로, 여러분의 선호에 맞게 자유롭게 애플리케이션을 구성해도 무방합니다.

드라이버 등록하기

커스텀 캐시 드라이버를 라라벨에 등록하려면 Cache 파사드의 extend 메서드를 사용합니다. 다른 서비스 프로바이더들이 부팅될 때 캐시 값을 조회하려고 시도할 수도 있으므로, booting 콜백 내부에서 커스텀 드라이버를 등록하는 것이 좋습니다. 이렇게 하면 커스텀 드라이버가 애플리케이션의 다른 모든 서비스 프로바이더 boot 메서드가 호출되기 직전, 그리고 등록된 모든 register 메서드가 호출된 이후에 등록되도록 보장할 수 있습니다. 이 콜백은 App\Providers\AppServiceProvider 클래스의 register 메서드 내부에서 등록합니다.

<?php namespace App\Providers; use App\Extensions\MongoStore; use Illuminate\Contracts\Foundation\Application; use Illuminate\Support\Facades\Cache; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * 애플리케이션 서비스를 등록합니다. */ public function register(): void { $this->app->booting(function () { Cache::extend('mongo', function (Application $app) { return Cache::repository(new MongoStore); }); }); } /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { // ... } }

extend 메서드에 전달하는 첫 번째 인자는 드라이버의 이름입니다. 이 이름은 config/cache.php 설정 파일 안의 driver 옵션과 일치해야 합니다. 두 번째 인자는 Illuminate\Cache\Repository 인스턴스를 반환하는 클로저입니다. 클로저에는 서비스 컨테이너 인스턴스인 $app이 전달됩니다.

이렇게 확장을 등록한 후에는, config/cache.php 설정 파일의 driver 옵션 값을 mongo로 업데이트하면 됩니다.

이벤트

모든 캐시 작업(operation) 마다 코드를 실행하고 싶다면, 캐시에서 발생하는 다양한 이벤트를 리스닝할 수 있습니다.

이벤트 이름
Illuminate\Cache\Events\CacheHit
Illuminate\Cache\Events\CacheMissed
Illuminate\Cache\Events\KeyForgotten
Illuminate\Cache\Events\KeyWritten

성능 향상을 위해, 특정 캐시 스토어에 대한 이벤트 발생을 비활성화할 수도 있습니다. config/cache.php 설정 파일에서 해당 캐시 스토어의 설정에 events 옵션을 false로 지정하면 됩니다.

'database' => [ 'driver' => 'database', // ... 'events' => false, ],

캐시

소개

애플리케이션에서 데이터를 조회하거나 가공하는 작업 중에는 CPU 자원을 많이 소모하거나 완료까지 몇 초씩 걸리는 것들이 있습니다. 이런 경우 조회한 데이터를 일정 시간 캐시에 저장해두면, 동일한 데이터를 요청하는 다음 요청부터는 훨씬 빠르게 응답할 수 있습니다. 캐시된 데이터는 보통 Memcached나 Redis처럼 매우 빠른 저장소에 보관합니다.

Laravel은 다양한 캐시 백엔드에 대해 일관되고 표현력 있는 API를 제공하므로, 이러한 초고속 데이터 조회 성능을 활용해 애플리케이션의 속도를 손쉽게 끌어올릴 수 있습니다.

설정

캐시 설정 파일은 config/cache.php에 위치하며, 애플리케이션 전반에서 기본으로 사용할 캐시 스토어를 이 파일에서 지정할 수 있습니다. Laravel은 Memcached, Redis, DynamoDB, 관계형 데이터베이스, 파일시스템 디스크 등 널리 쓰이는 캐싱 백엔드를 기본으로 지원합니다. 이 외에도 파일 기반 캐시 드라이버가 제공되며, array와 null 캐시 드라이버는 자동화 테스트에서 사용하기 편리한 캐시 백엔드입니다.

캐시 설정 파일에는 이 밖에도 다양한 옵션이 들어 있으니 한 번씩 살펴보는 것을 권장합니다. 기본적으로 Laravel은 database 캐시 드라이버를 사용하도록 설정되어 있으며, 이 드라이버는 직렬화된 캐시 객체를 애플리케이션 데이터베이스에 저장합니다.

드라이버 사전 준비 사항

데이터베이스

database 캐시 드라이버를 사용하려면 캐시 데이터를 저장할 데이터베이스 테이블이 필요합니다. 일반적으로 이 테이블은 Laravel의 기본 0001_01_01_000001_create_cache_table.php 마이그레이션에 포함되어 있습니다. 만약 애플리케이션에 이 마이그레이션 파일이 없다면, make:cache-table Artisan 명령어로 생성할 수 있습니다.

php artisan make:cache-tablephp artisan migrate

Memcached

Memcached 드라이버를 사용하려면 Memcached PECL 패키지가 설치되어 있어야 합니다. config/cache.php 설정 파일에 사용할 Memcached 서버 목록을 나열할 수 있으며, 이 파일에는 시작에 도움이 되도록 memcached.servers 항목이 이미 포함되어 있습니다.

'memcached' => [ // ... 'servers' => [ [ 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 'port' => env('MEMCACHED_PORT', 11211), 'weight' => 100, ], ], ],

필요하다면 host 옵션에 UNIX 소켓 경로를 지정할 수도 있습니다. 이 경우 port 옵션은 반드시 0으로 설정해야 합니다.

'memcached' => [ // ... 'servers' => [ [ 'host' => '/var/run/memcached/memcached.sock', 'port' => 0, 'weight' => 100 ], ], ],

Redis

Laravel에서 Redis 캐시를 사용하려면 PECL을 통해 PhpRedis PHP 확장을 설치하거나, Composer로 predis/predis 패키지를 설치해야 합니다. Laravel Sail에는 이 확장이 이미 포함되어 있습니다. 또한 Laravel Cloud나 Laravel Forge와 같은 공식 Laravel 애플리케이션 플랫폼에도 PhpRedis 확장이 기본으로 설치되어 있습니다.

Redis 설정에 대한 자세한 내용은 Redis 문서 페이지를 참고하세요.

Storage

storage 캐시 드라이버를 사용하면 애플리케이션에 설정된 파일시스템 디스크 어디에든 캐시 값을 저장할 수 있습니다. 예를 들어 S3 디스크처럼 이미 사용 중인 디스크를 키-값 캐시 저장소로 활용하고 싶을 때 유용합니다.

'storage' => [ 'driver' => 'storage', 'disk' => env('CACHE_STORAGE_DISK'), 'path' => env('CACHE_STORAGE_PATH', 'framework/cache/data'), ],

DynamoDB

DynamoDB 캐시 드라이버를 사용하기 전에, 캐시 데이터를 저장할 DynamoDB 테이블을 먼저 생성해야 합니다. 일반적으로 이 테이블의 이름은 cache로 지정하지만, 실제 이름은 cache 설정 파일 내 stores.dynamodb.table 설정값을 기준으로 정해야 합니다. 테이블 이름은 DYNAMODB_CACHE_TABLE 환경 변수로도 지정할 수 있습니다.

또한 이 테이블에는 문자열 파티션 키가 있어야 하며, 그 이름은 애플리케이션의 cache 설정 파일에 있는 stores.dynamodb.attributes.key 설정값과 일치해야 합니다. 기본적으로 파티션 키의 이름은 key입니다.

DynamoDB는 기본적으로 만료된 항목을 자동으로 삭제하지 않습니다. 따라서 테이블에 TTL(Time to Live) 기능을 활성화해 두어야 합니다. TTL을 설정할 때는 TTL 속성 이름을 expires_at으로 지정하면 됩니다.

다음으로, 애플리케이션이 DynamoDB와 통신할 수 있도록 AWS SDK를 설치합니다.

composer require aws/aws-sdk-php

그리고 DynamoDB 캐시 스토어 설정에 필요한 값들을 채워 넣어야 합니다. 보통 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY와 같은 값들은 애플리케이션의 .env 설정 파일에 정의합니다.

'dynamodb' => [ 'driver' => 'dynamodb', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 'endpoint' => env('DYNAMODB_ENDPOINT'), ],

MongoDB

MongoDB를 사용 중이라면, 공식 mongodb/laravel-mongodb 패키지에서 제공하는 mongodb 캐시 드라이버를 mongodb 데이터베이스 커넥션과 함께 설정해 사용할 수 있습니다. MongoDB는 TTL 인덱스를 지원하므로, 만료된 캐시 항목을 자동으로 정리하는 데 활용할 수 있습니다.

MongoDB 설정에 대한 자세한 내용은 MongoDB의 캐시 및 락 문서를 참고하세요.

캐시

캐시 사용법

캐시 인스턴스 얻기

캐시 스토어 인스턴스를 얻으려면 Cache 파사드를 사용하면 됩니다. 이 문서 전반에서도 이 파사드를 사용할 것입니다. Cache 파사드는 Laravel 캐시 컨트랙트의 실제 구현체에 간편하고 간결하게 접근할 수 있는 방법을 제공합니다.

<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Cache; class UserController extends Controller { /** * Show a list of all users of the application. */ public function index(): array { $value = Cache::get('key'); return [ // ... ]; } }

여러 캐시 스토어에 접근하기

Cache 파사드의 store 메서드를 사용하면 여러 캐시 스토어에 접근할 수 있습니다. store 메서드에 전달하는 키는 cache 설정 파일의 stores 배열에 정의된 스토어 이름 중 하나와 일치해야 합니다.

$value = Cache::store('file')->get('foo'); Cache::store('redis')->put('bar', 'baz', 600); // 10분

캐시에서 항목 가져오기

캐시에서 항목을 가져올 때는 Cache 파사드의 get 메서드를 사용합니다. 항목이 존재하지 않으면 null이 반환됩니다. 항목이 없을 때 반환할 기본값을 두 번째 인자로 지정할 수도 있습니다.

$value = Cache::get('key'); $value = Cache::get('key', 'default');

기본값으로 클로저를 전달할 수도 있습니다. 지정한 키가 캐시에 존재하지 않을 경우 클로저의 실행 결과가 반환됩니다. 이렇게 클로저를 사용하면 데이터베이스나 다른 외부 서비스로부터 기본값을 가져오는 작업을, 실제로 필요할 때까지 지연시킬 수 있습니다.

$value = Cache::get('key', function () { return DB::table(/* ... */)->get(); });

항목 존재 여부 확인하기

has 메서드를 사용하면 캐시에 특정 항목이 존재하는지 확인할 수 있습니다. 항목은 존재하지만 값이 null인 경우에도 이 메서드는 false를 반환합니다.

if (Cache::has('key')) { // ... }

값 증가시키기 / 감소시키기

increment와 decrement 메서드를 사용하면 캐시에 저장된 정수 값을 조정할 수 있습니다. 두 메서드 모두 두 번째 인자로 증가 또는 감소시킬 양을 선택적으로 전달할 수 있습니다.

// 값이 존재하지 않으면 초기화... Cache::add('key', 0, now()->plus(hours: 4)); // 값 증가 또는 감소... Cache::increment('key'); Cache::increment('key', $amount); Cache::decrement('key'); Cache::decrement('key', $amount);

조회 후 저장하기

캐시에서 항목을 가져오되, 만약 요청한 항목이 존재하지 않으면 기본값을 저장하고 싶은 경우가 있습니다. 예를 들어 캐시에서 전체 사용자 목록을 가져오고, 존재하지 않으면 데이터베이스에서 조회한 뒤 캐시에 저장하고 싶을 때 Cache::remember 메서드를 사용할 수 있습니다.

$value = Cache::remember('users', $seconds, function () { return DB::table('users')->get(); });

캐시에 항목이 존재하지 않으면 remember 메서드에 전달된 클로저가 실행되고, 그 결과가 캐시에 저장됩니다.

항목이 캐시에서 조회된 것인지, 아니면 클로저가 실행되어 만들어진 것인지 구분해야 할 때는 rememberWithWarmth 메서드를 사용할 수 있습니다. 이 메서드는 캐시된 값과, 해당 값이 캐시에서 가져온 "warm" 상태였는지(즉 클로저를 실행하지 않고 캐시에서 바로 가져왔는지)를 나타내는 불리언 값을 배열로 반환합니다.

[$value, $warm] = Cache::rememberWithWarmth('users', $seconds, function () { return DB::table('users')->get(); });

rememberForever 메서드를 사용하면 캐시에서 항목을 가져오거나, 존재하지 않을 경우 영구적으로 저장할 수 있습니다.

$value = Cache::rememberForever('users', function () { return DB::table('users')->get(); });

Stale While Revalidate (오래된 데이터 우선 제공)

Cache::remember 메서드를 사용할 때, 캐시된 값의 만료 시점이 지나면 일부 사용자는 응답 시간이 느려지는 경험을 할 수 있습니다. 데이터 종류에 따라서는, 캐시 값이 백그라운드에서 재계산되는 동안 일부 오래된(stale) 데이터를 그대로 제공하는 방식이 유용할 수 있습니다. 이렇게 하면 캐시 값을 다시 계산하는 동안 모든 사용자가 느린 응답을 경험하는 것을 방지할 수 있습니다. 이 패턴을 흔히 "stale-while-revalidate"라고 부르며, Cache::flexible 메서드가 이 패턴을 구현해서 제공합니다.

flexible 메서드는 캐시 값이 언제까지 "신선한(fresh)" 상태이고 언제부터 "오래된(stale)" 상태가 되는지를 지정하는 배열을 인자로 받습니다. 배열의 첫 번째 값은 캐시가 신선한 상태로 유지되는 시간(초)을, 두 번째 값은 재계산이 필요해지기 전까지 오래된 데이터로 제공할 수 있는 시간을 의미합니다.

신선한 기간(첫 번째 값 이전) 내에 요청이 들어오면 캐시는 재계산 없이 즉시 반환됩니다. 오래된 기간(두 값 사이)에 요청이 들어오면 사용자에게는 오래된 값이 그대로 제공되고, 응답이 전송된 뒤 캐시 값을 갱신하기 위한 지연 함수(deferred function)가 등록됩니다. 두 번째 값을 넘긴 이후에 요청이 들어오면 캐시는 만료된 것으로 간주되어 값이 즉시 재계산되며, 이 경우 사용자는 응답이 느려질 수 있습니다.

$value = Cache::flexible('users', [5, 10], function () { return DB::table('users')->get(); });

조회 후 삭제하기

캐시에서 항목을 가져온 뒤 바로 삭제하고 싶다면 pull 메서드를 사용할 수 있습니다. get 메서드와 마찬가지로, 항목이 존재하지 않으면 null이 반환됩니다.

$value = Cache::pull('key'); $value = Cache::pull('key', 'default');

캐시에 항목 저장하기

Cache 파사드의 put 메서드를 사용해 캐시에 항목을 저장할 수 있습니다.

Cache::put('key', 'value', $seconds = 10);

put 메서드에 저장 시간을 전달하지 않으면 항목은 무기한 저장됩니다.

Cache::put('key', 'value');

저장 시간을 초 단위 정수로 전달하는 대신, 캐시 항목이 만료되어야 하는 시점을 나타내는 DateTime 인스턴스를 전달할 수도 있습니다.

Cache::put('key', 'value', now()->plus(minutes: 10));

존재하지 않을 때만 저장하기

add 메서드는 캐시 스토어에 해당 항목이 아직 존재하지 않을 때만 항목을 추가합니다. 이 메서드는 항목이 실제로 캐시에 추가된 경우 true를 반환하고, 그렇지 않으면 false를 반환합니다. add 메서드는 원자적(atomic)으로 동작합니다.

Cache::add('key', 'value', $seconds);

항목의 유효 기간 연장하기

touch 메서드를 사용하면 기존 캐시 항목의 TTL(유효 기간)을 연장할 수 있습니다. touch 메서드는 캐시 항목이 존재하고 만료 시간이 성공적으로 연장된 경우 true를 반환합니다. 항목이 캐시에 존재하지 않으면 false를 반환합니다.

Cache::touch('key', 3600);

정확한 만료 시점을 지정하려면 DateTimeInterface, DateInterval, 또는 Carbon 인스턴스를 전달할 수도 있습니다.

Cache::touch('key', now()->addHours(2));

항목을 영구적으로 저장하기

forever 메서드를 사용하면 항목을 캐시에 영구적으로 저장할 수 있습니다. 이런 항목은 만료되지 않으므로 forget 메서드를 사용해 직접 삭제해야 합니다.

Cache::forever('key', 'value');

NOTE

Memcached 드라이버를 사용하는 경우, "영구적으로" 저장된 항목이라도 캐시가 저장 용량 제한에 도달하면 삭제될 수 있습니다.

캐시에서 항목 제거하기

forget 메서드를 사용해 캐시에서 항목을 제거할 수 있습니다.

Cache::forget('key');

만료 시간을 0 또는 음수로 지정해서 항목을 제거하는 방법도 있습니다.

Cache::put('key', 'value', 0); Cache::put('key', 'value', -5);

flush 메서드를 사용하면 캐시 전체를 비울 수 있습니다.

Cache::flush();

flushLocks 메서드를 사용하면 캐시에 걸려 있는 모든 원자적 락(atomic lock)을 지울 수 있습니다.

Cache::flushLocks();

WARNING

캐시를 flush할 때는 설정된 캐시 "프리픽스(prefix)"를 무시하고 캐시의 모든 항목을 삭제합니다. 다른 애플리케이션과 캐시를 공유하고 있다면 이 점에 특히 주의해야 합니다.

캐시 메모이제이션(Memoization)

Laravel의 memo 캐시 드라이버를 사용하면 한 번의 요청 또는 Job 실행 동안 조회한 캐시 값을 메모리에 임시로 저장할 수 있습니다. 이를 통해 동일한 실행 흐름 안에서 같은 캐시를 반복 조회하는 것을 막아 성능을 크게 향상시킬 수 있습니다.

메모이즈된 캐시를 사용하려면 memo 메서드를 호출하면 됩니다.

use Illuminate\Support\Facades\Cache; $value = Cache::memo()->get('key');

memo 메서드는 선택적으로 캐시 스토어의 이름을 인자로 받을 수 있는데, 이는 메모이즈 드라이버가 감쌀(decorate) 실제 캐시 스토어를 지정하는 역할을 합니다.

// 기본 캐시 스토어 사용... $value = Cache::memo()->get('key'); // Redis 캐시 스토어 사용... $value = Cache::memo('redis')->get('key');

특정 키에 대해 처음 호출한 get은 실제 캐시 스토어에서 값을 가져오지만, 같은 요청 또는 Job 실행 안에서 이후에 호출되는 get은 메모리에 저장된 값을 반환합니다.

// 캐시를 조회함... $value = Cache::memo()->get('key'); // 캐시를 조회하지 않고, 메모이즈된 값을 반환... $value = Cache::memo()->get('key');

put, increment, remember처럼 캐시 값을 변경하는 메서드를 호출하면, 메모이즈 캐시는 자동으로 메모이즈된 값을 잊어버리고(forget) 해당 변경 작업을 실제 캐시 스토어에 위임합니다.

Cache::memo()->put('name', 'Taylor'); // 실제 캐시에 기록... Cache::memo()->get('name'); // 실제 캐시를 조회... Cache::memo()->get('name'); // 메모이즈된 값 사용, 캐시 조회 안 함... Cache::memo()->put('name', 'Tim'); // 메모이즈된 값을 잊고, 새 값을 기록... Cache::memo()->get('name'); // 다시 실제 캐시를 조회...

cache 헬퍼 함수

Cache 파사드를 사용하는 것 외에도, 전역 cache 함수를 통해 캐시 데이터를 조회하거나 저장할 수 있습니다. cache 함수를 문자열 하나만 인자로 호출하면 해당 키의 값을 반환합니다.

$value = cache('key');

키/값 쌍으로 이루어진 배열과 만료 시간을 함수에 전달하면, 지정한 기간 동안 값을 캐시에 저장합니다.

cache(['key' => 'value'], $seconds); cache(['key' => 'value'], now()->plus(minutes: 10));

cache 함수를 아무 인자 없이 호출하면 Illuminate\Contracts\Cache\Factory 구현체의 인스턴스가 반환되며, 이를 통해 다른 캐싱 메서드들을 호출할 수 있습니다.

cache()->remember('users', $seconds, function () { return DB::table('users')->get(); });

NOTE

전역 cache 함수 호출을 테스트할 때는, 파사드를 테스트할 때와 마찬가지로 Cache::shouldReceive 메서드를 사용할 수 있습니다.

캐시 태그

WARNING

file, dynamodb, database, storage 캐시 드라이버는 캐시 태그 기능을 지원하지 않습니다.

태그가 지정된 캐시 항목 저장하기

캐시 태그를 사용하면 서로 연관된 캐시 항목들에 태그를 붙여두었다가, 특정 태그가 지정된 캐시 값들을 한 번에 모두 삭제할 수 있습니다. 태그가 지정된 캐시에 접근하려면 태그 이름을 순서대로 나열한 배열을 전달하면 됩니다. 예를 들어, 태그가 지정된 캐시에 접근하여 put으로 값을 저장하는 방법은 다음과 같습니다.

use Illuminate\Support\Facades\Cache; Cache::tags(['people', 'artists'])->put('John', $john, $seconds); Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);

태그가 지정된 캐시 항목 조회하기

태그를 사용해 저장한 캐시 항목은 저장할 때 사용했던 태그를 동일하게 지정해야만 조회할 수 있습니다. 태그가 지정된 캐시 항목을 가져오려면, 저장할 때와 동일한 순서의 태그 목록을 tags 메서드에 전달한 다음, 조회하려는 키로 get 메서드를 호출하면 됩니다.

$john = Cache::tags(['people', 'artists'])->get('John'); $anne = Cache::tags(['people', 'authors'])->get('Anne');

NOTE

태그를 지정할 때는 순서가 중요합니다. ['people', 'artists']와 ['artists', 'people']은 서로 다른 태그 조합으로 취급되므로, 저장할 때와 조회할 때 반드시 동일한 순서로 태그를 지정해야 합니다.

태그가 지정된 캐시 항목 삭제하기

하나의 태그 또는 여러 태그가 지정된 모든 캐시 항목을 한 번에 삭제할 수 있습니다. 예를 들어 아래 코드는 people 또는 authors 태그가 붙은 모든 캐시를 삭제합니다. 따라서 Anne과 John 모두 캐시에서 제거됩니다.

Cache::tags(['people', 'authors'])->flush();

반면 아래 코드는 authors 태그가 붙은 캐시 값만 삭제하므로, Anne은 제거되지만 John은 그대로 남아 있습니다.

Cache::tags('authors')->flush();

원자적 락 (Atomic Locks)

WARNING

이 기능을 사용하려면 애플리케이션의 기본 캐시 드라이버가 memcached, redis, dynamodb, database, file, array 중 하나여야 합니다. 또한 모든 서버가 동일한 중앙 캐시 서버와 통신하고 있어야 합니다.

락 관리하기

원자적 락(atomic lock)을 사용하면 경쟁 조건(race condition)을 걱정하지 않고도 분산 환경에서 락을 다룰 수 있습니다. 예를 들어 Laravel Cloud는 특정 서버에서 원격 작업이 동시에 하나만 실행되도록 보장하기 위해 원자적 락을 활용합니다. Cache::lock 메서드를 사용해 락을 생성하고 관리할 수 있습니다.

use Illuminate\Support\Facades\Cache; $lock = Cache::lock('foo', 10); if ($lock->get()) { // 10초 동안 락을 획득함... $lock->release(); }

get 메서드에는 클로저를 전달할 수도 있습니다. 클로저 실행이 끝나면 Laravel이 자동으로 락을 해제합니다.

Cache::lock('foo', 10)->get(function () { // 10초 동안 락을 획득하고, 이후 자동으로 해제됨... });

락을 요청한 시점에 즉시 사용할 수 없다면, 지정한 초만큼 대기하도록 Laravel에 지시할 수 있습니다. 지정된 시간 안에 락을 획득하지 못하면 Illuminate\Contracts\Cache\LockTimeoutException 예외가 발생합니다.

use Illuminate\Contracts\Cache\LockTimeoutException; $lock = Cache::lock('foo', 10); try { $lock->block(5); // 최대 5초 대기 후 락을 획득함... } catch (LockTimeoutException $e) { // 락을 획득하지 못함... } finally { $lock->release(); }

위 예제는 block 메서드에 클로저를 전달하는 방식으로 좀 더 간단하게 작성할 수 있습니다. 이 방식을 사용하면 Laravel이 지정된 시간 동안 락 획득을 시도하고, 클로저 실행이 끝나면 자동으로 락을 해제합니다.

Cache::lock('foo', 10)->block(5, function () { // 최대 5초 대기 후 락을 획득하며, 10초 동안 유지됨... });

여러 프로세스 간 락 관리하기

경우에 따라 하나의 프로세스에서 락을 획득하고, 다른 프로세스에서 그 락을 해제하고 싶을 수 있습니다. 예를 들어 웹 요청 도중 락을 획득한 뒤, 해당 요청에 의해 실행된 큐 작업(Job)이 끝나는 시점에 락을 해제하고 싶은 경우가 있습니다. 이런 시나리오에서는 락의 범위가 지정된 "소유자 토큰(owner token)"을 큐 Job에 전달하여, 해당 Job이 동일한 토큰으로 락을 다시 인스턴스화할 수 있도록 해야 합니다.

아래 예제에서는 락 획득에 성공하면 큐 Job을 디스패치합니다. 이때 락의 owner 메서드를 통해 소유자 토큰을 Job에 전달합니다.

$podcast = Podcast::find($id); $lock = Cache::lock('processing', 120); if ($lock->get()) { ProcessPodcast::dispatch($podcast, $lock->owner()); }

ProcessPodcast Job 내부에서는 전달받은 소유자 토큰을 사용해 락을 복원하고 해제할 수 있습니다.

Cache::restoreLock('processing', $this->owner)->release();

현재 소유자와 관계없이 락을 강제로 해제하고 싶다면 forceRelease 메서드를 사용하면 됩니다.

Cache::lock('processing')->forceRelease();

락 갱신하기

현재 보유하고 있는 락의 만료 시간을 연장하고 싶다면 refresh 메서드를 사용할 수 있습니다. 초 단위 값을 전달하지 않으면 락을 생성할 때 지정했던 원래 기간이 그대로 사용됩니다. 이 기능은 오래 걸리는 작업을 처리할 때, 처음부터 매우 긴 만료 시간으로 락을 잡기보다는 짧은 락을 잡고 주기적으로 갱신하고 싶을 때 유용합니다.

$lock = Cache::lock('generate-reports', 60); if ($lock->get()) { foreach ($reports as $report) { $report->generate(); // 락을 60초 더 연장함... $lock->refresh(); } $lock->release(); }

동시 실행 제한

Laravel의 원자적 락 기능은 클로저의 동시 실행을 제한할 수 있는 몇 가지 방법도 제공합니다. 인프라 전체에서 오직 하나의 인스턴스만 실행되도록 허용하고 싶다면 withoutOverlapping을 사용하세요.

Cache::withoutOverlapping('foo', function () { // 최대 10초 대기 후 락을 획득함... });

기본적으로 락은 클로저 실행이 끝날 때까지 유지되며, 락을 획득하기 위해 최대 10초까지 대기합니다. 다음과 같이 추가 인자를 사용해 이 값들을 직접 조정할 수도 있습니다.

Cache::withoutOverlapping('foo', function () { // 최대 5초 대기 후 락을 획득하며, 120초 동안 유지됨... }, lockFor: 120, waitFor: 5);

지정된 대기 시간 안에 락을 획득하지 못하면 Illuminate\Contracts\Cache\LockTimeoutException 예외가 발생합니다.

동시 실행 개수를 원하는 만큼 허용하는 제어된 병렬 처리를 원한다면 funnel 메서드를 사용해 최대 동시 실행 개수를 설정할 수 있습니다. funnel 메서드는 락을 지원하는 모든 캐시 드라이버에서 사용할 수 있습니다.

Cache::funnel('foo') ->limit(3) ->releaseAfter(60) ->block(10) ->then(function () { // 동시성 락을 획득함... }, function () { // 동시성 락을 획득하지 못함... });

funnel의 키는 제한 대상이 되는 리소스를 식별하는 역할을 합니다. limit 메서드는 허용할 최대 동시 실행 개수를 지정합니다. releaseAfter 메서드는 획득한 슬롯이 자동으로 해제되기까지의 안전장치용 타임아웃(초 단위)을 설정합니다. block 메서드는 사용 가능한 슬롯을 기다릴 최대 대기 시간(초)을 지정합니다.

타임아웃 처리를 실패 시 클로저 대신 예외로 처리하고 싶다면, 두 번째 클로저를 생략하면 됩니다. 지정된 대기 시간 안에 락을 획득하지 못하면 Illuminate\Cache\Limiters\LimiterTimeoutException 예외가 발생합니다.

use Illuminate\Cache\Limiters\LimiterTimeoutException; try { Cache::funnel('foo') ->limit(3) ->releaseAfter(60) ->block(10) ->then(function () { // 동시성 락을 획득함... }); } catch (LimiterTimeoutException $e) { // 동시성 락을 획득하지 못함... }

특정 캐시 스토어를 지정해 동시성 제한 기능을 사용하고 싶다면, 원하는 스토어에서 funnel 메서드를 호출하면 됩니다.

Cache::store('redis')->funnel('foo') ->limit(3) ->block(10) ->then(function () { // "redis" 스토어를 사용해 동시성 락을 획득함... });

NOTE

funnel 메서드를 사용하려면 캐시 스토어가 Illuminate\Contracts\Cache\LockProvider 인터페이스를 구현하고 있어야 합니다. 락을 지원하지 않는 캐시 스토어에서 funnel을 호출하면 BadMethodCallException 예외가 발생합니다.

캐시 페일오버(Failover)

failover 캐시 드라이버는 캐시를 사용할 때 자동으로 장애 대응(failover)을 처리해 주는 기능을 제공합니다. failover 스토어에 지정된 기본 캐시 스토어가 어떤 이유로든 동작하지 않으면, Laravel은 목록에 설정된 다음 캐시 스토어로 자동으로 전환해서 요청을 처리합니다. 프로덕션 환경에서 캐시 가용성이 서비스 안정성에 직결되는 경우, 이 기능은 매우 유용합니다.

페일오버 캐시 스토어를 설정하려면 드라이버를 failover로 지정하고, 순서대로 시도할 스토어 이름들을 배열로 나열하면 됩니다. Laravel은 기본적으로 애플리케이션의 config/cache.php 설정 파일에 다음과 같은 예시 페일오버 설정을 포함하고 있습니다.

'failover' => [ 'driver' => 'failover', 'stores' => [ 'database', 'array', ], ],

NOTE

위 예시에서는 database 스토어가 실패할 경우 array 스토어로 전환됩니다. 실제 운영 환경에서는 Redis, Memcached 등 신뢰도가 높은 스토어를 우선 순위로 두고, 최후의 안전망으로 database나 array 같은 항상 사용 가능한 드라이버를 뒤에 배치하는 방식을 권장합니다.

failover 드라이버를 사용하는 스토어를 설정했다면, 페일오버 기능을 실제로 활용하기 위해 애플리케이션의 .env 파일에서 기본 캐시 스토어를 페일오버 스토어로 지정해야 합니다.

CACHE_STORE=failover

캐시 스토어 작업이 실패해서 페일오버가 실행되면, Laravel은 Illuminate\Cache\Events\CacheFailedOver 이벤트를 발생시킵니다. 이 이벤트를 리스닝하면 특정 캐시 스토어에 장애가 발생했다는 사실을 리포팅하거나 로그로 남길 수 있습니다.

캐시 드라이버 직접 만들기

드라이버 작성하기

나만의 캐시 드라이버를 만들려면 먼저 Illuminate\Contracts\Cache\Store 컨트랙트를 구현해야 합니다. 예를 들어 MongoDB를 사용하는 캐시 드라이버를 구현한다면 다음과 같은 형태가 될 것입니다.

<?php namespace App\Extensions; use Illuminate\Contracts\Cache\Store; class MongoStore implements Store { public function get($key) {} public function many(array $keys) {} public function put($key, $value, $seconds) {} public function putMany(array $values, $seconds) {} public function increment($key, $value = 1) {} public function decrement($key, $value = 1) {} public function forever($key, $value) {} public function touch($key, $seconds) {} public function forget($key) {} public function flush() {} public function getPrefix() {} }

이제 각 메서드를 MongoDB 커넥션을 이용해 실제로 구현하기만 하면 됩니다. 각 메서드를 어떻게 구현해야 할지 감이 잘 안 잡힌다면, 라라벨 프레임워크 소스 코드에 있는 Illuminate\Cache\MemcachedStore 클래스를 참고하시길 권장합니다. 실제 동작하는 구현체를 보면 이해가 훨씬 빨라집니다.

구현이 끝났다면, Cache 파사드의 extend 메서드를 호출해서 커스텀 드라이버 등록을 마무리합니다.

Cache::extend('mongo', function (Application $app) { return Cache::repository(new MongoStore); });

NOTE

커스텀 캐시 드라이버 코드를 어디에 두어야 할지 고민된다면, app 디렉터리 안에 Extensions라는 네임스페이스를 만들어 그 안에 작성하는 것도 좋은 방법입니다. 다만 라라벨은 애플리케이션 구조를 엄격하게 강제하지 않으므로, 본인의 취향과 프로젝트 컨벤션에 맞게 자유롭게 구성하셔도 됩니다.

드라이버 등록하기

작성한 커스텀 캐시 드라이버를 라라벨에 등록하려면 Cache 파사드의 extend 메서드를 사용합니다. 그런데 여기서 한 가지 주의할 점이 있습니다. 다른 서비스 프로바이더들도 자신의 boot 메서드 안에서 캐시된 값을 읽으려고 시도할 수 있기 때문에, 우리의 커스텀 드라이버는 그보다 먼저 등록되어 있어야 합니다.

이를 위해 booting 콜백을 활용합니다. booting 콜백은 애플리케이션의 모든 서비스 프로바이더에서 register 메서드가 실행된 이후, 그리고 boot 메서드가 호출되기 직전 시점에 실행되므로, 정확히 우리가 원하는 타이밍에 커스텀 드라이버를 등록할 수 있습니다. 이 booting 콜백은 App\Providers\AppServiceProvider 클래스의 register 메서드 안에 등록하면 됩니다.

<?php namespace App\Providers; use App\Extensions\MongoStore; use Illuminate\Contracts\Foundation\Application; use Illuminate\Support\Facades\Cache; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * 애플리케이션의 서비스를 등록합니다. */ public function register(): void { $this->app->booting(function () { Cache::extend('mongo', function (Application $app) { return Cache::repository(new MongoStore); }); }); } /** * 애플리케이션의 서비스를 부트스트랩합니다. */ public function boot(): void { // ... } }

extend 메서드에 전달하는 첫 번째 인자는 드라이버의 이름입니다. 이 이름은 config/cache.php 설정 파일의 driver 옵션 값과 대응됩니다. 두 번째 인자는 Illuminate\Cache\Repository 인스턴스를 반환하는 클로저입니다. 이 클로저에는 서비스 컨테이너 인스턴스인 $app이 전달됩니다.

확장 드라이버 등록이 끝났다면, config/cache.php 설정 파일의 default 옵션(또는 CACHE_STORE 환경 변수 값)을 방금 등록한 확장 드라이버의 이름으로 변경해주면 됩니다.

이벤트

캐시에서 발생하는 모든 작업에 대해 특정 코드를 실행하고 싶다면, 캐시가 디스패치하는 다양한 이벤트를 리스닝하면 됩니다.

이벤트 이름
Illuminate\Cache\Events\CacheFlushed
Illuminate\Cache\Events\CacheFlushing
Illuminate\Cache\Events\CacheFlushFailed
Illuminate\Cache\Events\CacheLocksFlushed
Illuminate\Cache\Events\CacheLocksFlushing
Illuminate\Cache\Events\CacheLocksFlushFailed
Illuminate\Cache\Events\CacheHit
Illuminate\Cache\Events\CacheMissed
Illuminate\Cache\Events\ForgettingKey
Illuminate\Cache\Events\KeyForgetFailed
Illuminate\Cache\Events\KeyForgotten
Illuminate\Cache\Events\KeyWriteFailed
Illuminate\Cache\Events\KeyWritten
Illuminate\Cache\Events\RetrievingKey
Illuminate\Cache\Events\RetrievingManyKeys
Illuminate\Cache\Events\WritingKey
Illuminate\Cache\Events\WritingManyKeys

캐시 이벤트를 매번 디스패치하는 데는 약간의 오버헤드가 발생합니다. 성능이 중요한 상황이라면 config/cache.php 설정 파일에서 특정 캐시 저장소의 events 옵션을 false로 지정해 이벤트 발생을 비활성화할 수 있습니다.

'database' => [ 'driver' => 'database', // ... 'events' => false, ],

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 9월 29일