본문으로 건너뛰기

RubyKaigi가 경고하는 Rails 코드 안티 패턴: 단 3%만이 통과한 5가지 위험 요소

Only 3% Got It Right: 5 Dangerous Ruby on Rails Code Patterns from RubyKaigi | by Germán Giménez Silva | Feb, 2026 | Medium

작성자
jeff
발행일
2026년 02월 09일
https://medium.com/@german.gimenez.silva/only-3-got-it-right-5-dangerous-ruby-on-rails-code-patterns-from-rubykaigi-c8886ab4ecee

핵심 요약

  • 1 RubyKaigi 2025에서 실시된 Rails 코드 퀴즈 결과, 숙련된 개발자 중 단 3%만이 모든 함정을 파악했을 정도로 일상적인 코드 속에 치명적인 위험이 숨어 있다.
  • 2 ActiveRecord의 pluck, update_all과 같은 메서드들이 콜백이나 메모리에 미치는 영향을 정확히 이해하지 못하면 시스템 전체의 성능 저하나 데이터 부정합을 초래할 수 있다.
  • 3 트랜잭션 내부의 사이드 이펙트 처리나 연관 관계 삭제 옵션 선택 등 Rails의 내부 메커니즘을 고려한 설계가 견고한 애플리케이션 구축의 핵심임을 강조한다.

도입

RubyKaigi 2025 전시 부스에서 진행된 Rails 코드 퀴즈는 숙련된 개발자들에게도 큰 충격을 안겨주었습니다. 일상적인 프로덕션 코드처럼 보이는 문제들이었음에도 불구하고, 약 100명의 참가자 중 모든 문제를 맞힌 사람은 단 3명에 불과했습니다. 본 아티클은 Hubble, Inc.의 백엔드 테크 리드인 Yuta Nakashima의 발표 내용을 바탕으로, 겉보기에는 정상적이지만 실제로는 시스템에 심각한 결함을 초래할 수 있는 5가지 위험한 Rails 코드 패턴을 심층적으로 분석하고 올바른 대처 방안을 제시합니다.

1. pluck 사용 시의 메모리 관리 함정

pluck 메서드는 특정 컬럼의 값만 가져올 때 매우 유용하지만, 대량의 데이터를 처리할 때 메모리 문제를 일으킬 수 있습니다. pluck은 호출 즉시 쿼리를 실행하여 결과값을 Ruby 배열로 메모리에 로드하기 때문입니다. 수만 건 이상의 레코드를 다룰 때 이를 무분별하게 사용하면 메모리 사용량이 급증하여 애플리케이션 성능이 저하됩니다. 이를 방지하기 위해서는 select를 사용하여 ActiveRecord::Relation 객체를 유지하며 지연 로딩(Lazy Loading)을 활용하거나, find_each와 같은 배치 처리 메서드를 결합하여 한 번에 메모리에 로드되는 데이터 양을 제한해야 합니다.

2. update_alldelete_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 문으로 모든 자식 레코드를 삭제하여 매우 빠르지만 콜백을 무시합니다. 데이터의 무결성과 시스템 성능 중 무엇이 우선순위인지에 따라 명확한 기준을 가지고 선택해야 합니다.

결론

이번 분석을 통해 드러난 가장 큰 교훈은 '당연하게 생각하는 코드에 위험이 숨어 있다'는 점입니다. Rails는 개발 생산성을 높여주는 강력한 프레임워크이지만, 추상화된 메서드들의 내부 동작 방식을 정확히 이해하지 못한 채 사용하면 예상치 못한 장애를 마주하게 됩니다. 특히 대규모 트래픽이나 복잡한 비즈니스 로직을 다루는 환경일수록, 단순히 기능이 작동하는 것에 만족하지 않고 성능과 데이터 정합성 관점에서 코드를 면밀히 검토하는 습관이 필요합니다. 이는 시니어 개발자로서 갖추어야 할 필수적인 전문성입니다.

댓글0

댓글 작성

댓글 삭제 시 비밀번호가 필요합니다.

이미 계정이 있으신가요? 로그인 후 댓글을 작성하세요.

0/1000
정중하고 건설적인 댓글을 작성해 주세요.