ES /docs

ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'captures.ids' in 'where clause'

RCA: ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'captures.ids' in 'where clause'

Overview#

이 클러스터는 Datadog Error Tracking issue 19f875ca-23f2-11f0-82b0-da7ad0900002 로, cupixvista (tesla repo, QA/ECE 배포 cupix-tesla-ece-qa) 에서 발생한 MySQL 스키마 오류로 기록되어 있다. Representative Error 는 Unknown column 'captures.ids' 를 가리키지만, 이 샘플은 first_seen (2025-04-28) 시점의 오래된 것이며 현재 tesla 코드에서는 해당 SQL 을 생성하는 경로를 찾을 수 없다.

What Happened#

Error Tracking 이 cupixvista 배포에서 ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'captures.ids' in 'where clause' 를 grouping 하고 있다. service 필드의 cupixvista-mysql2 는 APM mysql2 어댑터 instrumentation 이름이며 실제 앱 서비스가 아니다. 실제 앱은 tesla 다. Representative Error 는 first_seen 시점의 stale sample 이고, retention 14일 범위의 Datadog 로그에는 이 메시지와 일치하는 error 로그가 하나도 없다.

Quick Facts#

Field Value
exception.class ActiveRecord::StatementInvalid / Mysql2::Error
exception.message Unknown column 'captures.ids' in 'where clause'
runtime Ruby on Rails (tesla), MySQL
env QA / ECE 배포 cupix-tesla-ece-qa (us-west-2), APM adapter service cupixvista-mysql2

top_framedeploy 는 Error Tracking sample 에 노출되지 않아 생략한다.

Affected Teams#

Team / Domain Error Count Impact
cupixvista (tesla, QA/ECE) 2280673 (ET 누적) Review 관련 record/capture 조회 쿼리에서 SQL 실패 가능성. 단, 현재 코드/로그로 재현되는 활성 발생은 확인되지 않음

occurrence_count 는 15개월 누적치이며 최근 발생 빈도를 반영하지 않는다.

Timeline#

  1. 2025-04-28 14:31 KST — Error Tracking issue 최초 발생 (first_seen). Representative Error 가 이 시점의 Unknown column 'captures.ids' 로 pin 됨.
  2. 2026-08-04 16:35 KST — issue last_seen. 그러나 이 시각 근방 Datadog error 로그에는 일치 항목 없음.
  3. 2026-08-04 (RCA) — 코드 탐색 결과 현재 tesla develop 에서 captures.ids SQL column 을 생성하는 경로 없음. stale representative sample 로 판정.

Error Log#

Datadog Logs

text
Mysql2::Error: Unknown column 'captures.ids' in 'where clause'

Impact#

  • Service: cupixvista-mysql2 (APM adapter, 실제 앱은 tesla)
  • 발생 횟수: 2280673 (ET 누적, 최근 빈도 아님)
  • 최초 발생: 2025-04-28 14:31 KST
  • 최근 발생: 2026-08-04 16:35 KST

Root Cause Summary#

Representative Error Unknown column 'captures.ids' 는 ActiveRecord 가 nested hash condition where(captures: { ids: ... })captures.ids 라는 컬럼 참조로 변환할 때 발생한다. captures 테이블의 primary key 는 id 이지 ids 가 아니므로 MySQL 이 Unknown column 을 던진다. 그러나 현재 tesla (develop) 코드에는 where(captures: { ids: ... }) 형태가 존재하지 않는다. 모든 nested captures: where 절은 유효한 컬럼(cycle_state, state, level_id, record_id, published_at, upload_state)만 참조한다. captures: { ids: ... } 는 SQL 이 아닌 Cupix::Logger kwargs 로만 존재한다. 이 Representative Error 는 first_seen (2025-04-28) 시점의 stale sample 로, 당시 존재했던 잘못된 nested where 절이 이후 수정되었으나 Error Tracking issue 는 resolve 되지 않아 계속 열려 있는 것으로 판단된다. Error Tracking 은 여러 Unknown column 'X' variant 를 하나의 issue 로 grouping 하며 오래된 메시지를 pin 하는 특성이 있어, 현재 last_seen 이 이 메시지를 실제로 반영하지 않을 수 있다.

Technical Analysis#

Code Path#

Entry point 후보는 Review 도메인의 capture 조회 경로다. Elasticsearch 조회가 Faraday::ConnectionFailed 로 실패하면 legacy MySQL 경로로 fallback 한다.

app/repositories/concerns/accessible_entities_repository/review.rb:110-121ruby
def captures(level_ids: nil, record_ids: nil, visibility: Cyclable.visibility[:UNTRASHED])
  accessible_captures(level_ids: level_ids, record_ids: record_ids, visibility: visibility)
rescue Faraday::ConnectionFailed
  Cupix::Logger.warn('Failed to get captures from ES. Try to get captures from legacy.', class: self.class.name, function: __method__)
  accessible_captures_legacy(level_ids: level_ids, record_ids: record_ids, visibility: visibility)

현재 legacy fallback 은 유효한 컬럼(level_id, record_id)만 참조한다. captures.ids 를 만들지 않는다.

app/repositories/concerns/accessible_entities_repository/review/legacy.rb:47-51ruby
def accessible_captures_legacy(level_ids: nil, record_ids: nil, visibility: Cyclable.visibility[:UNTRASHED])
  raise Cupix::Errors::PermissionDenied.new(code: 'PERM10000', reason: 'Permission denied') if @current_user.present? && !@model.readable_by?(@current_user)

  ::Capture.where(level_id: level_ids.presence || _level_ids, record_id: record_ids.presence || _record_ids).untrashed.published
end

현재 코드의 nested where(captures: {...}) 는 모두 유효 컬럼만 쓴다. 예를 들어 Review panos 조회는 다음과 같다.

app/repositories/review_repository.rb:418-428ruby
def panos
  ::Pano.joins(:capture, capture: %i[record level])
        .where(captures: {
          record_id: record_ids,
          level_id: level_ids
        })
        .where.not(captures: {
          published_at: nil
        })
        .untrashed
        .published
end

Unknown column 'captures.ids' 를 생성하려면 위와 같은 nested where 의 key 가 ids 여야 한다. 이런 형태는 현재 tree 에 없다. captures: { ids: ... } 패턴은 SQL 이 아닌 logger kwargs 로만 존재한다.

lib/cupix/cron/sitetrack.rb:24ruby
Cupix::Logger.warn("Sitetrack #{sitetrack.id} has captures not associated, count: #{not_associated_captures.count}", class: self.class.name, function: __method__, sitetrack: { id: sitetrack.id }, captures: { ids: not_associated_captures.join(','), count: not_associated_captures.count })

기대 동작: capture 조회 쿼리가 captures.id 등 유효 컬럼만 참조. 실제(과거) 동작: 어떤 경로가 where(captures: { ids: ... }) 를 만들어 captures.ids 참조 → MySQL Unknown column. 현재 코드에서는 이 실제 동작을 재현할 수 없다.

Log Evidence#

Datadog 로그에서 이 메시지 또는 관련 SQL 오류를 찾지 못했다. 사용한 쿼리와 결과는 다음과 같다.

text
service:cupixvista-mysql2 status:error            (now-2d)   → 0 logs
service:cupixvista-api "Unknown column"           (now-2d)   → 0 logs
service:cupixvista-api "captures.ids"             (now-2d)   → 0 logs
service:cupixvista-api status:error               (now-2d)   → 0 logs
"Unknown column" "captures"                       (now-2d)   → 0 logs
service:(cupixvista-api OR cupixvista-worker) status:error "StatementInvalid"  (now-7d) → 0 logs
"Mysql2::Error" "Unknown column"                  (now-14d)  → 0 logs
"Unknown column"                                  (now-14d)  → 0 logs
"ActiveRecord::StatementInvalid"                  (now-14d)  → 0 logs

cupixvista-api 는 Datadog 에 로그를 보내고 있다. info-level healthcheck 는 정상 수집된다.

json
{
  "timestamp": "2026-08-04 17:39:02",
  "status": "info",
  "message": "[200] GET /status (Api::V1::ApiController#status)"
}

즉 서비스는 로그를 배출하지만, retention 14일 범위에 이 SQL 오류의 error 로그는 하나도 없다. 이 issue 는 APM (Error Tracking) 이 예외를 포착했을 뿐 대응하는 로그 라인이 없는 형태다. 이는 cupixvista-* 어댑터 서비스 (-rest_client, -mysql2) ET issue 의 알려진 패턴과 일치한다 (memory: cupixvista-api 4f477daf episode).

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 현재 코드의 where(captures: { ids: ... }) nested condition 이 captures.ids SQL 을 생성 메시지 형태가 AR nested hash → table.column 변환과 정확히 일치 현재 tree 에 captures: { ids: ... } SQL 없음. 모든 nested captures: where 는 유효 컬럼(cycle_state/state/level_id/record_id/published_at) 사용 (review_repository.rb:420, legacy.rb:50, statable/pano.rb:20) Rejected
H2 Representative Error 가 stale sample 이고, 현재 last_seen 은 다른 variant 를 반영하거나 재발하지 않음 first_seen 2025-04-28 (15개월 전). 14일 retention 내 매칭 로그 0건. 현재 코드에 재현 경로 없음. ET 는 여러 Unknown column variant 를 한 issue 로 묶고 old sample 을 pin 없음 Confirmed
H3 ES Faraday::ConnectionFailed fallback 의 legacy MySQL 경로가 잘못된 컬럼 참조 fallback 존재 (review.rb:112-114) legacy 경로(legacy.rb:47-51)는 level_id/record_id 만 참조. ids 컬럼 없음 Rejected
H4 외부 의존성/인프라 outage 로 인한 발생 Incident board svc:cupixvista-mysql2::unknown → active null, recent 비어 있음. 스키마 오류는 dependency outage 아님 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 코드 변경 불필요. 현재 tesla develop 에는 captures.ids SQL 을 생성하는 경로가 없다. Representative Error 는 stale sample 이다.
  • Datadog Error Tracking 에서 issue 19f875ca-23f2-11f0-82b0-da7ad0900002 를 확인해 실제 최근(last_seen) occurrence 의 메시지가 captures.ids 인지, 아니면 다른 Unknown column 'X' variant 인지 검증한다. representative 와 다르면 그 variant 로 재분석이 필요하다.
  • 최근 occurrence 가 실제로 없다면(재발 종료) issue 를 resolve 처리해 노이즈를 정리한다.

단기 개선 (1주 이내)#

  • ET issue 에 실제 발생 timeseries 가 여전히 살아 있다면, sample 의 최신 stack frame 과 정확한 컬럼명을 확보해 어느 repository 메서드가 잘못된 nested where 를 만드는지 특정한다. 로그가 없는 상태에서는 APM trace 의 db.statement (실제 SQL) 를 근거로 삼아야 한다.

장기 개선 (재발 방지)#

  • cupixvista (QA/ECE) 배포에서 ActiveRecord::StatementInvalid 발생 시 error-level 로그를 남기도록 예외 로깅을 보강한다. 현재 이 예외는 APM 에만 잡히고 Datadog 로그로 오지 않아 RCA 시 evidence 확보가 불가능하다.
  • Error Tracking issue 가 서로 다른 Unknown column variant 를 하나로 묶어 stale sample 을 pin 하는 문제를 collector 단계에서 완화한다. representative 대신 last_seen sample 을 우선 수집하도록 개선을 검토한다.

Monitoring#

이 SQL 오류가 실제로 재발하는지 시계열로 추적한다.

text
count:trace.rails.request.errors{service:cupixvista-mysql2}.as_count()

앱 서비스 레벨에서 error 로그 발생 여부를 함께 본다.

text
count:logs{service:cupixvista-api status:error "Unknown column"}.as_count()

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial
  • 현재 코드에 재현 경로가 없고 14일 내 매칭 로그가 없어 코드 수정 대상이 아니다. Error Tracking issue 정리 및 로깅 보강이 실제 액션이다.

Noise Verdict#

noise — 현재 tesla 코드에 captures.ids SQL 을 생성하는 경로가 없고 retention 14일 내 매칭 로그가 0건이라 stale representative sample 로 판단되며, 코드 수정이 필요한 활성 결함이 아니다.