시간에 따른 데이터 처리에 해로운 Rails Way™

Data over time

작성자
발행일
2025년 07월 09일

핵심 요약

  • 1 Rails의 기본 상태 변이 방식은 시간에 따른 데이터 추적에 부적합하며, 원장(ledger) 기반의 불변(immutable) 돌연변이 기록 방식이 금융 시스템에 필수적입니다.
  • 2 시간 간격(intervals) 및 타임존 처리는 시작/종료 시점의 포함 여부 명시, DB 범위 인덱스 활용, 간극 고려 등 세심한 설계가 요구됩니다.
  • 3 타임스탬프 기반 캐시 키는 동시성 문제로 일관성을 해칠 수 있으며, 웹훅 이벤트는 순서가 보장되지 않는 '집합'으로 간주하고 이에 맞춰 처리해야 합니다.

도입

영국 핀테크 스타트업 Cheddar Payments에서의 경험을 바탕으로, "시간에 따른 데이터(data over time)" 처리의 중요성과 어려움을 다룹니다. 특히 Rails의 일반적인 접근 방식이 핀테크와 같은 도메인에서 어떻게 문제를 일으킬 수 있는지 지적하며, 기존 방식의 한계를 설명하고 더 나은 대안을 모색하는 배경을 제시합니다. Rails의 `updated_at` 기반 모델이 갖는 근본적인 문제점을 제기하며 글의 주제를 명확히 합니다.

시간에 따른 데이터 처리의 문제점과 해결책

1. Rails Way™의 한계와 원장(Ledger) 기반 접근법

Rails는 데이터베이스 행을 직접 변경하는 “현재 상태” 모델을 기본으로 하여, 계좌 잔액 변경 내역 추적, 임의 시점 잔액 계산, 정확한 잔액 조정에 어려움이 있습니다. 이를 해결하기 위해 저자는 모든 잔액 변경을 불변(immutable) 기록으로 저장하는 원장(ledger)과 유사한 회계 시스템을 제안합니다. CustomerAccount 모델에 Mutation 레코드를 연결하여 특정 시점의 잔액을 집계하는 방식은 감사 및 예측에 용이하며, 제한적인 이벤트 소싱 형태로 볼 수 있습니다. ruby class CustomerAccount < ActiveRecord::Base has_many :mutations, -> { order(created_at: :desc) } def balance(at: Time.now) mutations.where("created_at < ?", at).sum(:amount) end end class Mutation < ActiveRecord::Base belongs_to :customer_account end account.mutations.create!(amount: -purchase_amount) # 음수는 인출을 의미

2. 시간 간격(Intervals) 및 타임존 처리

“시간에 따른 데이터” 시스템에서 간격은 ‘시작 포함, 끝 배타적’으로 정의되는 경우가 많습니다. 이를 정확히 다루기 위해 다음을 권장합니다.

  • 컬럼명에 _before, _after 접미사를 사용하여 포함/배타 여부를 명확히 합니다.

  • WHERE starts_at >= ? AND ends_before < ?와 같은 쿼리 방식을 사용합니다.

  • 범위 유형 인덱스를 지원하는 데이터베이스를 활용하고, 간격 사이에 발생할 수 있는 간극(gaps)을 처리할 수 있도록 설계합니다. 타임존 또한 시간에 따라 변하는 간격의 한 형태로, 특정 시점의 타임존 규칙을 적용해야 합니다.

3. 캐시 키 및 이벤트 순서 관리

  • 타임스탬프 기반 캐시 키의 한계: Rails의 updated_at 기반 캐시 키는 동시성 환경에서 Read-after-Write 일관성을 보장하기 어렵습니다. 동일한 시간에 여러 업데이트가 발생할 경우, 캐시 키가 제대로 갱신되지 않아 오래된 데이터가 제공될 수 있습니다. 이를 보완하기 위해 locking_column을 캐시 키에 포함하는 것이 좋습니다.

  • 웹훅 및 이벤트의 순서: 웹훅이나 이벤트는 전송 채널의 직렬성과 무관하게 순서가 보장되지 않는 ‘집합(set)’으로 간주해야 합니다. 발신자가 명확한 순서 정보를 제공하지 않는 한, 수신된 이벤트의 순서를 임의로 추정해서는 안 됩니다. 순서가 중요한 경우, 시퀀스 번호를 직접 추가하거나 웹훅을 원격 리소스를 다시 읽는 신호로만 활용하는 것이 더 안전합니다. ruby # Mutation 모델에 시퀀스 번호 추가 예시 (고객 계정 내) class Mutation < ActiveRecord::Base belongs_to :customer_account after_create do |m| m.class.connection.execute(<<~SQL) UPDATE #{m.class.table_name} SET seq = ( SELECT COUNT(*) + 1 FROM #{m.class.table_name} WHERE customer_account_id = #{m.customer_account_id} ) WHERE id = #{m.id} SQL end end

결론

이 글은 핀테크와 같이 시간에 따른 데이터의 정확성과 이력 추적이 중요한 도메인에서 Rails의 기본 데이터 모델링 방식이 갖는 근본적인 한계를 지적하고, 이를 극복하기 위한 실용적인 접근법들을 제시합니다. 불변하는 돌연변이 기록을 통한 원장 시스템, 간격 및 타임존의 명확한 처리, 그리고 캐시 키와 이벤트 순서에 대한 신중한 고려는 견고하고 감사 가능한 시스템을 구축하는 데 필수적입니다. 데이터 모델을 올바르게 구축하는 것이 성능 최적화보다 우선되어야 함을 강조하며, 이러한 아키텍처를 "이중 시간 모델링(bitemporal modeling)"이라고 소개합니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!