1. pluck 사용 시의 메모리 관리 함정
pluck 메서드는 특정 컬럼의 값만 가져올 때 매우 유용하지만, 대량의 데이터를 처리할 때 메모리 문제를 일으킬 수 있습니다. pluck은 호출 즉시 쿼리를 실행하여 결과값을 Ruby 배열로 메모리에 로드하기 때문입니다. 수만 건 이상의 레코드를 다룰 때 이를 무분별하게 사용하면 메모리 사용량이 급증하여 애플리케이션 성능이 저하됩니다. 이를 방지하기 위해서는 select를 사용하여 ActiveRecord::Relation 객체를 유지하며 지연 로딩(Lazy Loading)을 활용하거나, find_each와 같은 배치 처리 메서드를 결합하여 한 번에 메모리에 로드되는 데이터 양을 제한해야 합니다.
2. update_all 및 delete_all과 콜백의 부재
성능 최적화를 위해 여러 레코드를 한 번에 수정하거나 삭제할 때 update_all이나 delete_all을 자주 사용합니다. 하지만 이 메서드들은 ActiveRecord 모델에 정의된 콜백(Callbacks)과 유효성 검사(Validations)를 완전히 무시하고 데이터베이스에 직접 SQL 명령을 전송합니다. 만약 after_commit에서 외부 API를 호출하거나 검색 엔진의 인덱스를 갱신하는 로직이 있다면, 이 메서드들을 사용할 때 해당 로직이 실행되지 않아 데이터 불일치 문제가 발생하게 됩니다. 성능과 로직의 무결성 사이에서 신중한 선택이 필요합니다.
3. 데이터베이스 트랜잭션 내 외부 사이드 이펙트
ActiveRecord::Base.transaction 블록 내부에서 외부 API 호출이나 이메일 발송과 같은 작업을 수행하는 것은 전형적인 안티 패턴입니다. 외부 서비스의 응답이 지연될 경우 데이터베이스 커넥션을 점유하는 시간이 길어져 커넥션 풀 고갈 현상을 초래할 수 있습니다. 또한, 트랜잭션이 롤백되더라도 이미 실행된 외부 요청은 취소되지 않으므로 심각한 비즈니스 오류를 발생시킵니다. 이러한 사이드 이펙트는 반드시 after_commit 콜백을 사용하여 트랜잭션이 완전히 성공한 후에 실행되도록 구조화해야 합니다.
4. count, size, length의 미묘한 동작 차이
이 세 메서드는 컬렉션의 크기를 반환한다는 점은 같지만 동작 방식은 판이합니다. count는 호출할 때마다 매번 SQL COUNT 쿼리를 실행하며, length는 모든 레코드를 메모리에 로드한 후 배열의 길이를 측정합니다. size는 레코드가 이미 로드된 상태라면 length처럼 동작하고, 그렇지 않다면 count 쿼리를 실행합니다. 불필요한 쿼리 실행을 줄이고 메모리 낭비를 막기 위해서는 현재 데이터의 로딩 상태를 파악하고 적절한 메서드를 선택하는 안목이 필요합니다.
5. 연관 관계 삭제 옵션: dependent: :destroy vs :delete_all
모델 간의 연관 관계를 정리할 때 사용하는 두 옵션은 성능과 로직 실행 측면에서 큰 차이를 보입니다. dependent: :destroy는 연관된 각 자식 레코드를 하나씩 인스턴스화하여 destroy 메서드를 호출하므로 콜백이 실행되지만, 데이터 양이 많을 경우 N+1 쿼리와 유사한 성능 저하를 일으킵니다. 반면 :delete_all은 단일 SQL 문으로 모든 자식 레코드를 삭제하여 매우 빠르지만 콜백을 무시합니다. 데이터의 무결성과 시스템 성능 중 무엇이 우선순위인지에 따라 명확한 기준을 가지고 선택해야 합니다.