Laravel Scout

업데이트됨

번역일: 2026년 7월 7일

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

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

Laravel Scout

소개

Laravel Scout는 Eloquent 모델에 전문(full-text) 검색 기능을 간편하게 추가할 수 있는 드라이버 기반 패키지입니다. Scout는 모델 옵저버를 활용해 Eloquent 레코드와 검색 인덱스를 자동으로 동기화합니다.

Scout는 현재 Algolia, Meilisearch, Typesense, 그리고 MySQL / PostgreSQL 기반의 database 엔진을 공식 지원합니다. 이 외에도 커뮤니티가 개발한 다양한 서드파티 드라이버가 존재하며, 필요하다면 직접 커스텀 엔진을 작성할 수도 있습니다.

NOTE

외부 검색 서비스 없이 간단히 시작하고 싶다면 database 또는 collection 엔진을 바로 사용할 수 있습니다. 작은 규모의 애플리케이션이나 개발 초기 단계에 적합합니다.

설치

먼저 Composer로 Scout 패키지를 설치합니다.

composer require laravel/scout

설치 후 vendor:publish Artisan 명령어로 Scout 설정 파일을 퍼블리시합니다.

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

이 명령을 실행하면 config/scout.php 파일이 생성됩니다. 해당 파일에서 검색 엔진 드라이버, 인덱스 접두사 등 다양한 옵션을 설정할 수 있습니다.

큐 사용

Scout를 사용할 때 반드시 큐를 설정해야 하는 것은 아닙니다. 하지만 인덱싱 작업을 큐로 처리하면 애플리케이션의 응답 시간이 크게 개선됩니다. 검색 인덱스 동기화가 HTTP 요청과 분리되어 백그라운드에서 처리되기 때문입니다.

큐를 사용하려면 config/scout.phpqueue 옵션을 true로 설정하세요.

'queue' => true,

큐 연결과 큐 이름을 명시적으로 지정하고 싶을 때는 아래처럼 배열 형태로 설정할 수 있습니다.

'queue' => [ 'connection' => 'redis', 'queue' => 'scout', ],

이렇게 설정하면 Scout가 scout 큐로 Job을 디스패치합니다. 해당 큐를 처리하는 워커를 별도로 실행해야 인덱싱이 실제로 수행됩니다.

php artisan queue:work redis --queue=scout

드라이버 사전 요구사항

각 외부 검색 엔진마다 추가 패키지나 설정이 필요합니다. 사용할 엔진에 맞는 항목을 확인하세요.

Algolia

Algolia를 사용하려면 config/scout.phpALGOLIA_APP_IDALGOLIA_SECRET 키를 설정한 뒤, Composer로 Algolia PHP SDK를 설치합니다.

composer require algolia/algoliasearch-client-php

Meilisearch

Meilisearch는 속도가 매우 빠른 오픈소스 검색 엔진으로, 로컬 환경이나 자체 서버에 직접 설치해서 운용할 수 있습니다. Composer로 Meilisearch PHP SDK를 설치합니다.

composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle

그런 다음 .env 파일에 SCOUT_DRIVER와 Meilisearch 접속 정보를 설정합니다.

SCOUT_DRIVER=meilisearch MEILISEARCH_HOST=http://127.0.0.1:7700 MEILISEARCH_KEY=masterKey

로컬 개발 환경에서는 Laravel Sail을 통해 Meilisearch를 손쉽게 구동할 수 있습니다.

Meilisearch에 대한 자세한 내용은 공식 문서를 참고하세요.

Typesense

Typesense는 매우 빠른 오픈소스 검색 엔진으로, Algolia나 Meilisearch와 유사한 방식으로 동작합니다. Composer로 Typesense PHP SDK를 설치합니다.

composer require typesense/typesense-php

그런 다음 .env 파일에 Typesense 접속 정보를 설정합니다.

SCOUT_DRIVER=typesense TYPESENSE_API_KEY=masterKey TYPESENSE_HOST=localhost

Typesense에 대한 자세한 내용은 공식 문서를 참고하세요.

설정

검색 가능한 데이터 설정

특정 Eloquent 모델에 Scout를 적용하려면 해당 모델에 Laravel\Scout\Searchable 트레이트를 추가합니다. 이 트레이트는 모델 옵저버를 자동으로 등록하여 모델이 저장되거나 삭제될 때 검색 인덱스를 갱신합니다.

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; class Post extends Model { use Searchable; }

Searchable 트레이트를 추가하는 것만으로 기본 동작이 시작됩니다. 기본적으로 모델의 toArray() 결과가 검색 인덱스에 저장됩니다. 인덱스에 포함할 데이터를 직접 지정하려면 모델에 toSearchableArray 메서드를 정의하세요.

/** * 모델의 검색 인덱스 데이터 배열을 반환합니다. * * @return array<string, mixed> */ public function toSearchableArray(): array { $array = $this->toArray(); // 데이터 가공 예시: 특정 필드만 포함하거나 형식을 변환할 수 있습니다. return $array; }

NOTE

관계 데이터나 커스텀 캐스트 등 복잡한 데이터를 인덱스에 포함해야 할 때도 toSearchableArray에서 자유롭게 가공할 수 있습니다.

데이터베이스 / 컬렉션 엔진

데이터베이스 엔진

database 엔진은 MySQL 또는 PostgreSQL의 LIKE 쿼리와 전문 검색 인덱스를 활용해 검색 결과를 반환합니다. 외부 검색 서비스 없이 기존 데이터베이스만으로 검색을 구현할 수 있어 소규모 애플리케이션에 적합합니다.

database 엔진을 사용하려면 .env 파일의 SCOUT_DRIVERdatabase로 설정하세요.

SCOUT_DRIVER=database

database 엔진을 기본 드라이버로 지정했다면 검색 가능한 데이터 설정을 마친 후 바로 검색 쿼리를 실행할 수 있습니다. Algolia, Meilisearch, Typesense와 달리 별도의 인덱스 동기화 명령이 필요하지 않습니다.

데이터베이스 검색 전략 커스터마이징

기본적으로 database 엔진은 toSearchableArray에 설정된 모든 컬럼에 대해 LIKE 쿼리를 실행합니다. 하지만 성능이나 정확도를 높이기 위해 특정 컬럼에 전문 검색 인덱스를 활용하도록 설정할 수 있습니다.

use Laravel\Scout\Attributes\SearchUsingFullText; use Laravel\Scout\Attributes\SearchUsingPrefix; /** * 모델의 검색 인덱스 데이터 배열을 반환합니다. * * @return array<string, mixed> */ #[SearchUsingPrefix(['id', 'email'])] #[SearchUsingFullText(['bio'])] public function toSearchableArray(): array { return [ 'id' => $this->id, 'email' => $this->email, 'bio' => $this->bio, ]; }

NOTE

전문 검색 인덱스(FULLTEXT)를 사용하려면 해당 컬럼에 실제로 전문 검색 인덱스가 생성되어 있어야 합니다. 마이그레이션에서 $table->fullText('bio') 형태로 추가할 수 있습니다.

컬렉션 엔진

collection 엔진은 데이터베이스에서 모든 레코드를 불러온 뒤 PHP에서 직접 필터링합니다. 외부 서비스나 특수 데이터베이스 인덱스 없이 동작하지만, 데이터가 많을수록 성능이 낮아지므로 주로 개발이나 테스트 환경에서 사용합니다.

collection 엔진을 사용하려면 .env 파일의 SCOUT_DRIVERcollection으로 설정하세요.

SCOUT_DRIVER=collection

collection 엔진도 database 엔진과 마찬가지로 별도의 인덱스 동기화 없이 즉시 사용할 수 있습니다.

데이터베이스 엔진과 컬렉션 엔진의 차이

데이터베이스 엔진컬렉션 엔진
검색 방식SQL LIKE / FULLTEXT 쿼리PHP 메모리 내 필터링
외부 서비스 필요불필요불필요
적합한 환경소규모 프로덕션개발 / 테스트
대용량 데이터적합부적합

서드파티 엔진 설정

모델 인덱스 설정

각 Eloquent 모델은 해당 모델의 모든 검색 가능 레코드를 담는 검색 "인덱스"와 연결됩니다. 인덱스는 관계형 데이터베이스의 테이블과 유사한 개념입니다. 기본적으로 모델의 테이블 이름(예: posts)이 인덱스 이름으로 사용됩니다. 인덱스 이름을 직접 지정하려면 모델에 searchableAs 메서드를 오버라이드하세요.

/** * 모델과 연결된 인덱스 이름을 반환합니다. */ public function searchableAs(): string { return 'posts_index'; }

Algolia

Algolia를 사용할 때는 config/scout.phpidsecret 자격 증명을 입력합니다.

'algolia' => [ 'id' => env('ALGOLIA_APP_ID', ''), 'secret' => env('ALGOLIA_SECRET', ''), ],

Meilisearch

Meilisearch를 사용할 때는 config/scout.php에 호스트와 키를 설정합니다.

'meilisearch' => [ 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), 'key' => env('MEILISEARCH_KEY', null), 'index-settings' => [ // 인덱스별 설정을 여기에 추가할 수 있습니다. ], ],

Meilisearch는 인덱스별로 필터 가능한 속성, 정렬 가능한 속성 등을 상세하게 설정할 수 있습니다. 예를 들어 Post 모델의 인덱스에 필터 가능한 속성을 지정하려면 다음과 같이 설정합니다.

'meilisearch' => [ 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), 'key' => env('MEILISEARCH_KEY', null), 'index-settings' => [ 'posts' => [ 'filterableAttributes' => ['id', 'published_at'], 'sortableAttributes' => ['published_at'], ], ], ],

인덱스 설정을 변경한 후에는 반드시 scout:sync-index-settings Artisan 명령어를 실행해야 설정이 Meilisearch 서버에 반영됩니다.

php artisan scout:sync-index-settings

Typesense

Typesense를 사용할 때는 config/scout.php에 다음과 같이 설정합니다.

'typesense' => [ 'client-settings' => [ 'api_key' => env('TYPESENSE_API_KEY', 'xyz'), 'nodes' => [ [ 'host' => env('TYPESENSE_HOST', 'localhost'), 'port' => env('TYPESENSE_PORT', '8108'), 'path' => env('TYPESENSE_PATH', ''), 'protocol' => env('TYPESENSE_PROTOCOL', 'http'), ], ], 'nearest_node' => [ 'host' => env('TYPESENSE_HOST', 'localhost'), 'port' => env('TYPESENSE_PORT', '8108'), 'path' => env('TYPESENSE_PATH', ''), 'protocol' => env('TYPESENSE_PROTOCOL', 'http'), ], 'connection_timeout_seconds' => env('TYPESENSE_CONNECTION_TIMEOUT_SECONDS', 2), 'healthcheck_interval_seconds' => env('TYPESENSE_HEALTHCHECK_INTERVAL_SECONDS', 30), 'num_retries' => env('TYPESENSE_NUM_RETRIES', 3), 'retry_interval_seconds' => env('TYPESENSE_RETRY_INTERVAL_SECONDS', 1), ], 'model-settings' => [ // 모델별 컬렉션 스키마 등을 여기에 설정합니다. ], ],

Typesense는 인덱스를 "컬렉션"이라고 부르며, 각 컬렉션에 스키마를 정의해야 합니다. 모델별 스키마는 model-settings에 추가합니다. 자세한 내용은 Typesense 공식 문서를 참고하세요.

서드파티 엔진 인덱싱

일괄 가져오기

기존에 운영 중인 프로젝트에 Scout를 도입할 경우, 이미 데이터베이스에 있는 레코드들을 검색 인덱스에 일괄 등록해야 합니다. scout:import Artisan 명령어를 사용하세요.

php artisan scout:import "App\Models\Post"

레코드를 모두 삭제하고 인덱스를 새로 만들려면 scout:flush 명령어를 먼저 실행한 뒤 가져오기를 진행합니다.

php artisan scout:flush "App\Models\Post"

가져오기 쿼리 커스터마이징

일괄 가져오기 시 사용하는 쿼리를 수정하려면 모델에 makeAllSearchableUsing 메서드를 정의합니다. 이 메서드는 가져오기 전에 필요한 관계를 eager 로딩할 때 특히 유용합니다.

use Illuminate\Database\Eloquent\Builder; /** * 전체 검색 가능 상태로 만들 때 사용되는 쿼리를 수정합니다. */ protected function makeAllSearchableUsing(Builder $query): Builder { return $query->with('author'); }

NOTE

makeAllSearchableUsing은 큐를 사용하는 일괄 가져오기에는 적용되지 않습니다. 큐 Job이 처리될 때 관계 데이터는 자동으로 복원되지 않습니다.

레코드 추가

모델에 Searchable 트레이트를 추가하면, save 또는 create를 호출할 때마다 Scout가 자동으로 검색 인덱스를 갱신합니다. 큐를 사용하도록 설정한 경우에는 이 작업이 백그라운드에서 처리됩니다.

use App\Models\Post; $post = new Post; $post->title = 'Scout 시작하기'; $post->save();

`searchable` 메서드로 직접 추가

Eloquent 쿼리 결과를 수동으로 인덱스에 추가하려면 searchable 메서드를 사용합니다. 이 메서드는 쿼리 결과를 청크 단위로 나눠 검색 엔진에 등록합니다.

use App\Models\Post; // Eloquent 쿼리 결과를 인덱스에 추가 Post::all()->searchable(); // 관계를 통해서도 인덱스에 추가 가능 $user->posts()->searchable();

이미 인덱스에 있는 레코드라면 업데이트되고, 없는 레코드라면 새로 추가됩니다.

레코드 수정

모델을 업데이트하면 Scout가 자동으로 검색 인덱스를 갱신합니다. 별도 작업 없이 save()만 호출하면 됩니다.

$post = Post::find(1); $post->title = '업데이트된 제목'; $post->save();

Eloquent 쿼리 결과에 대해 searchable 메서드를 호출해도 레코드를 업서트(upsert)할 수 있습니다.

Post::where('status', 'published')->searchable();

관계의 역방향에서 모델 인덱스를 업데이트하려면 관계 인스턴스에서 searchable을 호출합니다.

$user->posts()->searchable();

레코드 삭제

데이터베이스에서 모델을 삭제하면 Scout가 자동으로 검색 인덱스에서도 제거합니다.

$post = Post::find(1); $post->delete();

소프트 삭제 모델의 경우에 대한 처리는 소프트 삭제 섹션을 참고하세요.

수동으로 인덱스에서 레코드를 제거하려면 unsearchable 메서드를 사용합니다.

// 특정 모델 인스턴스 제거 $post->unsearchable(); // Eloquent 쿼리 결과 일괄 제거 Post::where('status', 'draft')->unsearchable(); // 관계를 통해 제거 $user->posts()->unsearchable();

인덱싱 일시 중지

모델 데이터를 여러 건 수정하는 동안 일시적으로 인덱스 동기화를 멈추고 싶을 때는 withoutSyncingToSearch 메서드를 사용합니다. 이 메서드는 클로저를 받으며, 클로저 내에서 수행된 모든 모델 작업은 검색 인덱스에 반영되지 않습니다.

use App\Models\Post; Post::withoutSyncingToSearch(function () { // 여러 건의 모델 수정 작업 Post::factory()->count(20)->create(); });

조건부 검색 가능 모델 인스턴스

특정 조건을 만족하는 모델만 검색 인덱스에 등록하고 싶을 때는 shouldBeSearchable 메서드를 오버라이드합니다.

/** * 이 모델 인스턴스가 검색 가능한 상태인지 판단합니다. */ public function shouldBeSearchable(): bool { return $this->isPublished(); }

shouldBeSearchablefalse를 반환하면 Scout는 해당 모델을 인덱스에 추가하거나 갱신하지 않습니다. 이미 인덱스에 있는 모델이 false를 반환하도록 변경되면 자동으로 인덱스에서 제거됩니다.

NOTE

database 엔진을 사용할 때는 shouldBeSearchable이 적용되지 않습니다. 데이터베이스 엔진은 항상 데이터베이스에서 직접 데이터를 조회하기 때문입니다. 유사한 동작이 필요하다면 Where 절을 사용하세요.

검색

search 메서드를 사용해 모델을 검색합니다. 이 메서드는 검색할 문자열을 인자로 받습니다. get 메서드를 체이닝하면 검색 결과로 Eloquent 모델 컬렉션을 반환합니다.

use App\Models\Post; $posts = Post::search('Scout 시작하기')->get();

Scout는 먼저 검색 엔진에서 결과를 가져오고, 해당 ID를 기반으로 데이터베이스에서 Eloquent 모델을 조회합니다. 따라서 일반 Eloquent 컬렉션처럼 관계 로딩, 뷰 렌더링 등을 자유롭게 사용할 수 있습니다.

Eloquent 모델이 아닌 검색 엔진의 원시 결과(raw result)를 가져오려면 raw 메서드를 사용합니다.

$results = Post::search('Scout')->raw();

특정 인덱스를 지정해서 검색하려면 within 메서드를 사용합니다.

$posts = Post::search('Scout') ->within('posts_archive_index') ->get();

Where 절

Scout는 검색 쿼리에 간단한 where 조건을 추가할 수 있습니다. 현재는 숫자형 등가 비교만 지원하며, 주로 소유자 ID 등으로 검색 범위를 제한할 때 유용합니다.

use App\Models\Post; $posts = Post::search('Scout') ->where('user_id', 1) ->get();

whereIn 메서드를 사용하면 값 목록으로 결과를 필터링할 수 있습니다.

$posts = Post::search('Scout') ->whereIn('status', ['published', 'draft']) ->get();

whereNotIn 메서드는 특정 값들을 결과에서 제외합니다.

$posts = Post::search('Scout') ->whereNotIn('status', ['archived']) ->get();

whereNull / whereNotNull 메서드로 null 여부를 필터링할 수도 있습니다.

$posts = Post::search('Scout') ->whereNull('deleted_at') ->get(); $posts = Post::search('Scout') ->whereNotNull('published_at') ->get();

페이지네이션

Scout는 Eloquent의 페이지네이션과 동일한 방식으로 검색 결과를 페이지네이션할 수 있습니다. paginate 메서드를 사용하세요.

use App\Models\Post; $posts = Post::search('Scout')->paginate();

페이지당 결과 수를 지정하려면 첫 번째 인자로 숫자를 전달합니다.

$posts = Post::search('Scout')->paginate(15);

결과를 가져왔으면 Blade에서 일반 Eloquent 쿼리와 동일하게 결과를 렌더링하고 페이지네이션 링크를 표시할 수 있습니다.

<div class="container"> @foreach ($posts as $post) {{ $post->title }} @endforeach </div> {{ $posts->links() }}

커서 기반 페이지네이션을 사용하려면 simplePaginate 메서드 대신 cursorPaginate 메서드를 사용합니다.

$posts = Post::search('Scout')->cursorPaginate();

NOTE

커서 페이지네이션(cursorPaginate)은 일부 검색 엔진에서 지원되지 않을 수 있습니다. 사용 전 해당 엔진의 지원 여부를 확인하세요.

소프트 삭제

인덱스에 등록된 모델이 소프트 삭제를 사용하고, 소프트 삭제된 모델도 검색 결과에 포함시키고 싶다면 config/scout.phpsoft_delete 옵션을 true로 설정합니다.

'soft_delete' => true,

이 옵션을 활성화하면 Scout가 소프트 삭제된 레코드를 인덱스에서 제거하지 않고 __soft_deleted 속성을 추가합니다.

기본 검색 시에는 소프트 삭제된 레코드가 결과에서 자동 제외됩니다. 소프트 삭제된 레코드만 조회하려면 onlyTrashed를 사용합니다.

use App\Models\Post; $posts = Post::search('Scout')->onlyTrashed()->get();

소프트 삭제된 레코드를 포함한 전체 결과를 조회하려면 withTrashed를 사용합니다.

$posts = Post::search('Scout')->withTrashed()->get();

엔진 검색 커스터마이징

검색 엔진이 제공하는 고급 옵션을 활용해야 할 때는 options 메서드를 사용합니다. 예를 들어 Algolia에서 특정 속성만 검색 대상으로 지정하거나 부스팅 옵션을 적용할 때 유용합니다.

use App\Models\Post; $posts = Post::search('Scout') ->options(['attributesToRetrieve' => ['title', 'body']]) ->get();

엔진별로 지원하는 옵션이 다르므로 각 엔진의 공식 문서를 참고하세요.

검색 쿼리가 실행되기 직전에 로직을 추가하려면 모델에 queryCallback을 등록하거나, SearchUsingFullText 등의 어트리뷰트를 활용하세요.

커스텀 엔진

Scout에서 제공하지 않는 검색 엔진을 직접 구현하고 싶다면 커스텀 엔진을 만들 수 있습니다. 엔진 클래스는 Laravel\Scout\Engines\Engine 추상 클래스를 상속하고, 아래 메서드들을 구현해야 합니다.

use Laravel\Scout\Builder; abstract public function update($models); abstract public function delete($models); abstract public function search(Builder $builder); abstract public function paginate(Builder $builder, $perPage, $page); abstract public function mapIds($results); abstract public function map(Builder $builder, $results, $model); abstract public function getTotalCount($results); abstract public function flush($model);

엔진을 구현했다면 서비스 프로바이더의 boot 메서드에서 Scout 엔진 매니저에 등록합니다.

use App\Search\CustomSearchEngine; use Laravel\Scout\EngineManager; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { resolve(EngineManager::class)->extend('custom', function () { return new CustomSearchEngine; }); }

엔진을 등록한 후 config/scout.phpdriver 옵션을 등록한 이름으로 설정합니다.

'driver' => env('SCOUT_DRIVER', 'custom'),

Scout

목차

소개

Laravel ScoutEloquent 모델에 전문 검색(Full-text Search) 기능을 손쉽게 추가할 수 있는 드라이버 기반 패키지입니다. 모델 옵저버(Observer)를 활용해 Eloquent 레코드가 변경될 때 검색 인덱스를 자동으로 동기화합니다.

Scout는 별도의 외부 서비스 없이 사용할 수 있는 database 엔진을 기본 제공합니다. 이 엔진은 MySQL / PostgreSQL의 전문 검색 인덱스와 LIKE 절을 활용하므로, 대부분의 프로젝트에서는 이것만으로도 충분합니다. Laravel에서 사용 가능한 검색 옵션 전반이 궁금하다면 검색 문서를 참고하세요.

대규모 서비스에서 오타 허용(typo tolerance), 패싯 필터링(faceted filtering), 위치 기반 검색(geo-search) 등의 고급 기능이 필요한 경우에는 Algolia, Meilisearch, Typesense 드라이버를 사용할 수 있습니다. 로컬 개발 환경에 특화된 "collection" 드라이버도 제공되며, 필요에 따라 커스텀 엔진을 직접 구현할 수도 있습니다.

설치

Composer로 Scout 패키지를 설치합니다.

composer require laravel/scout

설치 후 vendor:publish Artisan 명령어로 Scout 설정 파일을 퍼블리시합니다.

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

명령어를 실행하면 config/scout.php 파일이 생성됩니다. 이 파일에서 검색 엔진 드라이버, 인덱스 접두사 등 Scout의 주요 옵션을 구성할 수 있습니다.

마지막으로, 검색 기능을 추가할 모델에 Laravel\Scout\Searchable 트레이트를 추가합니다. 이 트레이트는 모델 옵저버를 자동으로 등록해 모델을 검색 인덱스와 동기화된 상태로 유지합니다.

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; class Post extends Model { use Searchable; }

큐 사용 권장

Scout 사용 시 큐를 반드시 설정해야 하는 것은 아니지만, 큐 드라이버를 구성해 두는 것을 강력히 권장합니다. 큐 워커를 활용하면 모델 정보를 검색 인덱스에 동기화하는 작업이 백그라운드에서 처리되어 애플리케이션 응답 속도가 크게 향상됩니다.

큐 드라이버를 설정했다면 config/scout.phpqueue 옵션을 true로 변경하세요.

'queue' => true,

queue 옵션이 false인 경우에도 Algolia, Meilisearch 같은 일부 드라이버는 항상 비동기 방식으로 레코드를 인덱싱한다는 점을 참고하세요. 즉, Laravel 애플리케이션 내에서는 인덱싱 작업이 완료되었더라도 검색 엔진 자체에서 새 레코드가 즉시 반영되지 않을 수 있습니다.

Scout Job에서 사용할 커넥션과 큐 이름을 별도로 지정하려면 queue 옵션을 배열로 설정하세요.

'queue' => [ 'connection' => 'redis', 'queue' => 'scout', ],

드라이버 사전 준비사항

Algolia

Algolia 드라이버를 사용하려면 config/scout.php에 Algolia idsecret 자격 증명을 설정해야 합니다. 그런 다음 Algolia PHP SDK를 Composer로 설치합니다.

composer require algolia/algoliasearch-client-php

Meilisearch

Meilisearch는 빠르고 오픈 소스인 검색 엔진입니다. 로컬에서 Meilisearch를 실행하는 방법이 확실하지 않다면, Laravel의 공식 도커 개발 환경인 Laravel Sail을 활용할 수 있습니다.

Meilisearch 드라이버를 사용하려면 Composer로 Meilisearch PHP SDK를 설치합니다.

composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle

그런 다음 .env 파일에 SCOUT_DRIVER 환경 변수와 Meilisearch의 hostkey 자격 증명을 설정합니다.

SCOUT_DRIVER=meilisearch MEILISEARCH_HOST=http://127.0.0.1:7700 MEILISEARCH_KEY=masterKey

Meilisearch에 대한 자세한 내용은 Meilisearch 공식 문서를 참고하세요.

또한 사용 중인 meilisearch/meilisearch-php 버전과 호환되는 Meilisearch 바이너리 버전을 설치해야 합니다. Meilisearch의 바이너리 호환성 문서에서 확인할 수 있습니다.

NOTE

Meilisearch를 사용하는 애플리케이션에서 Scout를 업그레이드할 때는 항상 Meilisearch 서비스 자체의 추가 주요 변경 사항을 함께 확인하세요.

Typesense

Typesense는 매우 빠른 오픈 소스 검색 엔진으로, 키워드 검색, 시맨틱 검색, 지오 검색, 벡터 검색 등을 지원합니다.

Typesense는 자체 호스팅하거나 Typesense Cloud를 통해 사용할 수 있습니다.

Typesense 드라이버를 사용하려면 Composer로 Typesense PHP SDK를 설치합니다.

composer require typesense/typesense-php

그런 다음 .env 파일에 SCOUT_DRIVER 환경 변수와 Typesense 호스트 및 API 키를 설정합니다.

SCOUT_DRIVER=typesense TYPESENSE_API_KEY=masterKey TYPESENSE_HOST=localhost

필요에 따라 포트, 경로, 프로토콜도 지정할 수 있습니다.

TYPESENSE_PORT=8108 TYPESENSE_PATH= TYPESENSE_PROTOCOL=http

Typesense 컬렉션의 추가 설정 및 스키마 정의는 config/scout.phptypesense 항목에서 확인할 수 있습니다. Typesense에 대한 자세한 내용은 Typesense 공식 문서를 참고하세요.

설정

모델 인덱스 설정

각 Eloquent 모델은 특정 검색 "인덱스"와 동기화됩니다. 인덱스는 데이터베이스의 테이블과 유사한 개념으로, 해당 모델의 모든 검색 가능한 레코드를 담고 있습니다. 기본적으로 각 모델은 모델의 "테이블명"에 해당하는 인덱스에 저장됩니다. 보통 모델 이름의 복수형입니다(예: posts, users).

인덱스명을 직접 지정하려면 모델의 searchableAs 메서드를 오버라이드하세요.

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; class Post extends Model { use Searchable; /** * 모델과 연결된 인덱스명을 반환합니다. */ public function searchableAs(): string { return 'posts_index'; } }

검색 가능한 데이터 설정

기본적으로 모델의 toArray 결과 전체가 검색 인덱스에 저장됩니다. 인덱싱할 데이터를 직접 제어하려면 모델의 toSearchableArray 메서드를 오버라이드하세요.

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; class Post extends Model { use Searchable; /** * 모델의 인덱싱 가능한 데이터 배열을 반환합니다. * * @return array<string, mixed> */ public function toSearchableArray(): array { $array = $this->toArray(); // 데이터 커스터마이징 ... return $array; } }

Meilisearch 같은 일부 검색 엔진은 올바른 데이터 타입의 필드에서만 필터링(>, < 등)을 수행합니다. 따라서 이러한 엔진을 사용할 때는 toSearchableArray 메서드를 오버라이드할 때 숫자 값을 올바른 타입으로 캐스팅해야 합니다.

public function toSearchableArray(): array { return [ 'id' => (int) $this->id, 'name' => $this->name, 'price' => (float) $this->price, ]; }

필터 가능 데이터 및 인덱스 설정 (Meilisearch)

Scout의 다른 드라이버와 달리 Meilisearch는 인덱스 검색 설정을 미리 정의해야 합니다. 필터링 가능한 속성, 정렬 가능한 속성 등이 이에 해당합니다.

필터링 가능한 속성은 Scout의 where 메서드로 필터링할 항목이고, 정렬 가능한 속성은 Scout의 orderBy 메서드로 정렬할 항목입니다. 이 설정은 config/scout.phpmeilisearch 항목 중 index-settings 부분에 정의합니다.

use App\Models\User; use App\Models\Flight; 'meilisearch' => [ 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), 'key' => env('MEILISEARCH_KEY', null), 'index-settings' => [ User::class => [ 'filterableAttributes'=> ['id', 'name', 'email'], 'sortableAttributes' => ['created_at'], // 그 외 설정 필드... ], Flight::class => [ 'filterableAttributes'=> ['id', 'destination'], 'sortableAttributes' => ['updated_at'], ], ], ],

설정을 변경한 후에는 scout:sync-index-settings Artisan 명령어를 실행해 Meilisearch에 반영하세요. 배포 파이프라인에 이 명령어를 포함시키는 것을 권장합니다.

php artisan scout:sync-index-settings

모델 ID 설정

기본적으로 Scout는 모델의 기본 키(primary key)를 검색 인덱스에 저장되는 고유 ID로 사용합니다. 이 동작을 변경하려면 모델에서 getScoutKeygetScoutKeyName 메서드를 오버라이드하세요.

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; class User extends Model { use Searchable; /** * 모델 인덱싱에 사용할 값을 반환합니다. */ public function getScoutKey(): mixed { return $this->email; } /** * 모델 인덱싱에 사용할 키 이름을 반환합니다. */ public function getScoutKeyName(): mixed { return 'email'; } }

모델별 검색 엔진 설정

Scout는 기본적으로 config/scout.phpdriver 옵션에 설정된 검색 엔진을 사용합니다. 특정 모델에 다른 엔진을 사용하고 싶다면 모델에서 searchableUsing 메서드를 오버라이드하세요.

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Engines\Engine; use Laravel\Scout\EngineManager; use Laravel\Scout\Searchable; class User extends Model { use Searchable; /** * 모델 인덱싱에 사용할 엔진을 반환합니다. */ public function searchableUsing(): Engine { return app(EngineManager::class)->engine('meilisearch'); } }

사용자 식별

Scout를 사용하면 Algolia를 사용할 때 검색 요청을 특정 사용자와 연결할 수 있습니다. 인증된 사용자와 검색 요청을 연결하면 Algolia 대시보드에서 검색 분석을 확인할 때 유용합니다. .env에서 SCOUT_IDENTIFY 환경 변수를 true로 설정해 사용자 식별 기능을 활성화할 수 있습니다.

SCOUT_IDENTIFY=true

이 기능을 활성화하면 요청자의 IP 주소와 인증된 사용자의 기본 키가 Algolia에 전달되어 해당 사용자가 수행한 모든 검색 요청과 연결됩니다.

데이터베이스 / 컬렉션 엔진

데이터베이스 엔진

NOTE

데이터베이스 엔진은 현재 MySQL과 PostgreSQL을 지원합니다.

소규모~중규모 데이터베이스를 사용하거나, 작업 부하가 가벼운 애플리케이션이라면 Scout의 "database" 엔진이 가장 편리한 시작점입니다. 데이터베이스 엔진은 기존 데이터베이스에서 "where like" 절과 전문 검색 인덱스를 사용해 검색 결과를 필터링합니다. 따라서 별도로 외부 서비스에 데이터를 인덱싱할 필요 없이 바로 사용할 수 있습니다.

데이터베이스 엔진을 활성화하려면 .envSCOUT_DRIVER 환경 변수를 database로 설정하거나, config/scout.php에서 database 드라이버를 직접 지정하세요.

SCOUT_DRIVER=database

데이터베이스 엔진을 기본 드라이버로 지정했다면, 검색 가능한 데이터 설정을 완료한 후 바로 검색 쿼리 실행을 시작할 수 있습니다. Algolia, Meilisearch, Typesense와 달리 별도 인덱스 동기화 작업이 필요하지 않습니다.

데이터베이스 검색 전략 커스터마이징

기본적으로 데이터베이스 엔진은 검색 가능하도록 설정한 모든 모델 속성에 대해 "where like" 쿼리를 실행합니다. 하지만 경우에 따라 이 방식이 성능 저하를 유발할 수 있습니다. 이 경우 특정 컬럼에는 전문 검색 인덱스를 활용하거나, 접두사 일치(example%)로만 검색하도록 전략을 변경할 수 있습니다.

이 동작을 제어하려면 모델의 toSearchableArray 메서드에 PHP 속성(attribute)을 적용하세요.

  • #[SearchUsingFullText]: 해당 컬럼에 전문 검색 인덱스 사용
  • #[SearchUsingPrefix]: 해당 컬럼에 접두사(LIKE 'term%') 방식 사용
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Attributes\SearchUsingFullText; use Laravel\Scout\Attributes\SearchUsingPrefix; use Laravel\Scout\Searchable; class User extends Model { use Searchable; /** * 모델의 인덱싱 가능한 데이터 배열을 반환합니다. * * @return array<string, mixed> */ #[SearchUsingPrefix(['id', 'email'])] #[SearchUsingFullText(['bio'])] public function toSearchableArray(): array { return [ 'id' => $this->id, 'email' => $this->email, 'bio' => $this->bio, ]; } }

NOTE

전문 검색 인덱스(SearchUsingFullText)를 적용하기 전에, 해당 컬럼에 전문 검색 인덱스가 생성되어 있어야 합니다.

컬렉션 엔진

로컬 개발 환경에서 Algolia, Meilisearch, Typesense 검색 엔진을 직접 설치하고 운영하는 것이 번거롭다면 "collection" 엔진을 사용하세요. 컬렉션 엔진은 데이터베이스에서 모든 레코드를 가져온 뒤 PHP의 Illuminate\Support\Collection에서 "where" 절과 컬렉션 필터링을 통해 결과를 반환합니다. 별도 인덱싱이 필요 없으며, 로컬 개발 시 편리하게 검색 기능을 테스트할 수 있습니다.

컬렉션 엔진을 사용하려면 .envSCOUT_DRIVER 환경 변수를 collection으로 설정하세요.

SCOUT_DRIVER=collection

컬렉션 드라이버를 기본 드라이버로 지정하면, 별도 인덱스 동기화 없이 바로 검색 쿼리를 실행할 수 있습니다.

컬렉션 엔진과 데이터베이스 엔진의 차이

두 엔진 모두 외부 서비스가 필요 없다는 점에서 비슷해 보이지만, 실제 동작 방식은 다릅니다.

  • 데이터베이스 엔진: 데이터베이스에서 직접 LIKE, 전문 검색 쿼리를 실행합니다.
  • 컬렉션 엔진: 데이터베이스에서 모든 레코드를 가져온 뒤 PHP 수준에서 필터링합니다.

컬렉션 엔진은 데이터 양이 많을수록 성능이 떨어질 수 있어 실제 운영 환경에는 적합하지 않습니다. 로컬 개발 전용으로 사용하는 것을 권장합니다.

인덱싱

배치 임포트

Scout를 기존 프로젝트에 추가하는 경우, 이미 데이터베이스에 있는 레코드를 검색 인덱스로 가져와야 합니다. Scout의 scout:import Artisan 명령어를 사용하면 기존 레코드를 일괄로 검색 인덱스에 임포트할 수 있습니다.

php artisan scout:import "App\Models\Post"

인덱스의 모든 레코드를 제거하고 싶다면 scout:flush 명령어를 사용하세요.

php artisan scout:flush "App\Models\Post"

임포트 쿼리 수정

배치 임포트 시 불러오는 모델 쿼리를 커스터마이징하고 싶다면 모델에 makeAllSearchableUsing 메서드를 정의하세요. 관계(relationship) 즉시 로딩(eager loading) 등 임포트 전에 필요한 설정을 여기에 추가할 수 있습니다.

use Illuminate\Database\Eloquent\Builder; /** * 모든 모델을 검색 가능하게 만들 때 사용할 쿼리를 반환합니다. */ protected function makeAllSearchableUsing(Builder $query): Builder { return $query->with('author'); }

NOTE

makeAllSearchableUsing은 큐를 사용하여 배치 임포트하는 경우에는 적용되지 않을 수 있습니다. 큐 Job으로 모델 컬렉션을 처리할 때는 관계가 자동으로 복원되지 않습니다.

레코드 추가

모델에 Laravel\Scout\Searchable 트레이트를 추가하면, 이후에는 모델 인스턴스를 save 또는 create하기만 하면 자동으로 검색 인덱스에 추가됩니다. 큐를 사용하도록 Scout를 설정한 경우 이 작업은 큐 워커가 백그라운드에서 처리합니다.

use App\Models\Order; $order = new Order; // ... $order->save();

쿼리를 통한 레코드 추가

Eloquent 쿼리로 여러 모델을 한 번에 검색 인덱스에 추가하려면 searchable 메서드를 체이닝하세요. searchable은 쿼리 결과를 청크로 분할하여 검색 인덱스에 추가합니다. 큐를 사용하는 경우 모든 청크는 큐 워커가 백그라운드에서 처리합니다.

use App\Models\Order; // Eloquent 쿼리로 추가 Order::where('price', '>', 100)->searchable(); // 관계를 통해 추가 $user->orders()->searchable(); // 컬렉션으로 추가 $orders->searchable();

레코드 수정

Eloquent 모델을 수정하려면 모델 인스턴스의 속성을 변경한 뒤 save를 호출하세요. Scout가 자동으로 검색 인덱스에 변경 사항을 반영합니다.

use App\Models\Order; $order = Order::find(1); // 주문 수정 ... $order->save();

여러 모델을 한 번에 업데이트할 때는 Eloquent 쿼리 인스턴스에 searchable을 체이닝하세요.

Order::where('price', '>', 100)->searchable();

관계에 속한 모든 모델의 인덱스를 업데이트하려면 관계 인스턴스에 searchable을 호출하세요.

$user->orders()->searchable();

이미 Eloquent 모델 컬렉션이 있다면 컬렉션 인스턴스에서 searchable을 호출하세요.

$orders->searchable();

임포트 전 레코드 수정

검색 가능한 상태로 만들기 전에 모델 컬렉션을 전처리해야 하는 경우가 있습니다. 예를 들어, 관계 데이터를 효율적으로 인덱싱하기 위해 즉시 로딩(eager load)을 적용할 수 있습니다. 이를 위해 해당 모델에 makeSearchableUsing 메서드를 정의하세요.

use Illuminate\Database\Eloquent\Collection; /** * 모델을 검색 가능하게 만들 때 컬렉션을 수정합니다. */ public function makeSearchableUsing(Collection $models): Collection { return $models->load('author'); }

레코드 삭제

인덱스에서 레코드를 삭제하려면 데이터베이스에서 해당 모델을 delete하면 됩니다. 소프트 삭제 모델에서도 동일하게 동작합니다.

use App\Models\Order; $order = Order::find(1); $order->delete();

모델 인스턴스 없이 삭제하려면 Eloquent 쿼리 인스턴스에 unsearchable을 사용하세요.

Order::where('price', '>', 100)->unsearchable();

관계에 속한 모든 모델의 인덱스를 제거하려면 관계 인스턴스에 unsearchable을 호출하세요.

$user->orders()->unsearchable();

이미 Eloquent 모델 컬렉션이 있다면 컬렉션 인스턴스에서 unsearchable을 호출하세요.

$orders->unsearchable();

모델 인스턴스를 데이터베이스에서 삭제하지 않고 인덱스에서만 제거하려면 removeFromSearch 메서드를 사용하세요.

$orders->removeFromSearch();

인덱싱 일시 중지

모델 데이터를 검색 인덱스와 동기화하지 않고 여러 Eloquent 작업을 수행해야 할 때가 있습니다. 이런 경우 withoutSyncingToSearch 메서드를 사용하면 콜백 내에서 수행되는 모든 모델 작업이 인덱스에 반영되지 않습니다.

use App\Models\Order; Order::withoutSyncingToSearch(function () { // 여러 모델 작업 수행... Order::factory()->create(); });

조건부 검색 가능 모델 인스턴스

특정 조건을 만족할 때만 모델을 검색 인덱스에 추가하고 싶은 경우가 있습니다. 예를 들어 published 상태인 게시글만 인덱싱하고 싶을 때, 모델에 shouldBeSearchable 메서드를 정의하세요.

/** * 모델이 검색 가능해야 하는지 여부를 반환합니다. */ public function shouldBeSearchable(): bool { return $this->isPublished(); }

shouldBeSearchable 메서드는 save, create, 쿼리, 관계를 통해 모델을 조작할 때만 적용됩니다. searchable 메서드를 직접 호출하면 shouldBeSearchable의 반환값에 관계없이 인덱스에 추가됩니다.

NOTE

shouldBeSearchable은 Scout의 "database" 엔진을 사용할 때는 적용되지 않습니다. 데이터베이스 엔진에서는 항상 데이터베이스에서 직접 검색하기 때문입니다. 유사한 동작이 필요하다면 where 절을 사용하세요.

검색

search 메서드로 모델 검색을 시작할 수 있습니다. search는 검색어 문자열을 받으며, get 메서드를 체이닝해 검색 쿼리와 일치하는 Eloquent 모델 컬렉션을 가져올 수 있습니다.

use App\Models\Order; $orders = Order::search('서울')->get();

Scout는 Eloquent 모델 컬렉션을 반환하므로, 라우트나 컨트롤러에서 직접 결과를 반환해도 자동으로 JSON으로 변환됩니다.

use App\Models\Order; use Illuminate\Http\Request; Route::get('/search', function (Request $request) { return Order::search($request->search)->get(); });

Eloquent 모델로 변환하기 전에 검색 결과 원본을 가져오려면 raw 메서드를 사용하세요.

$orders = Order::search('서울')->raw();

커스텀 인덱스

검색 쿼리는 기본적으로 모델의 searchableAs 메서드에 지정된 인덱스에서 수행됩니다. 다른 인덱스를 사용하려면 within 메서드를 사용하세요.

$orders = Order::search('서울') ->within('discounted_orders') ->get();

Where 절

Scout의 where 메서드를 사용하면 검색 쿼리에 간단한 "where" 조건을 추가할 수 있습니다. 현재는 기본적인 숫자 동등 비교만 지원하며, 주로 소유자 ID 등으로 검색 결과를 범위 지정할 때 유용합니다.

use App\Models\Order; $orders = Order::search('서울')->where('account_id', 1)->get();

whereIn 메서드로 컬럼 값이 특정 배열에 포함되는지 확인할 수 있습니다.

$orders = Order::search('서울')->whereIn( 'status', ['open', 'paid'] )->get();

whereNotIn 메서드로 컬럼 값이 특정 배열에 포함되지 않는지 확인할 수 있습니다.

$orders = Order::search('서울')->whereNotIn( 'status', ['closed'] )->get();

검색 인덱스는 관계형 데이터베이스가 아니기 때문에, 보다 복잡한 "where" 절은 지원되지 않습니다.

NOTE

Meilisearch를 사용한다면 Scout의 where 메서드를 사용하기 전에 필터링 가능 속성을 반드시 설정해야 합니다.

페이지네이션

Scout는 모델 컬렉션 반환 외에도 paginate 메서드를 통해 검색 결과를 페이지네이션할 수 있습니다. 이 메서드는 일반 Eloquent 쿼리 페이지네이션처럼 Illuminate\Pagination\LengthAwarePaginator 인스턴스를 반환합니다.

use App\Models\Order; $orders = Order::search('서울')->paginate();

페이지당 항목 수는 첫 번째 인수로 지정할 수 있습니다.

$orders = Order::search('서울')->paginate(15);

결과를 가져왔다면 Blade를 사용해 페이지네이션 링크를 일반 Eloquent 페이지네이션처럼 렌더링할 수 있습니다.

<div class="container"> @foreach ($orders as $order) {{ $order->price }} @endforeach </div> {{ $orders->links() }}

페이지네이터의 JSON 표현을 가져오려면 라우트나 컨트롤러에서 페이지네이터 인스턴스를 직접 반환하면 됩니다.

use App\Models\Order; use Illuminate\Http\Request; Route::get('/orders', function (Request $request) { return Order::search($request->input('query'))->paginate(15); });

NOTE

검색 엔진은 Eloquent 모델의 글로벌 스코프를 알지 못하므로, Scout 페이지네이션을 사용하는 애플리케이션에서는 글로벌 스코프 사용을 자제해야 합니다. 또는 Scout 검색 시 글로벌 스코프의 제약을 직접 재구현해야 합니다.

소프트 삭제

인덱싱된 모델이 소프트 삭제를 사용하고, 소프트 삭제된 모델도 검색 결과에 포함시키려면 config/scout.phpsoft_delete 옵션을 true로 설정하세요.

'soft_delete' => true,

이 옵션이 true이면 Scout는 소프트 삭제된 모델을 검색 인덱스에서 제거하지 않고, 인덱싱된 레코드에 숨겨진 __soft_deleted 속성을 설정합니다. 검색 시에는 withTrashed 또는 onlyTrashed 메서드를 사용해 소프트 삭제 레코드를 포함시킬 수 있습니다.

use App\Models\Order; // 삭제된 레코드를 포함하여 검색 $orders = Order::search('서울')->withTrashed()->get(); // 삭제된 레코드만 검색 $orders = Order::search('서울')->onlyTrashed()->get();

NOTE

소프트 삭제된 모델이 forceDelete로 완전히 삭제되면 Scout가 자동으로 검색 인덱스에서 제거합니다.

엔진 검색 커스터마이징

엔진 자체의 고급 검색 옵션을 활용해야 하는 경우 options 메서드를 사용하세요. 예를 들어 Algolia에서 검색 쿼리에 추가 옵션을 전달할 수 있습니다.

use App\Models\Order; $orders = Order::search('서울') ->options(['typoTolerance' => false]) ->get();

검색 쿼리 커스터마이징

Scout가 내부적으로 실행하는 Eloquent 쿼리를 수정해야 하는 경우 query 메서드를 사용하세요. 이 메서드는 클로저를 받으며 클로저 안에서 Eloquent 쿼리 빌더를 직접 수정할 수 있습니다.

use App\Models\Order; $orders = Order::search('서울') ->query(fn ($query) => $query->with('lineItems')) ->get();

NOTE

이 클로저는 검색 엔진에서 관련 모델 ID를 가져온 후 호출됩니다. 따라서 query 메서드는 검색 결과를 필터링하는 용도가 아닌, 관계 즉시 로딩 등 데이터를 보강하는 용도로 사용하는 것이 적합합니다. 결과 필터링이 필요하다면 Scout의 where 절을 사용하세요.

커스텀 엔진

엔진 구현하기

Scout에서 기본 제공하는 검색 엔진이 요구사항에 맞지 않는다면 커스텀 엔진을 직접 구현하고 Scout에 등록할 수 있습니다. 엔진은 Laravel\Scout\Engines\Engine 추상 클래스를 상속해야 하며, 다음 8개의 추상 메서드를 반드시 구현해야 합니다.

use Laravel\Scout\Builder; abstract public function update($models); abstract public function delete($models); abstract public function search(Builder $builder); abstract public function paginate(Builder $builder, $perPage, $page); abstract public function mapIds($results); abstract public function map(Builder $builder, $results, $model); abstract public function getTotalCount($results); abstract public function flush($model);

각 메서드의 구현 방법은 Laravel\Scout\Engines\AlgoliaEngine 클래스를 참고하면 도움이 됩니다.

엔진 등록하기

커스텀 엔진을 구현했다면, Scout의 EngineManagerextend 메서드로 등록합니다. EngineManager는 Laravel 서비스 컨테이너에서 해결할 수 있습니다. App\Providers\AppServiceProviderboot 메서드나 별도의 서비스 프로바이더에서 등록하세요.

use App\ScoutExtensions\MySqlSearchEngine; use Laravel\Scout\EngineManager; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { resolve(EngineManager::class)->extend('mysql', function () { return new MySqlSearchEngine; }); }

엔진을 등록한 뒤, config/scout.phpdriver 옵션을 등록한 엔진명으로 설정하세요.

'driver' => 'mysql',

Scout

설치

Composer를 통해 Scout를 설치합니다:

composer require laravel/scout

설치 후, vendor:publish Artisan 명령어로 Scout 설정 파일을 퍼블리시합니다. 이 명령어는 scout.php 설정 파일을 애플리케이션의 config 디렉토리에 생성합니다:

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

마지막으로, 검색 가능하게 만들 모델에 Laravel\Scout\Searchable 트레이트를 추가합니다. 이 트레이트는 모델 옵저버를 자동으로 등록하여, 모델 데이터가 변경될 때마다 검색 인덱스와 동기화가 유지되도록 합니다:

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; class Post extends Model { use Searchable; }

큐 설정

database 또는 collection 엔진이 아닌 외부 검색 엔진(Algolia, Meilisearch 등)을 사용할 경우, Scout를 사용하기 전에 큐 드라이버를 설정하는 것을 강력히 권장합니다. 큐 워커를 실행하면 모델 정보를 검색 인덱스와 동기화하는 작업이 백그라운드에서 처리되므로, 웹 인터페이스의 응답 속도가 크게 개선됩니다.

큐 드라이버를 설정했다면, config/scout.phpqueue 옵션 값을 true로 변경합니다:

'queue' => true,

NOTE

queue 옵션을 false로 설정하더라도, Algolia나 Meilisearch 같은 일부 드라이버는 내부적으로 항상 비동기로 인덱싱합니다. 즉, Laravel 애플리케이션에서 인덱싱 작업이 완료된 것처럼 보여도, 검색 엔진 자체에는 변경 사항이 즉시 반영되지 않을 수 있습니다.

Scout Job이 사용할 커넥션과 큐를 직접 지정하고 싶다면, queue 옵션을 배열로 정의할 수 있습니다:

'queue' => [ 'connection' => 'redis', 'queue' => 'scout' ],

이렇게 커넥션과 큐를 커스터마이징했다면, 해당 커넥션과 큐를 처리하는 큐 워커를 반드시 실행해야 합니다:

php artisan queue:work redis --queue=scout

중복 Job 방지 (Unique Jobs)

쓰기 작업이 많은 애플리케이션에서는 동일한 모델 레코드에 대해 인덱싱 Job이 중복으로 큐에 쌓이는 상황이 발생할 수 있습니다. 예를 들어, 짧은 시간 안에 같은 레코드가 여러 번 수정되면 동일한 인덱싱 Job이 불필요하게 여러 개 생성됩니다.

이를 방지하려면 MakeSearchableUniquelyRemoveFromSearchUniquely Job 클래스를 등록하세요. 일반적으로 서비스 프로바이더의 boot 메서드 안에 작성합니다:

use Laravel\Scout\Jobs\MakeSearchableUniquely; use Laravel\Scout\Jobs\RemoveFromSearchUniquely; use Laravel\Scout\Scout; Scout::makeSearchableUsing(MakeSearchableUniquely::class); Scout::removeFromSearchUsing(RemoveFromSearchUniquely::class);

이 Job들은 Laravel의 유니크 Job 잠금 기능을 활용하여, 동일한 레코드에 대한 인덱싱 Job이 이미 큐에 대기 중일 때 중복 Job이 추가되지 않도록 합니다.

드라이버 사전 준비

Algolia

Algolia 드라이버를 사용하려면, config/scout.php 설정 파일에 Algolia idsecret 자격 증명을 입력해야 합니다. 자격 증명 설정이 완료되면, Composer를 통해 Algolia PHP SDK를 설치합니다:

composer require algolia/algoliasearch-client-php

Meilisearch

Meilisearch는 빠르고 오픈소스인 검색 엔진입니다. 로컬 머신에 Meilisearch를 직접 설치하는 방법이 낯설다면, Laravel의 공식 Docker 개발 환경인 Laravel Sail을 활용하는 것이 간편합니다.

Meilisearch 드라이버를 사용하려면 Composer로 Meilisearch PHP SDK를 설치합니다:

composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle

그런 다음 애플리케이션의 .env 파일에 SCOUT_DRIVER 환경 변수와 Meilisearch의 host, key 자격 증명을 설정합니다:

SCOUT_DRIVER=meilisearch MEILISEARCH_HOST=http://127.0.0.1:7700 MEILISEARCH_KEY=masterKey

Meilisearch에 대한 자세한 내용은 Meilisearch 공식 문서를 참고하세요.

또한, 설치된 Meilisearch 바이너리 버전과 호환되는 meilisearch/meilisearch-php 버전을 사용해야 합니다. 호환성 정보는 Meilisearch PHP SDK의 바이너리 호환성 문서에서 확인할 수 있습니다.

WARNING

Meilisearch를 사용하는 애플리케이션에서 Scout를 업그레이드할 때는, Meilisearch 서비스 자체의 추가 브레이킹 체인지를 반드시 함께 검토하세요.

Typesense

Typesense는 매우 빠른 오픈소스 검색 엔진으로, 키워드 검색, 시맨틱 검색, 지역 검색, 벡터 검색을 모두 지원합니다.

Typesense는 직접 셀프 호스팅하거나 Typesense Cloud를 통해 사용할 수 있습니다.

Scout에서 Typesense를 사용하려면 먼저 Composer로 Typesense PHP SDK를 설치합니다:

composer require typesense/typesense-php

그런 다음 .env 파일에 SCOUT_DRIVER 환경 변수와 Typesense 호스트 및 API 키 자격 증명을 설정합니다:

SCOUT_DRIVER=typesense TYPESENSE_API_KEY=masterKey TYPESENSE_HOST=localhost

Laravel Sail을 사용하는 경우, Docker 컨테이너 이름에 맞게 TYPESENSE_HOST 환경 변수를 조정해야 할 수 있습니다. 필요에 따라 포트, 경로, 프로토콜도 추가로 지정할 수 있습니다:

TYPESENSE_PORT=8108 TYPESENSE_PATH= TYPESENSE_PROTOCOL=http

Typesense 컬렉션에 대한 추가 설정 및 스키마 정의는 애플리케이션의 config/scout.php 설정 파일에서 확인할 수 있습니다. Typesense에 대한 자세한 내용은 Typesense 공식 문서를 참고하세요.

설정

검색 가능한 데이터 설정

기본적으로 모델의 toArray 결과 전체가 검색 인덱스에 저장됩니다. 검색 인덱스에 동기화할 데이터를 직접 제어하고 싶다면, 모델에서 toSearchableArray 메서드를 오버라이드하세요:

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; class Post extends Model { use Searchable; /** * 인덱싱할 데이터 배열을 반환합니다. * * @return array<string, mixed> */ public function toSearchableArray(): array { $array = $this->toArray(); // 필요에 따라 데이터를 가공합니다... return $array; } }

모델별 검색 엔진 설정

Scout는 기본적으로 scout 설정 파일에 지정된 기본 검색 엔진을 사용합니다. 그러나 특정 모델에서 searchableUsing 메서드를 오버라이드하면 해당 모델에만 다른 검색 엔진을 적용할 수 있습니다:

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Engines\Engine; use Laravel\Scout\Scout; use Laravel\Scout\Searchable; class User extends Model { use Searchable; /** * 이 모델의 인덱싱에 사용할 엔진을 반환합니다. */ public function searchableUsing(): Engine { return Scout::engine('meilisearch'); } }

Database / Collection 엔진

Database 엔진

WARNING

Database 엔진은 현재 MySQL과 PostgreSQL만 지원합니다. 두 데이터베이스 모두 빠른 전문 검색(Full-Text) 컬럼 인덱싱을 제공합니다.

database 엔진은 MySQL / PostgreSQL의 전문 검색 인덱스와 LIKE 절을 활용하여 기존 데이터베이스를 직접 검색합니다. 외부 서비스나 별도의 인프라 없이 검색 기능을 추가할 수 있어, 많은 프로젝트에서 가장 간단하고 실용적인 선택입니다.

Database 엔진을 사용하려면 SCOUT_DRIVER 환경 변수를 database로 설정하세요:

SCOUT_DRIVER=database

설정 후에는 검색 가능한 데이터를 정의하고, 모델에 대한 검색 쿼리를 실행할 수 있습니다. 서드파티 엔진과 달리, Database 엔진은 별도의 인덱싱 작업이 필요하지 않습니다. 데이터베이스 테이블을 직접 검색합니다.

데이터베이스 검색 전략 커스터마이징

기본적으로 Database 엔진은 검색 가능하도록 설정된 모든 모델 속성에 대해 LIKE 쿼리를 실행합니다. 그러나 특정 컬럼에 더 효율적인 검색 전략을 지정할 수 있습니다.

  • SearchUsingFullText: 해당 컬럼에 데이터베이스의 전문 검색 인덱스를 사용합니다.
  • SearchUsingPrefix: 문자열 전체를 검색(%example%)하는 대신, 문자열의 시작 부분만 일치시킵니다(example%).

이 동작을 정의하려면 모델의 toSearchableArray 메서드에 PHP 어트리뷰트를 지정하세요. 어트리뷰트가 지정되지 않은 컬럼은 기본 LIKE 전략을 그대로 사용합니다:

use Laravel\Scout\Attributes\SearchUsingFullText; use Laravel\Scout\Attributes\SearchUsingPrefix; /** * 모델의 검색 가능한 데이터 배열을 반환합니다. * * @return array<string, mixed> */ #[SearchUsingPrefix(['id', 'email'])] #[SearchUsingFullText(['bio'])] public function toSearchableArray(): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'bio' => $this->bio, ]; }

WARNING

특정 컬럼에 전문 검색 쿼리 제약을 지정하기 전에, 해당 컬럼에 전문 검색 인덱스가 설정되어 있는지 반드시 확인하세요.

Collection 엔진

collection 엔진은 빠른 프로토타입 제작, 아주 소규모 데이터셋(수백 건 이하), 또는 테스트 환경을 위한 엔진입니다. 데이터베이스에서 가능한 모든 레코드를 가져온 뒤, Laravel의 Str::is 헬퍼를 사용하여 PHP 레이어에서 필터링합니다. 인덱싱이나 데이터베이스별 특수 기능이 전혀 필요하지 않습니다.

실제 서비스 규모의 데이터에는 Database 엔진 사용을 권장합니다.

Collection 엔진을 사용하려면 SCOUT_DRIVER 환경 변수를 collection으로 설정하거나, scout 설정 파일에서 직접 지정하세요:

SCOUT_DRIVER=collection

Collection 드라이버를 설정한 후에는 바로 검색 쿼리를 실행할 수 있습니다. Algolia, Meilisearch, Typesense 등의 인덱스를 초기화하는 작업은 필요하지 않습니다.

Database 엔진과의 차이점

항목Database 엔진Collection 엔진
검색 방식전문 검색 인덱스 + LIKE 쿼리전체 레코드 로드 후 PHP 필터링
성능비교적 효율적데이터가 많을수록 급격히 저하
지원 DBMySQL, PostgreSQLLaravel이 지원하는 모든 DB (SQLite, SQL Server 포함)
적합한 용도실제 서비스, 중간 규모 이상프로토타입, 소규모 데이터, 테스트

Collection 엔진은 지원 DB의 범위가 넓어 이식성이 가장 높지만, 대용량 데이터셋에는 적합하지 않습니다.

Scout - 서드파티 엔진 설정

서드파티 엔진 설정

이 섹션의 설정 옵션들은 Algolia, Meilisearch, Typesense 같은 서드파티 검색 엔진을 사용할 때만 해당됩니다. 데이터베이스 엔진을 사용하는 경우 이 섹션은 건너뛰어도 됩니다.

모델 인덱스 설정

서드파티 엔진을 사용할 때, 각 Eloquent 모델은 해당 모델의 검색 가능한 레코드를 담은 "인덱스"와 동기화됩니다. 기본적으로 모델 이름의 복수형(테이블명과 동일)이 인덱스 이름으로 사용됩니다. 인덱스 이름을 변경하고 싶다면 모델에서 searchableAs 메서드를 오버라이드하면 됩니다:

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; class Post extends Model { use Searchable; /** * 이 모델과 연결된 인덱스 이름을 반환합니다. */ public function searchableAs(): string { return 'posts_index'; } }

NOTE

searchableAs 메서드는 데이터베이스 엔진에서는 아무런 효과가 없습니다. 데이터베이스 엔진은 항상 모델의 데이터베이스 테이블을 직접 검색합니다.

모델 ID 설정

Scout는 기본적으로 모델의 기본 키(primary key)를 검색 인덱스에서 해당 레코드를 식별하는 고유 ID로 사용합니다. 서드파티 엔진을 사용할 때 이 동작을 변경하고 싶다면, 모델에서 getScoutKeygetScoutKeyName 메서드를 오버라이드하세요:

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; class User extends Model { use Searchable; /** * 인덱스에서 모델을 식별할 값을 반환합니다. */ public function getScoutKey(): mixed { return $this->email; } /** * 인덱스에서 모델을 식별할 키 이름을 반환합니다. */ public function getScoutKeyName(): mixed { return 'email'; } }

NOTE

getScoutKeygetScoutKeyName 메서드는 데이터베이스 엔진에서는 효과가 없습니다. 데이터베이스 엔진은 항상 모델의 기본 키를 사용합니다.

Algolia

인덱스 설정

Algolia 인덱스에 추가 설정이 필요한 경우, Algolia 대시보드 UI를 통해 관리할 수도 있지만, 애플리케이션의 config/scout.php 파일에서 직접 관리하는 것이 더 효율적인 경우가 많습니다.

설정을 코드로 관리하면 자동화된 배포 파이프라인(CI/CD)에 포함시킬 수 있고, 개발·스테이징·프로덕션 등 여러 환경 간의 설정 일관성을 유지할 수 있습니다. 필터링 가능한 속성, 랭킹, 패싯(faceting) 등 Algolia가 지원하는 모든 설정을 구성할 수 있습니다.

config/scout.php 파일의 algolia 항목 안에 각 인덱스에 대한 설정을 추가하세요:

use App\Models\User; use App\Models\Flight; 'algolia' => [ 'id' => env('ALGOLIA_APP_ID', ''), 'secret' => env('ALGOLIA_SECRET', ''), 'index-settings' => [ User::class => [ 'searchableAttributes' => ['id', 'name', 'email'], 'attributesForFaceting'=> ['filterOnly(email)'], // 기타 설정 필드... ], Flight::class => [ 'searchableAttributes'=> ['id', 'destination'], ], ], ],

index-settings 배열에 포함된 인덱스의 모델이 소프트 삭제(soft delete)를 지원하는 경우, Scout는 해당 인덱스에서 소프트 삭제된 레코드에 대한 패싯 필터링을 자동으로 포함합니다. 소프트 삭제 가능한 모델에 별도로 정의할 패싯 속성이 없다면, index-settings 배열에 빈 항목만 추가해도 됩니다:

'index-settings' => [ Flight::class => [] ],

인덱스 설정을 구성한 후에는 scout:sync-index-settings Artisan 명령어를 실행하여 Algolia에 설정을 반영해야 합니다. 배포 프로세스에 이 명령어를 포함해두면 편리합니다:

php artisan scout:sync-index-settings

사용자 식별

Scout는 Algolia 사용 시 현재 인증된 사용자를 검색 요청과 자동으로 연결하는 기능을 제공합니다. 이를 활성화하면 Algolia 대시보드의 검색 분석에서 사용자별 검색 패턴을 파악하는 데 유용합니다. .env 파일에 SCOUT_IDENTIFY 환경 변수를 true로 설정하면 됩니다:

SCOUT_IDENTIFY=true

이 기능을 활성화하면 요청의 IP 주소와 인증된 사용자의 기본 식별자(primary identifier)가 Algolia로 전달되어, 해당 사용자의 모든 검색 요청에 연결됩니다.

Meilisearch

인덱스 설정

Meilisearch는 필터링 가능한 속성(filterable attributes), 정렬 가능한 속성(sortable attributes) 등의 인덱스 검색 설정을 미리 정의해야 합니다. 지원하는 설정 항목의 전체 목록은 Meilisearch 공식 문서를 참고하세요.

  • filterableAttributes: Scout의 where 메서드로 필터링할 속성
  • sortableAttributes: Scout의 orderBy 메서드로 정렬할 속성

config/scout.phpmeilisearch 항목에서 index-settings를 다음과 같이 설정합니다:

use App\Models\User; use App\Models\Flight; 'meilisearch' => [ 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), 'key' => env('MEILISEARCH_KEY', null), 'index-settings' => [ User::class => [ 'filterableAttributes'=> ['id', 'name', 'email'], 'sortableAttributes' => ['created_at'], // 기타 설정 필드... ], Flight::class => [ 'filterableAttributes'=> ['id', 'destination'], 'sortableAttributes' => ['updated_at'], ], ], ],

index-settings 배열에 포함된 인덱스의 모델이 소프트 삭제를 지원하는 경우, Scout는 해당 인덱스에서 소프트 삭제된 레코드에 대한 필터링을 자동으로 지원합니다. 별도로 정의할 filterableAttributes나 sortableAttributes가 없는 소프트 삭제 가능 모델이라면, 빈 항목만 추가해도 됩니다:

'index-settings' => [ Flight::class => [] ],

인덱스 설정을 구성한 후에는 scout:sync-index-settings Artisan 명령어를 실행하여 Meilisearch에 설정을 반영해야 합니다. 배포 프로세스에 이 명령어를 포함해두면 편리합니다:

php artisan scout:sync-index-settings

검색 가능한 데이터 타입

Meilisearch는 비교 연산자(>, < 등)를 사용하는 필터 작업을 올바른 타입의 데이터에 대해서만 수행합니다. 검색 가능한 데이터를 커스터마이징할 때는 숫자 값이 적절한 타입으로 캐스팅되어 있는지 반드시 확인하세요:

public function toSearchableArray() { return [ 'id' => (int) $this->id, 'name' => $this->name, 'price' => (float) $this->price, ]; }

Typesense

검색 가능한 데이터 준비

Typesense를 사용할 때는, 검색 가능한 모델에서 toSearchableArray 메서드를 정의해야 합니다. 이때 모델의 기본 키는 문자열로, 생성일(created_at)은 UNIX 타임스탬프로 캐스팅해야 합니다:

/** * 모델의 인덱싱 가능한 데이터 배열을 반환합니다. * * @return array<string, mixed> */ public function toSearchableArray(): array { return array_merge($this->toArray(),[ 'id' => (string) $this->id, 'created_at' => $this->created_at->timestamp, ]); }

또한 config/scout.php 파일에 Typesense 컬렉션 스키마를 정의해야 합니다. 컬렉션 스키마는 Typesense로 검색 가능한 각 필드의 데이터 타입을 기술합니다. 사용 가능한 스키마 옵션에 대한 자세한 내용은 Typesense 공식 문서를 참고하세요.

이미 정의된 Typesense 컬렉션 스키마를 변경해야 하는 경우, scout:flushscout:import를 순서대로 실행하여 기존 인덱싱 데이터를 모두 삭제하고 스키마를 재생성할 수 있습니다. 또는 Typesense API를 직접 사용하면 인덱싱된 데이터를 삭제하지 않고도 컬렉션 스키마를 수정할 수 있습니다.

검색 가능한 모델이 소프트 삭제를 지원하는 경우, config/scout.php의 해당 모델 Typesense 스키마에 __soft_deleted 필드를 정의해야 합니다:

User::class => [ 'collection-schema' => [ 'fields' => [ // ... [ 'name' => '__soft_deleted', 'type' => 'int32', 'optional' => true, ], ], ], ],

동적 검색 파라미터

Typesense는 검색을 수행할 때 options 메서드를 통해 검색 파라미터를 동적으로 변경할 수 있습니다:

use App\Models\Todo; Todo::search('장보기')->options([ 'query_by' => 'title, description' ])->get();

Scout

서드파티 엔진 인덱싱

NOTE

이 섹션에서 설명하는 인덱싱 기능은 Algolia, Meilisearch, Typesense 같은 서드파티 엔진을 사용할 때에만 해당됩니다. 데이터베이스 엔진은 데이터베이스 테이블을 직접 검색하므로 별도의 인덱스 관리가 필요하지 않습니다.

일괄 임포트

기존 프로젝트에 Scout를 도입할 경우, 이미 데이터베이스에 저장된 레코드들을 검색 인덱스로 가져와야 합니다. Scout의 scout:import Artisan 명령어를 사용하면 기존 레코드 전체를 검색 인덱스에 일괄 등록할 수 있습니다.

php artisan scout:import "App\Models\Post"

큐 Job을 활용해 비동기로 임포트하려면 scout:queue-import 명령어를 사용하세요.

php artisan scout:queue-import "App\Models\Post" --chunk=500

모델의 모든 레코드를 검색 인덱스에서 제거하려면 flush 명령어를 사용합니다.

php artisan scout:flush "App\Models\Post"

임포트 쿼리 수정

일괄 임포트 시 모델을 조회하는 쿼리를 커스터마이즈하고 싶다면 모델에 makeAllSearchableUsing 메서드를 정의하세요. 임포트 전에 필요한 관계(Relationship)를 Eager 로딩하기에 적합한 위치입니다.

use Illuminate\Database\Eloquent\Builder; /** * 모든 모델을 검색 가능하게 만들 때 사용하는 쿼리를 수정합니다. */ protected function makeAllSearchableUsing(Builder $query): Builder { return $query->with('author'); }

WARNING

큐를 통해 모델을 일괄 임포트할 경우 makeAllSearchableUsing 메서드가 적용되지 않을 수 있습니다. Job이 모델 컬렉션을 처리할 때 관계(Relationship)는 복원되지 않습니다.

레코드 추가

모델에 Laravel\Scout\Searchable 트레이트를 추가하면, 이후 savecreate로 모델 인스턴스를 저장할 때 검색 인덱스에 자동으로 등록됩니다. Scout가 큐를 사용하도록 설정되어 있다면 이 작업은 큐 워커에 의해 백그라운드에서 처리됩니다.

use App\Models\Order; $order = new Order; // ... $order->save();

쿼리를 통한 레코드 추가

Eloquent 쿼리 결과 전체를 검색 인덱스에 추가하려면 쿼리에 searchable 메서드를 체이닝하세요. searchable 메서드는 쿼리 결과를 청크 단위로 처리하여 인덱스에 추가합니다. 큐가 설정되어 있다면 각 청크는 큐 워커가 백그라운드에서 처리합니다.

use App\Models\Order; Order::where('price', '>', 100)->searchable();

Eloquent 관계 인스턴스에도 searchable 메서드를 호출할 수 있습니다.

$user->orders()->searchable();

메모리에 이미 Eloquent 모델 컬렉션이 있다면 컬렉션 인스턴스에 직접 searchable을 호출할 수도 있습니다.

$orders->searchable();

NOTE

searchable 메서드는 "upsert" 방식으로 동작합니다. 즉, 인덱스에 이미 존재하는 레코드라면 업데이트하고, 없다면 새로 추가합니다.

레코드 업데이트

검색 가능한 모델을 업데이트하려면 모델 인스턴스의 속성을 변경한 뒤 데이터베이스에 save하기만 하면 됩니다. Scout가 변경 사항을 검색 인덱스에 자동으로 반영합니다.

use App\Models\Order; $order = Order::find(1); // 주문 정보를 수정합니다... $order->save();

Eloquent 쿼리 인스턴스에 searchable 메서드를 호출하여 여러 모델을 한꺼번에 업데이트할 수도 있습니다. 인덱스에 해당 모델이 존재하지 않는다면 새로 생성됩니다.

Order::where('price', '>', 100)->searchable();

관계에 속한 모든 모델의 인덱스를 업데이트하려면 관계 인스턴스에 searchable을 호출하세요.

$user->orders()->searchable();

메모리에 Eloquent 모델 컬렉션이 이미 있는 경우에도 컬렉션 인스턴스에 직접 호출할 수 있습니다.

$orders->searchable();

임포트 전 레코드 가공

모델을 검색 가능하게 만들기 전에 컬렉션을 사전에 준비해야 할 때가 있습니다. 예를 들어 관계 데이터를 인덱스에 포함시키기 위해 Eager 로딩을 미리 수행하고 싶을 수 있습니다. 이 경우 모델에 makeSearchableUsing 메서드를 정의하세요.

use Illuminate\Database\Eloquent\Collection; /** * 검색 가능하게 만들 모델 컬렉션을 수정합니다. */ public function makeSearchableUsing(Collection $models): Collection { return $models->load('author'); }

조건부 검색 인덱스 업데이트

기본적으로 Scout는 모델이 업데이트될 때 어떤 속성이 변경되었는지와 무관하게 인덱스를 다시 작성합니다. 이 동작을 커스터마이즈하려면 모델에 searchIndexShouldBeUpdated 메서드를 정의하세요.

/** * 검색 인덱스를 업데이트해야 할지 결정합니다. */ public function searchIndexShouldBeUpdated(): bool { return $this->wasRecentlyCreated || $this->wasChanged(['title', 'body']); }

레코드 제거

인덱스에서 레코드를 제거하려면 데이터베이스에서 해당 모델을 delete하면 됩니다. 소프트 삭제 모델을 사용하는 경우에도 동일하게 동작합니다.

use App\Models\Order; $order = Order::find(1); $order->delete();

모델을 먼저 조회하지 않고 바로 인덱스에서 제거하려면 Eloquent 쿼리 인스턴스에 unsearchable 메서드를 사용하세요.

Order::where('price', '>', 100)->unsearchable();

관계에 속한 모든 모델의 인덱스 레코드를 제거하려면 관계 인스턴스에 unsearchable을 호출하세요.

$user->orders()->unsearchable();

메모리에 Eloquent 모델 컬렉션이 있다면 컬렉션 인스턴스에 직접 호출할 수도 있습니다.

$orders->unsearchable();

해당 모델의 모든 레코드를 인덱스에서 한꺼번에 제거하려면 removeAllFromSearch 메서드를 사용하세요.

Order::removeAllFromSearch();

인덱싱 일시 중지

모델에 대해 여러 Eloquent 작업을 수행하면서 검색 인덱스와의 동기화를 일시적으로 중단해야 할 경우 withoutSyncingToSearch 메서드를 사용하세요. 이 메서드는 클로저를 인자로 받아 즉시 실행하며, 클로저 내부에서 발생한 모든 모델 작업은 검색 인덱스에 반영되지 않습니다.

use App\Models\Order; Order::withoutSyncingToSearch(function () { // 인덱스 동기화 없이 모델 작업을 수행합니다... });

조건부 검색 가능 모델 인스턴스

특정 조건을 만족하는 모델만 검색 인덱스에 등록하고 싶을 때가 있습니다. 예를 들어 App\Models\Post 모델이 "임시저장(draft)"과 "공개(published)" 두 가지 상태를 가질 때, "공개" 상태의 게시글만 검색되도록 제한할 수 있습니다. 이 경우 모델에 shouldBeSearchable 메서드를 정의하세요.

/** * 이 모델을 검색 인덱스에 포함할지 결정합니다. */ public function shouldBeSearchable(): bool { return $this->isPublished(); }

shouldBeSearchable 메서드는 save, create, 쿼리, 또는 관계를 통해 모델을 조작할 때만 적용됩니다. searchable 메서드를 사용하여 모델이나 컬렉션을 직접 인덱싱하면 shouldBeSearchable의 반환값은 무시됩니다.

WARNING

Scout의 "database" 엔진을 사용하는 경우 shouldBeSearchable 메서드는 적용되지 않습니다. 데이터베이스 엔진은 모든 검색 데이터가 항상 데이터베이스에 저장되기 때문입니다. 유사한 동작이 필요하다면 where 절을 활용하세요.

검색 (Scout)

검색

search 메서드를 사용하여 모델 검색을 시작할 수 있습니다. search 메서드는 검색어 문자열 하나를 인수로 받으며, 이어서 get 메서드를 체이닝하면 검색 결과와 일치하는 Eloquent 모델 컬렉션을 가져올 수 있습니다:

use App\Models\Order; $orders = Order::search('Star Trek')->get();

Scout 검색 결과는 Eloquent 모델 컬렉션으로 반환되므로, 라우트나 컨트롤러에서 결과를 바로 반환하면 자동으로 JSON으로 변환됩니다:

use App\Models\Order; use Illuminate\Http\Request; Route::get('/search', function (Request $request) { return Order::search($request->search)->get(); });

Eloquent 모델로 변환되기 전의 원시 검색 결과가 필요하다면 raw 메서드를 사용하세요:

$orders = Order::search('Star Trek')->raw();

커스텀 인덱스

서드파티 검색 엔진을 사용할 때, 검색 쿼리는 기본적으로 모델의 searchableAs 메서드가 반환하는 인덱스를 대상으로 실행됩니다. within 메서드를 사용하면 특정 검색에 한해 다른 인덱스를 지정할 수 있습니다:

$orders = Order::search('Star Trek') ->within('tv_shows_popularity_desc') ->get();

Where 절

Scout는 검색 쿼리에 where 조건을 추가하는 기능을 제공합니다. 예를 들어, 특정 사용자 ID로 검색 결과를 한정할 때 유용하게 사용할 수 있습니다:

use App\Models\Order; $orders = Order::search('Star Trek')->where('user_id', 1)->get();

=, !=, <, >, >=, <= 비교 연산자를 사용해 더 세밀한 조건을 구성할 수도 있습니다:

Order::search('Star Trek') ->where('status', '=', 'completed') ->where('is_refunded', '!=', true) ->where('total_price', '>', 100) ->where('shipping_cost', '<', 20) ->where('discount_percent', '>=', 10) ->where('item_count', '<=', 5) ->get();

whereIn 메서드를 사용하면 특정 컬럼의 값이 주어진 배열에 포함되어 있는지 확인할 수 있습니다:

$orders = Order::search('Star Trek')->whereIn( 'status', ['open', 'paid'] )->get();

whereNotIn 메서드는 반대로 특정 컬럼의 값이 배열에 포함되지 않은 결과만 반환합니다:

$orders = Order::search('Star Trek')->whereNotIn( 'status', ['closed'] )->get();

WARNING

Meilisearch를 사용하는 경우, where 절을 사용하기 전에 반드시 애플리케이션의 필터링 가능한 속성(filterable attributes)을 먼저 설정해야 합니다.

Eloquent 결과 쿼리 커스터마이징

Scout가 검색 엔진으로부터 일치하는 모델의 기본 키 목록을 받아오면, Eloquent를 통해 해당 모델들을 데이터베이스에서 조회합니다. 이 조회 쿼리를 커스터마이징하려면 query 메서드를 사용하세요. query 메서드는 Eloquent 쿼리 빌더 인스턴스를 인수로 받는 클로저를 인수로 받습니다:

use App\Models\Order; use Illuminate\Database\Eloquent\Builder; $orders = Order::search('Star Trek') ->query(fn (Builder $query) => $query->with('invoices')) ->get();

NOTE

서드파티 검색 엔진을 사용할 때, 이 콜백은 검색 엔진에서 모델이 이미 조회된 이후에 실행됩니다. 따라서 결과 필터링 용도로는 사용하지 말고, Scout의 where 절을 활용하세요. 단, 데이터베이스 엔진을 사용하는 경우에는 query 메서드의 조건이 데이터베이스 쿼리에 직접 적용되므로 필터링 목적으로도 사용할 수 있습니다.

페이지네이션

모델 컬렉션을 한 번에 가져오는 대신, paginate 메서드를 사용하여 검색 결과를 페이지네이션할 수 있습니다. 이 메서드는 일반 Eloquent 쿼리의 페이지네이션과 마찬가지로 Illuminate\Pagination\LengthAwarePaginator 인스턴스를 반환합니다:

use App\Models\Order; $orders = Order::search('Star Trek')->paginate();

paginate 메서드의 첫 번째 인수로 페이지당 조회할 모델 수를 지정할 수 있습니다:

$orders = Order::search('Star Trek')->paginate(15);

데이터베이스 엔진을 사용하는 경우, simplePaginate 메서드를 사용할 수도 있습니다. paginate는 전체 결과 수를 조회하여 페이지 번호를 표시하는 반면, simplePaginate는 다음 페이지 존재 여부만 확인합니다. 전체 레코드 수가 매우 많아 총 페이지 수 계산이 부담스러울 때 더 효율적인 선택입니다:

$orders = Order::search('Star Trek')->simplePaginate(15);

결과를 가져온 후에는 일반 Eloquent 페이지네이션과 동일하게 Blade로 결과와 페이지 링크를 렌더링할 수 있습니다:

<div class="container"> @foreach ($orders as $order) {{ $order->price }} @endforeach </div> {{ $orders->links() }}

페이지네이션 결과를 JSON으로 반환하려면, 라우트나 컨트롤러에서 페이지네이터 인스턴스를 직접 반환하면 됩니다:

use App\Models\Order; use Illuminate\Http\Request; Route::get('/orders', function (Request $request) { return Order::search($request->input('query'))->paginate(15); });

WARNING

검색 엔진은 Eloquent 모델의 글로벌 스코프를 인식하지 못합니다. 따라서 Scout 페이지네이션을 사용하는 애플리케이션에서는 글로벌 스코프를 사용하지 않거나, Scout 검색 시 글로벌 스코프와 동일한 조건을 직접 구성해야 합니다.

소프트 삭제

인덱싱된 모델이 소프트 삭제를 지원하고, 소프트 삭제된 모델도 검색 대상에 포함시켜야 한다면 config/scout.php 설정 파일의 soft_delete 옵션을 true로 설정하세요:

'soft_delete' => true,

이 옵션이 true이면, Scout는 소프트 삭제된 모델을 검색 인덱스에서 제거하지 않고, 인덱싱된 레코드에 숨김 속성인 __soft_deleted를 설정합니다. 이후 검색 시 withTrashed 또는 onlyTrashed 메서드를 사용하여 소프트 삭제된 레코드를 포함하거나 제한할 수 있습니다:

use App\Models\Order; // 소프트 삭제된 레코드를 결과에 포함 $orders = Order::search('Star Trek')->withTrashed()->get(); // 소프트 삭제된 레코드만 결과에 포함 $orders = Order::search('Star Trek')->onlyTrashed()->get();

NOTE

forceDelete로 모델을 영구 삭제하면, Scout가 해당 레코드를 검색 인덱스에서 자동으로 제거합니다.

엔진 검색 커스터마이징

검색 엔진의 동작을 세밀하게 제어해야 할 경우, search 메서드의 두 번째 인수로 클로저를 전달할 수 있습니다. 예를 들어, 아래 코드는 Algolia에 검색 쿼리가 전달되기 전에 지리적 위치(geo-location) 데이터를 검색 옵션에 추가하는 예시입니다:

use Algolia\AlgoliaSearch\SearchIndex; use App\Models\Order; Order::search( 'Star Trek', function (SearchIndex $algolia, string $query, array $options) { $options['body']['query']['bool']['filter']['geo_distance'] = [ 'distance' => '1000km', 'location' => ['lat' => 37, 'lon' => 127], // 예: 서울 근방 ]; return $algolia->search($query, $options); } )->get();

Scout

커스텀 엔진

엔진 작성하기

Scout에 내장된 검색 엔진이 요구사항에 맞지 않는다면, 직접 커스텀 엔진을 작성하여 Scout에 등록할 수 있습니다. 커스텀 엔진은 Laravel\Scout\Engines\Engine 추상 클래스를 상속해야 하며, 이 추상 클래스에서 정의된 다음 여덟 가지 메서드를 반드시 구현해야 합니다:

use Laravel\Scout\Builder; abstract public function update($models); abstract public function delete($models); abstract public function search(Builder $builder); abstract public function paginate(Builder $builder, $perPage, $page); abstract public function mapIds($results); abstract public function map(Builder $builder, $results, $model); abstract public function getTotalCount($results); abstract public function flush($model);

각 메서드의 구체적인 구현 방식을 파악하려면 Laravel\Scout\Engines\AlgoliaEngine 클래스의 소스 코드를 참고하는 것이 좋습니다. 이 클래스는 각 메서드를 어떻게 구현해야 하는지 잘 보여주는 좋은 출발점이 됩니다.

엔진 등록하기

커스텀 엔진 작성이 완료되면, Scout 엔진 매니저의 extend 메서드를 사용하여 Scout에 등록합니다. 엔진 매니저는 Laravel 서비스 컨테이너에서 resolve할 수 있습니다. extend 메서드 호출은 App\Providers\AppServiceProviderboot 메서드, 또는 애플리케이션에서 사용하는 다른 서비스 프로바이더의 boot 메서드 안에서 수행해야 합니다:

use App\ScoutExtensions\MySqlSearchEngine; use Laravel\Scout\EngineManager; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { resolve(EngineManager::class)->extend('mysql', function () { return new MySqlSearchEngine; }); }

엔진을 등록한 후에는 config/scout.php 설정 파일에서 기본 Scout driver로 지정할 수 있습니다:

'driver' => 'mysql',

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

번역일: 2026년 7월 7일