발표는 실제 프로덕션 코드에서 간과하기 쉬운 5가지 주요 문제점을 로직 흐름에 따라 상세히 설명하고, 각 문제에 대한 해결 방안을 제시합니다. 문제 코드는 Huble의 문서 업로드 API를 모방한 형태로, 사용자 인증, 파라미터 유효성 검사, 문서 생성, 외부 API 호출, 비동기 알림 발행 등의 일반적인 처리 과정을 포함합니다.
1. SQL에서의 전체 데이터 조회 (N+1 문제의 서막)
-
문제점:
user.folders.any?와 같이 특정 조건 확인을 위해 테이블의 모든 레코드를 가져오는 방식은 N개의 ActiveRecord 객체를 메모리에 생성하여 가비지 컬렉션(GC) 부담을 증가시키고, 커넥션 풀을 장시간 점유하며, 데이터 양 증가 시 O(N)의 성능 저하를 야기합니다. 이는 특히 DDoS 공격에 취약할 수 있습니다. -
해결 방안: SQL 단에서 존재 여부를 확인하는
exists?쿼리를 사용합니다. 이를 통해 불필요한 객체 생성과 컬럼 데이터 조회를 방지하고, 쿼리 횟수를 1회로 줄여 성능을 최적화합니다.
2. Ruby 측에서의 계산 (N+1 문제의 심화)
-
문제점: 조직 내 총 업로드 사이즈를 계산하기 위해 모든 문서를 가져와 Ruby 코드 내에서 파일 사이즈를 합산하는 방식은 N+1 쿼리 문제를 발생시킵니다. 이는 다량의 쿼리 발행으로 커넥션 풀을 장시간 점유하고, 동일 쿼리라도 파라미터가 다르면 DB 캐시를 활용하지 못해 CPU 부하를 증가시킵니다.
-
해결 방안: SQL의 집계 함수(예:
SUM)를 사용하여 데이터베이스 단에서 계산을 수행합니다. 이를 통해 쿼리 횟수를 1회로 줄이고, SQL 엔진에 최적화된 집계 기능을 활용하여 성능을 향상시킵니다. 대안으로organization테이블에 총 파일 사이즈 집계 컬럼을 두거나 KVS 캐싱을 고려할 수 있습니다.
3. 트랜잭션 내에서의 외부 API 실행
-
문제점: 문서 생성 트랜잭션 내에서 OCR API와 같은 외부 API를 호출하는 경우, 참조 테이블(예:
organization)의 행이 외부 API 응답이 완료될 때까지 공유 락(Shared Lock) 상태로 유지됩니다. 이는 DB 락 시간을 불필요하게 늘리고, DB 로직 문제와 외부 API 장애를 구분하기 어렵게 만듭니다. -
해결 방안: 외부 API 호출을 트랜잭션 외부로 분리합니다. 이를 통해 참조 테이블의 락 시간을 줄이고 문제 원인 분석을 용이하게 합니다. OCR과 같이 즉각적인 정합성이 크게 요구되지 않는 경우 비동기 처리를 고려할 수 있으며, 복잡한 분산 트랜잭션의 경우 Saga 패턴과 같은 기법을 참고할 수 있습니다.
4. 트랜잭션 내에서의 비동기 처리 발화
-
문제점: 트랜잭션 내에서 비동기 알림 처리와 같은 비동기 작업을 시작하는 경우, 작업 실행 시점에 따라 커밋되지 않은 데이터에 접근하여 예외를 발생시키는 레이스 컨디션(Race Condition)이 발생할 수 있습니다. 또한 트랜잭션이 롤백되더라도 비동기 작업은 이미 큐에 남아 불필요하게 실행되어 데이터 불일치를 초래할 수 있습니다.
-
해결 방안: 비동기 처리 시작 시점을 트랜잭션 외부로 이동합니다. 이를 통해 미커밋 데이터 접근 문제를 해결하고 레이스 컨디션 및 데드락을 방지할 수 있습니다. Rails 7.2부터는
config.active_job.after_transaction_commit옵션을 통해 트랜잭션 커밋 후에만 Active Job이 발행되도록 설정할 수 있습니다.
5. StandardError를 통한 모든 예외 처리
-
문제점:
rescue StandardError => e와 같이 모든 예외를 포괄적으로 처리하고e.message를 반환하는 방식은 내부 오류 발생 시 데이터베이스 구조, 테이블명, 컬럼명, 파일 경로, 환경 변수 등 민감한 정보가 노출될 위험이 있습니다. 이는 SQL 인젝션 공격 등에 악용될 수 있으며, 모든 오류를 500 에러로 처리하여 유효성 검사 오류조차도 서버 오류로 보이게 하여 사용자 경험을 저해하고 문제 분석 및 디버깅을 어렵게 만듭니다. -
해결 방안: 오류 유형별로 분기하여 적절한 HTTP 상태 코드와 사용자 친화적인 메시지를 반환합니다. 이를 통해 불필요한 내부 정보 노출을 방지하고, 오류 조건별 테스트 작성을 용이하게 하여 시스템의 견고성을 높입니다.