dd()를 여기저기 넣는 것을 멈추고 대신 소스에서 데이터베이스를 디버그하세요
Source: Dev.to
dd()와 로그를 흩뿌리는 문제
모든 백엔드 개발자는 한 번쯤 겪어봤을 겁니다: 요청이 실패하고, 데이터베이스에 무언가가 삽입되지 않으며, 코드베이스가 디버깅 문장들로 가득 찬 바다처럼 변해버립니다:
dd($request->all());
dd($user);
dd($campaign);
Log::info($query);
컨트롤러, 서비스, 리포지토리, 큐 워커, 이벤트 리스너 등 곳곳에 로그가 추가됩니다. 몇 시간을 파고든 뒤에도 여전히 혼란스러운 이유는 실제 문제의 위치가 현재 찾고 있는 곳이 아니기 때문입니다.
실제 사례
몇 달 전, 한 팀이 간단한 폼 제출에서 이상한 문제에 직면했습니다. 이 폼은 다음을 수행해야 했습니다:
- 사용자 생성
- 캠페인 할당
- 리드 생성
- 환영 이메일을 큐에 푸시
- 활동 로그 기록
무작위로 일부 사용자는 캠페인 데이터 없이 생성되었습니다. 때때로 큐 작업이 실행되기도 하고, 실행되지 않기도 했으며, 가끔은 트랜잭션이 조용히 롤백되었습니다.
첫 번째 반응은 모든 곳에 로그를 뿌리는 것이었습니다:
// Controllers
Log::info('Controller hit');
// Services
Log::info('Campaign assigned');
// Queue workers
Log::info('Email queued');
// Transactions
DB::beginTransaction();
코드는 디버깅 문구의 숲이 되었지만, 명확한 해답은 나오지 않았습니다.
놓친 통찰: 데이터베이스는 단일 진실의 원천이다
아키텍처가 얼마나 아름답든—Laravel, 순수 PHP, Node.js, Python, Java를 사용하든—모든 쿼리는 결국 하나의 장소, MySQL에 도달합니다. 데이터베이스는 모든 것을 관찰하며, 이 사실을 깨달으면 디버깅 방식이 달라집니다.
일반 쿼리 로그 활성화
SET GLOBAL general_log = 'ON';
그런 다음 실시간 로그를 확인하세요:
tail -f /var/lib/mysql/hostname.log
정확한 명령 순서를 확인할 수 있습니다:
INSERT INTO users ...
UPDATE campaigns ...
ROLLBACK;
위 예시에서 ROLLBACK은 다른 프로세스가 몇 초 동안 테이블을 잠궈두었다는 것을 드러냈습니다—이는 컨트롤러 로그에서는 전혀 확인할 수 없었던 내용입니다.
왜 애플리케이션 내부에서 디버깅하면 실제 문제를 놓치기 쉬운가
단일 요청은 다음을 포함할 수 있습니다:
- 큐
- 이벤트
- 트랜잭션
- 예약 작업
- 캐싱
- 백그라운드 워커
- 타사 API
컨트롤러 내부에서 디버깅을 시작할 때쯤이면 실제 문제는 이미 다른 곳에서 발생하고 있을 수 있습니다 (데드락, 락 대기, 느린 쿼리, 인덱스 누락, 트랜잭션 충돌 등). MySQL은 이미 이 모든 상황을 알고 있습니다.
백엔드 개발자가 알아야 할 네 가지 필수 MySQL 기능
1. General Query Log
SET GLOBAL general_log = 'ON';
사용 사례: 여러 테이블을 동시에 다루거나 숨겨진 ORM 쿼리가 의심될 때, 실시간으로 모든 쿼리를 확인할 수 있습니다.
주의: 디버깅이 끝난 후에는 로그가 과도하게 쌓이고 성능에 영향을 주지 않도록 반드시 비활성화하세요.
SET GLOBAL general_log = 'OFF';
2. Slow Query Log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 1초 이상 걸리는 쿼리를 로그에 기록
사용 사례: 인덱스 누락, N+1 문제, 대규모 테이블 스캔, 비효율적인 조인 등을 찾아낼 수 있습니다.
예시: 슬로우 쿼리 로그를 통해 누락된 인덱스를 발견한 뒤, 대시보드 응답 시간이 12초에서 800 ms 이하로 크게 개선되었습니다.
3. InnoDB Engine Status
SHOW ENGINE INNODB STATUS;
사용 사례: 요청이 오래 걸리거나, 큐가 멈추고, 삽입이 지연되거나, 트랜잭션 충돌이 발생했을 때 최신 데드락·락‑대기 정보를 확인할 수 있습니다.
4. Triggers for Auditing
프레임워크에 의존하지 않는 데이터베이스 수준의 감사 로그를 만들 수 있습니다.
CREATE TRIGGER users_after_insert
AFTER INSERT ON users
FOR EACH ROW
INSERT INTO audit_logs(table_name, action_type)
VALUES ('users', 'INSERT');
장점: 누가, 언제, 어떤 테이블에 어떤 작업을 수행했는지를 자동으로 추적할 수 있어, 애플리케이션 로그에 의존하지 않아도 됩니다.
모범 사례
- 필요할 때만 로그를 활성화하세요.
general_log또는slow_query_log를 일시적으로 켜고 조사한 뒤 비활성화합니다. - 로그 크기와 성능을 모니터링하세요. 영구적인 로깅은 저장소를 빠르게 소모하고 성능을 저하시킬 수 있습니다.
- MySQL 인사이트와 애플리케이션 로그를 결합하세요. 데이터베이스 로그를 사용해 근본 원인을 파악한 뒤, 필요하면 목표 지향적인 애플리케이션 로그를 추가합니다.
- 신뢰할 수 있는 프레임워크에 구애받지 않는 변경 추적이 필요할 때 트리거를 활용해 감사 로그를 구현하세요.
데이터베이스를 진실의 기본 소스로 간주하고 이러한 내장 MySQL 도구를 사용하면 디버깅 시간을 크게 단축하고 코드 전반에 dd()와 Log::info()를 여기저기 삽입하는 끝없는 반복을 피할 수 있습니다.