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_frame 와 deploy 는 Error Tracking sample 에 노출되지 않아 생략한다.
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixvista (tesla, QA/ECE) | 2280673 (ET 누적) | Review 관련 record/capture 조회 쿼리에서 SQL 실패 가능성. 단, 현재 코드/로그로 재현되는 활성 발생은 확인되지 않음 |
occurrence_count 는 15개월 누적치이며 최근 발생 빈도를 반영하지 않는다.
Timeline#
- 2025-04-28 14:31 KST — Error Tracking issue 최초 발생 (
first_seen). Representative Error 가 이 시점의Unknown column 'captures.ids'로 pin 됨. - 2026-08-04 16:35 KST — issue
last_seen. 그러나 이 시각 근방 Datadog error 로그에는 일치 항목 없음. - 2026-08-04 (RCA) — 코드 탐색 결과 현재 tesla develop 에서
captures.idsSQL column 을 생성하는 경로 없음. stale representative sample 로 판정.
Error Log#
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 한다.
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 를 만들지 않는다.
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 조회는 다음과 같다.
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 로만 존재한다.
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 오류를 찾지 못했다. 사용한 쿼리와 결과는 다음과 같다.
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 는 정상 수집된다.
{
"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.idsSQL 을 생성하는 경로가 없다. 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 columnvariant 를 하나로 묶어 stale sample 을 pin 하는 문제를 collector 단계에서 완화한다. representative 대신 last_seen sample 을 우선 수집하도록 개선을 검토한다.
Monitoring#
이 SQL 오류가 실제로 재발하는지 시계열로 추적한다.
count:trace.rails.request.errors{service:cupixvista-mysql2}.as_count()
앱 서비스 레벨에서 error 로그 발생 여부를 함께 본다.
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 로 판단되며, 코드 수정이 필요한 활성 결함이 아니다.