메인 콘텐츠로 건너뛰기

캐시란

데이터베이스에의 질의나 외부 API의 호출은 CPU나 네트워크의 비용이 높아 처리에 수 초가 걸리는 경우가 있습니다. 같은 데이터를 몇 번이고 취득하는 경우, 결과를 캐시에 보존해 둠으로써 다음 번 이후의 요청을 고속으로 처리할 수 있습니다. Laravel은 Memcached, Redis, DynamoDB, 데이터베이스 등, 다양한 캐시 백엔드에 대응한 통합 API를 제공하고 있습니다.
기본적으로는 database 드라이버가 설정되어 있습니다. Redis나 Memcached로 전환함으로써 더욱 고속의 캐시를 실현할 수 있습니다.

캐시 설정

config/cache.php

캐시 설정은 config/cache.php에 집약되어 있습니다. CACHE_STORE 환경 변수로 기본 드라이버를 전환합니다.
// config/cache.php
'default' => env('CACHE_STORE', 'database'),

사용 가능한 드라이버

직렬화된 캐시 데이터를 데이터베이스 테이블에 저장합니다. Laravel 11 이후의 신규 프로젝트에는 처음부터 마이그레이션이 포함되어 있습니다.
CACHE_STORE=database
마이그레이션이 포함되어 있지 않은 경우 Artisan 명령어로 작성합니다.
php artisan make:cache-table
php artisan migrate
캐시 데이터를 파일시스템에 저장합니다. 추가의 셋업은 불필요하며, 소규모 애플리케이션에 적합합니다.
CACHE_STORE=file
인메모리로 동작하는 고속의 캐시 드라이버입니다. 운영 환경에서 가장 자주 사용됩니다. PhpRedis PHP 확장 또는 predis/predis 패키지가 필요합니다.
CACHE_STORE=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
Memcached PECL 패키지가 필요합니다. config/cache.php에서 서버를 설정합니다.
'memcached' => [
    'servers' => [
        [
            'host' => env('MEMCACHED_HOST', '127.0.0.1'),
            'port' => env('MEMCACHED_PORT', 11211),
            'weight' => 100,
        ],
    ],
],
AWS DynamoDB를 캐시 스토어로 사용합니다. 사전에 DynamoDB 테이블을 작성하고 AWS SDK를 설치합니다.
composer require aws/aws-sdk-php
CACHE_STORE=dynamodb
DYNAMODB_CACHE_TABLE=cache
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-key
임의의 파일시스템 디스크를 키/값의 캐시 스토어로 사용합니다. 기존의 S3 디스크를 그대로 캐시로 사용하고 싶은 경우에 편리합니다.
'storage' => [
    'driver' => 'storage',
    'disk' => env('CACHE_STORAGE_DISK'),
    'path' => env('CACHE_STORAGE_PATH', 'framework/cache/data'),
],
array는 요청 내에서만 유효한 인메모리 캐시입니다. null은 모든 조작을 무시합니다. 둘 다 자동 테스트에서 편리합니다.
CACHE_STORE=array

캐시 드라이버의 계층 구조

용도나 속도에 따라 드라이버를 구분해서 사용합니다.

기본적인 조작

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 [
            // ...
        ];
    }
}
여러 캐시 스토어를 구분해서 사용하려면 store() 메서드를 사용합니다.
$value = Cache::store('file')->get('foo');

Cache::store('redis')->put('bar', 'baz', 600); // 10분간 저장

데이터 취득: Cache::get()

get() 메서드로 캐시에서 데이터를 취득합니다. 캐시가 존재하지 않는 경우 null을 반환합니다. 기본값을 지정할 수도 있습니다.
$value = Cache::get('key');

// 기본값 지정
$value = Cache::get('key', 'default');

// 클로저로 기본값을 지연 취득
$value = Cache::get('key', function () {
    return DB::table('settings')->get();
});

데이터 저장: Cache::put()

put() 메서드로 데이터를 캐시에 저장합니다. 세 번째 인수에 유효 기간(초)을 지정합니다.
// 10초간 저장
Cache::put('key', 'value', 10);

// Carbon 인스턴스로 유효 기간을 지정
Cache::put('key', 'value', now()->plus(minutes: 10));

// 유효 기간 없음(영구 저장)
Cache::put('key', 'value');
캐시가 존재하지 않는 경우에만 저장하는 add() 메서드도 있습니다.
// 존재하지 않는 경우에만 추가(원자 조작)
Cache::add('key', 'value', $seconds);
영구적으로 저장하려면 forever()를 사용합니다.
Cache::forever('key', 'value');

취득 또는 저장: Cache::remember()

가장 자주 사용되는 조작입니다. 캐시에 데이터가 존재하면 그것을 반환하고, 없으면 클로저를 실행하여 그 결과를 저장합니다.
$users = Cache::remember('users', 3600, function () {
    return DB::table('users')->get();
});
Cache::remember()를 사용함으로써 “캐시 확인 → 없으면 취득 → 캐시에 저장”이라는 3단계를 한 줄로 쓸 수 있습니다. 데이터베이스 쿼리 결과나 외부 API 응답의 캐시에 최적입니다.

remember() 의 플로우

영구 저장 버전의 rememberForever()도 있습니다.
$value = Cache::rememberForever('users', function () {
    return DB::table('users')->get();
});
캐시가 “히트”했는지, 클로저에서 새로 취득되었는지를 알고 싶은 경우 rememberWithWarmth() 메서드를 사용합니다. 이 메서드는 캐시 값과 그것이 신선한지(캐시에서 취득)를 나타내는 진위값을 배열로 반환합니다.
[$value, $warm] = Cache::rememberWithWarmth('users', 3600, function () {
    return DB::table('users')->get();
});

if ($warm) {
    // 캐시로부터의 데이터
} else {
    // 클로저에서 새로 취득한 데이터
}
rememberWithWarmth()는 캐시의 효율성을 감시하고 싶은 경우에 편리합니다. $warm이 false인 경우가 많다면 캐시의 유효 기간(TTL)을 조정함으로써 개선할 수 있을지도 모릅니다.

Stale While Revalidate(유연한 캐시 업데이트)

Cache::flexible()은 캐시가 “신선한 기간”과 “오래되어도 사용할 수 있는 기간”을 배열로 지정합니다. 오래된 데이터를 사용자에게 반환하면서 백그라운드에서 캐시를 업데이트하는 패턴입니다.
// [신선한 기간(초), 오래되어도 사용할 수 있는 기간(초)]
$value = Cache::flexible('users', [5, 10], function () {
    return DB::table('users')->get();
});

존재 확인: Cache::has()

if (Cache::has('key')) {
    // 캐시가 존재하는 경우의 처리
}

값의 인크리먼트 / 디크리먼트

정수값의 카운터를 조작할 수 있습니다.
// 값이 존재하지 않는 경우는 초기화
Cache::add('key', 0, now()->addHours(4));

// 인크리먼트 / 디크리먼트
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);

취득과 삭제: Cache::pull()

취득 후에 캐시에서 삭제합니다. 원타임 데이터의 관리에 편리합니다.
$value = Cache::pull('key');

데이터 삭제: Cache::forget()

// 특정 키 삭제
Cache::forget('key');

// 캐시 전체 클리어
Cache::flush();
Cache::flush()는 캐시의 “프리픽스” 설정과 관계없이 모든 엔트리를 삭제합니다. 여러 애플리케이션이 공유하고 있는 캐시의 경우 주의해 주십시오.
Cache::flushLocks()를 사용하면 캐시 내의 모든 원자 락을 클리어할 수 있습니다.
Cache::flushLocks();

TTL 의 연장: Cache::touch()

기존 캐시 아이템의 유효 기간을 연장합니다.
// 초 수로 지정
Cache::touch('key', 3600);

// Carbon 인스턴스로 지정
Cache::touch('key', now()->addHours(2));

캐시 메모화

memo 드라이버를 사용하면 동일 요청 내에서의 캐시 접근을 메모리에 캐시합니다. 같은 키에의 반복 접근이 많은 경우, 캐시 스토어에의 라운드트립을 삭감할 수 있습니다.
use Illuminate\Support\Facades\Cache;

// 기본 스토어를 메모화
$value = Cache::memo()->get('key');

// Redis 스토어를 메모화
$value = Cache::memo('redis')->get('key');
// 최초의 접근은 캐시 스토어에 접근
$value = Cache::memo()->get('key');

// 두 번째 이후는 메모리에서 반환(스토어에 접근하지 않음)
$value = Cache::memo()->get('key');

캐시 태그

관련된 캐시 아이템을 태그로 그룹화하여 함께 삭제할 수 있습니다.
캐시 태그는 file, dynamodb, database, storage 드라이버에서는 사용할 수 없습니다. redismemcached 드라이버가 필요합니다.

태그 부착 캐시의 구조

여러 태그를 붙인 캐시 아이템은 어느 태그로든 함께 삭제할 수 있습니다.

태그 부착 캐시의 저장과 취득

use Illuminate\Support\Facades\Cache;

// 태그를 붙여 저장
Cache::tags(['people', 'artists'])->put('John', $john, $seconds);
Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);

// 태그를 지정해 취득
$john = Cache::tags(['people', 'artists'])->get('John');
$anne = Cache::tags(['people', 'authors'])->get('Anne');

태그 부착 캐시의 삭제

// 'people' 과 'authors' 태그를 가지는 모든 캐시를 삭제(John도 Anne도 삭제)
Cache::tags(['people', 'authors'])->flush();

// 'authors' 태그만 삭제(Anne 만 삭제. John 은 남음)
Cache::tags('authors')->flush();
사용자별이나 기사별 등, 그룹 단위로 캐시를 무효화하고 싶은 경우에 태그가 도움이 됩니다. 예: Cache::tags(['user', "user:{$userId}"])->flush()로 그 사용자에 관한 모든 캐시를 클리어할 수 있습니다.

원자 조작(락)

Cache::lock()을 사용하면 분산 락을 구현할 수 있으며, 여러 프로세스나 병렬 요청으로부터의 경합 상태를 방지합니다.
이 기능은 memcached, redis, dynamodb, database, file, array 캐시 드라이버에서 이용할 수 있습니다. 모든 서버가 같은 중앙 캐시 서버와 통신하고 있어야 합니다.

기본적인 락

use Illuminate\Support\Facades\Cache;

$lock = Cache::lock('foo', 10);

if ($lock->get()) {
    // 락 취득 성공(10초간 유효)

    // 무언가의 배타 처리를 수행

    $lock->release();
}
클로저를 전달하면 처리 완료 후에 자동으로 락을 해방합니다.
Cache::lock('foo', 10)->get(function () {
    // 락을 자동 해방
});

락 대기

락을 취득할 수 있을 때까지 최대 지정 초 수 대기합니다. 타임아웃되면 LockTimeoutException이 던져집니다.
use Illuminate\Contracts\Cache\LockTimeoutException;

$lock = Cache::lock('foo', 10);

try {
    $lock->block(5); // 최대 5초 대기

    // 락 취득 후의 처리

} catch (LockTimeoutException $e) {
    // 락 취득 실패
} finally {
    $lock->release();
}
클로저를 사용하면 단순하게 쓸 수 있습니다.
Cache::lock('foo', 10)->block(5, function () {
    // 최대 5초 대기해서 락을 취득하고, 완료 후에 자동 해방
});

중복 실행 방지: withoutOverlapping()

동일 처리의 다중 실행을 방지하는 단순한 방법입니다.
Cache::withoutOverlapping('foo', function () {
    // 동시에 1개만 실행됨
});

// 대기 시간과 락 보유 시간을 커스터마이즈
Cache::withoutOverlapping('foo', function () {
    // ...
}, lockFor: 120, waitFor: 5);

동시 실행 수 제한: funnel()

동시에 실행할 수 있는 처리 수를 제한합니다.
Cache::funnel('foo')
    ->limit(3)          // 최대 3개의 병렬 실행을 허가
    ->releaseAfter(60)  // 60초 후에 자동 해방
    ->block(10)         // 최대 10초 대기
    ->then(function () {
        // 동시 실행 슬롯을 확보할 수 있었던 경우의 처리
    }, function () {
        // 슬롯을 확보할 수 없었던 경우의 처리
    });

프로세스 간에서의 락 이양

Web 요청에서 락을 취득하고 큐 잡에서 해방하는 케이스입니다.
// 요청에서 락을 취득해 잡을 디스패치
$lock = Cache::lock('processing', 120);

if ($lock->get()) {
    ProcessPodcast::dispatch($podcast, $lock->owner());
}

// 큐 잡 내에서 락을 해방
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();
}

캐시 헬퍼

cache() 헬퍼 함수를 사용하면 Cache 파사드와 같은 조작을 보다 단순하게 쓸 수 있습니다.
// 값을 취득
$value = cache('key');

// 값을 저장(유효 기간 포함)
cache(['key' => 'value'], $seconds);
cache(['key' => 'value'], now()->addMinutes(10));

// 인수 없이 파사드와 같은 인스턴스를 취득
cache()->remember('users', $seconds, function () {
    return DB::table('users')->get();
});

실전 예시

데이터베이스 쿼리 결과의 캐시

1

컨트롤러에서 쿼리 결과를 캐시

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

public function index(): array
{
    $users = Cache::remember('all-users', 3600, function () {
        return DB::table('users')->orderBy('name')->get();
    });

    return compact('users');
}
2

데이터가 업데이트되면 캐시를 삭제

public function store(Request $request): RedirectResponse
{
    User::create($request->validated());

    // 캐시를 삭제해 다음 접근 시에 재취득
    Cache::forget('all-users');

    return redirect()->route('users.index');
}

Eloquent 모델의 캐시

use App\Models\Product;
use Illuminate\Support\Facades\Cache;

// 카테고리별 상품 목록을 1시간 캐시
public function byCategory(int $categoryId): array
{
    $products = Cache::remember(
        "products:category:{$categoryId}",
        3600,
        fn () => Product::where('category_id', $categoryId)
            ->where('is_active', true)
            ->orderBy('name')
            ->get()
    );

    return compact('products');
}

API 응답의 캐시

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

public function getWeather(string $city): array
{
    return Cache::remember(
        "weather:{$city}",
        1800, // 30분간 캐시
        function () use ($city) {
            $response = Http::get('https://api.weather.example.com/current', [
                'city' => $city,
            ]);

            return $response->json();
        }
    );
}

캐시 태그를 사용한 그룹 관리

use Illuminate\Support\Facades\Cache;

class ArticleController extends Controller
{
    public function show(Article $article): array
    {
        $data = Cache::tags(['articles', "article:{$article->id}"])
            ->remember("article:{$article->id}:detail", 3600, function () use ($article) {
                return $article->load(['author', 'tags', 'comments']);
            });

        return compact('data');
    }

    public function update(Request $request, Article $article): RedirectResponse
    {
        $article->update($request->validated());

        // 이 기사에 관한 모든 캐시를 클리어
        Cache::tags(["article:{$article->id}"])->flush();

        return redirect()->route('articles.show', $article);
    }
}

정리

메서드설명
Cache::get('key')캐시를 취득
Cache::put('key', $value, $ttl)캐시를 저장
Cache::remember('key', $ttl, fn)취득 또는 저장(가장 중요)
Cache::forget('key')캐시를 삭제
Cache::has('key')캐시의 존재 확인
Cache::flush()모든 캐시를 클리어
Cache::forever('key', $value)영구 저장
Cache::pull('key')취득한 후 삭제
Cache::increment('key')숫자를 인크리먼트
Cache::tags([...])->flush()태그 부착 캐시를 클리어
  • 개발·소규모: file 또는 database
  • 운영·고트래픽: redis(Laravel Horizon과 조합하면 감시도 용이)
  • AWS 환경: dynamodb 또는 storage(S3 디스크를 유용하는 경우)
  • 테스트: array 또는 null
  • 캐시 키는 앱 전체에서 유니크해지도록 명명한다(예: users:1:profile)
  • 데이터가 업데이트되면 Cache::forget() 또는 Cache::tags()->flush()로 캐시를 무효화한다
  • 캐시의 TTL은 너무 짧지도 너무 길지도 않도록, 데이터의 업데이트 빈도에 맞춰 설정한다
  • Cache::remember()를 사용함으로써 캐시 미스 시의 처리를 간결하게 쓸 수 있다
  • 운영 환경에서는 Redis를 사용하고 페일오버 설정도 검토한다
마지막 수정일 2026년 7월 13일