Eloquent 성능 트릭: 수천 개의 레코드로 Laravel을 날게 만들기
2024-02-05 * 9분 읽기 min de lecture Web Development
Laravel을 처음 시작할 때, Eloquent는 마법처럼 느껴집니다. 몇 줄의 코드로 아름답고 표현력 있는 구문으로 데이터베이스를 쿼리할 수 있습니다. 하지만 프로젝트가 성장하고 수천 개(또는 수백만 개)의 행을 처리하기 시작하면, 그 마법이 갑자기 느린 쿼리, 메모리 스파이크, 지연되는 페이지로 변할 수 있습니다.
좋은 소식? Laravel의 Eloquent ORM은 정말 잘 확장될 수 있습니다 - 몇 가지 성능 트릭을 알고 있다면. 이 기사에서는 Eloquent의 우아함을 포기하지 않고 대규모 애플리케이션을 최적화하는 데 저와 제 팀을 도운 기법들을 공유하겠습니다.
1. select()를 사용하여 필요한 것만 검색하기
기본적으로 Eloquent는 데이터베이스에서 모든 열(SELECT *)을 검색합니다. 작은 테이블의 경우 괜찮습니다. 하지만 큰 테이블과 넓은 스키마에서는 메모리를 낭비하고 쿼리를 느리게 만듭니다.
// ❌ 피하기: 모든 열을 가져옴
$users = User::all();
// ✅ 더 좋음: 필요한 필드만 가져옴
$users = User::select('id', 'name', 'email')->get();
👉 이 작은 변경으로 큰 쿼리에서 메가바이트의 메모리를 절약할 수 있습니다.2. 큰 쿼리를 청크로 처리하기
수천 개의 레코드를 처리해야 할 때, 한 번에 모두 로드하지 마세요. 청킹을 사용하여 배치로 처리하세요.
User::chunk(500, function ($users) {
foreach ($users as $user) {
// 사용자 처리
}
});
100k 행을 메모리로 가져오는 대신, 이것은 한 번에 500개를 가져옵니다. Laravel이 백그라운드에서 페이지네이션을 처리합니다.
순서가 중요한 경우(예: 일관된 배치 처리), id로 정렬하는 것을 잊지 마세요.3. 스트리밍을 위해 lazy() 또는 cursor() 사용하기
정말 거대한 데이터셋을 작업하고 있다면, 청킹도 여전히 너무 많은 메모리를 소비할 수 있습니다. 그 경우 커서를 사용하세요:
foreach (User::cursor() as $user) {
// 레코드를 하나씩 스트리밍
}
이것은 큰 청크를 메모리에 로드하는 것을 피하고, 데이터베이스에서 직접 행을 스트리밍합니다. 행당으로는 느리지만 메모리 효율성이 극도로 높습니다.4. 관계를 미리 로드하기 (하지만 과부하하지 않기)
N+1 쿼리 문제는 Laravel 앱에서 가장 큰 성능 킬러 중 하나입니다.
// ❌ 나쁨: 각 포스트에 대해 새로운 쿼리 실행
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name;
}
// ✅ 좋음: 사용자 미리 로드
$posts = Post::with('user')->get();
이것은 수십 개의 쿼리를 단 두 개로 줄입니다.
⚠️ 하지만 모든 것을 미리 로드하지 마세요. 정말 필요한 관계만 로드하세요, 그렇지 않으면 사용하지 않는 데이터로 메모리를 압도할 것입니다.5. 데이터베이스 열에 인덱스 추가하기
때로는 병목이 Eloquent가 아닙니다 - 데이터베이스 자체입니다. 적절한 인덱스를 추가하면 쿼리 시간을 초에서 밀리초로 단축할 수 있습니다.
Schema::table('users', function (Blueprint $table) {
$table->index('email');
$table->index(['status', 'created_at']);
});
👉 WHERE, ORDER BY, 또는 JOIN 절에서 자주 사용되는 열은 항상 인덱스를 추가하세요.6. 모든 것을 가져오는 대신 페이지네이션 사용하기
목록과 대시보드의 경우, 한 번에 수천 개의 레코드를 로드하지 마세요. 대신 페이지네이션을 사용하세요.
$users = User::paginate(50);
이것은 깔끔한 페이지네이션 링크를 제공하고 메모리 사용량을 작게 유지합니다.
무한 스크롤이 필요하다면, simplePaginate()가 더욱 빠릅니다.7. 대량으로 update()와 delete() 사용하기
많은 레코드를 업데이트하거나 삭제해야 할 때, 루프로 처리하지 마세요. 하나의 쿼리로 하세요.
// ❌ 나쁨: 행별로 업데이트
foreach ($users as $user) {
$user->update(['active' => false]);
}
// ✅ 좋음: 단일 대량 업데이트
User::where('active', true)->update(['active' => false]);
이것은 수천 개의 쿼리를 피하고 앱을 날게 만듭니다.8. 의미가 있을 때 캐시 사용하기
같은 무거운 쿼리를 반복적으로 실행하고 있다면, 결과를 캐시하는 것을 고려하세요.
$users = Cache::remember('active_users', 600, function () {
return User::where('active', true)->get();
});
Laravel은 이것을 쉽게 만들고, 캐싱은 느린 DB 쿼리를 빠른 메모리 검색으로 바꿀 수 있습니다.9. 쿼리 프로파일링
병목이 어디에 있는지 추측하지 마세요. DB::enableQueryLog() 또는 Laravel Telescope를 사용하여 쿼리를 프로파일링하세요.
DB::enableQueryLog();
$users = User::where('status', 'active')->get();
dd(DB::getQueryLog());
또는 Eloquent가 생성하는 것을 보기 위해 toSql()을 사용하세요:
dd(User::where('status', 'active')->toSql());
10. 필요할 때... 원시 쿼리로 내려가기
Eloquent는 우아하지만, 때로는 원시 성능이 필요합니다. Laravel은 쿼리 빌더나 심지어 원시 SQL로 돌아가는 것을 쉽게 만듭니다.
// 쿼리 빌더
$users = DB::table('users')->where('status', 'active')->get();
// 원시 SQL
$users = DB::select('SELECT id, name FROM users WHERE status = ?', ['active']);
접근 방식을 혼합하는 것은 실패가 아닙니다 - 그것은 실용적인 엔지니어링입니다.Conclusion
Laravel의 Eloquent ORM은 작은 프로젝트만을 위한 것이 아닙니다. 올바른 트릭들 - 청킹과 커서부터, 이거 로딩과 대량 업데이트까지 - 으로 Laravel을 특별하게 만드는 가독성과 유지보수성을 포기하지 않고 수백만 개의 행을 효율적으로 처리할 수 있습니다.
하루의 끝에, Laravel에서의 성능 최적화는 Eloquent를 포기하는 것이 아닙니다. 그것의 강점을 이해하고, 그것의 함정을 피하고, 언제 원시 힘을 섞을지 아는 것입니다.
그리고 네, 이 모든 년이 지난 후에도, 저는 여전히 Laravel로 코딩하는 것을 사랑합니다. 그것은 제 첫 번째 사이드 프로젝트부터 오늘의 CTO 역할까지 저와 함께 성장했습니다. PHP 자체처럼, 그것은 죽기를 거부합니다 - 그리고 솔직히, 저는 그것으로 괜찮습니다. 🚀