ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'capture_ids' in 'field list'
RCA: ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column in field list
Overview#
What Happened#
tesla (cupixworks-api) 의 entity 검색 인덱싱 경로에서 Record.where(id: ...).pick(:name) 쿼리가 실행되는데, records 테이블에는 name 컬럼이 존재하지 않아 Mysql2::Error: Unknown column 'name' in 'field list' 가 매번 발생한다. 예외는 상위 rescue StandardError 에서 즉시 nil 로 삼켜지므로(error.handling: handled) 사용자에게는 실패가 노출되지 않지만, record entity 를 재인덱싱할 때마다 실패하는 SQL 이 전송되어 APM 이 error span 으로 기록한다. 최근 1시간 표본에서 errored mysql2 span 2050+ 건이 전부 이 쿼리였다.
Representative Error 는 STALE 하다. 클러스터에 고정된 대표 메시지는 Unknown column 'capture_ids' (first_seen 2025-04-16) 이지만, Error Tracking 이 동일 fingerprint family(Unknown column 'X' in 'field list') 의 여러 변종을 한 이슈로 묶으면서 오래된 샘플을 pin 한 것이다. last_seen 시점의 실제 발생 메시지는 Unknown column 'name' 이며, capture_ids 변종은 현재 발생하지 않는다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | ActiveRecord::StatementInvalid (cause: Mysql2::Error) |
| exception.message | Mysql2::Error: Unknown column 'name' in 'field list' (현재), 대표값 Unknown column 'capture_ids' 는 stale |
| SQL | SELECT name FROM records WHERE records.id = ? LIMIT ? |
| top_frame | app/models/concerns/entity_indexable.rb:155 (_ancestor_name → pick(:name)) |
| error.handling | handled (rescue StandardError → nil) |
| runtime | ruby 3.3.0, activerecord-7.2.2, mysql2-0.5.4 |
| deploy | records ancestor 인덱싱 도입 커밋 75616e02b / 5e8521b9f (TSLA-1895) |
| env | production, us-west-2 (tesla_production) |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| tesla / cupixworks-api (entity search indexing) | 최근 1h 표본 2050+ span (전량 동일 쿼리) | 사용자 영향 없음 (rescue 로 삼켜짐). record entity 재인덱싱마다 실패 SQL round-trip + APM error span 노이즈 |
Timeline#
- 2025-04-16 11:16 KST — 클러스터 first_seen. 대표 메시지
Unknown column 'capture_ids'(현재는 미발생하는 stale 변종). - 2026-05-27 16:07 KST — TSLA-1895 로 unified entity search / ancestry 인덱싱 도입 (
75616e02b)._ancestor_name에klass.where(id:).pick(:name)추가. - 2026-06-01 09:42 KST — TSLA-1895 후속 (
5e8521b9f)에서_add_ancestor(h, :record, :record_id, cache)로 record ancestor 추가.records테이블에name컬럼이 없어Unknown column 'name'발생 시작. - 2026-08-04 18:55 KST — APM span 증거:
SELECT name FROM records WHERE records.id = ? LIMIT ?→Mysql2::Error: Unknown column 'name' in 'field list',error.handling: handled. - 2026-08-04 16:38 KST — 클러스터 last_seen.
Error Log#
Mysql2::Error: Unknown column 'capture_ids' in 'field list'
주의: 위는 클러스터에 고정된 stale representative 이다. last_seen 시점의 실제 발생 메시지는 아래 APM span (Log Evidence 참조):
Mysql2::Error: Unknown column 'name' in 'field list'
SQL: SELECT name FROM records WHERE records.id = ? LIMIT ?
Impact#
- Service:
cupixworks-mysql2(APM mysql2 instrumentation span; 실제 앱은 tesla /cupixworks-api) - 발생 횟수: 39131929 (Error Tracking 누적, 여러
Unknown column변종 포함) - 최초 발생: 2025-04-16 11:16 KST (stale
capture_ids변종 기준) - 최근 발생: 2026-08-04 16:38 KST (
name변종)
Root Cause Summary#
tesla 의 EntityIndexable concern 이 entity 검색 문서를 만들 때 조상(ancestor) 이름을 조회한다. _entity_ancestry (entity_indexable.rb:108-120) 는 _add_ancestor(h, :record, :record_id, cache) 를 호출하고, 이는 _ancestor_name (entity_indexable.rb:149-158) 에서 key.to_s.camelize.safe_constantize 로 Record 클래스를 얻은 뒤 Record.where(id: ancestor_id).pick(:name) 를 실행한다. 그러나 records 테이블에는 name 컬럼이 없어(schema 확인: note/description/uuid 는 있으나 name 없음) MySQL 이 Unknown column 'name' in 'field list' 를 반환한다. _ancestor_name 은 이 예외를 rescue StandardError 로 잡아 nil 을 반환하므로(error.handling: handled) 인덱싱은 정상 진행되고 사용자 영향은 없다. 다만 record entity 가 재인덱싱될 때마다 실패하는 SQL 이 DB 로 전송되어 mysql2 APM span 이 error 로 태깅되고, 이것이 Error Tracking 에 대량 누적된다. :capture ancestor 는 captures 테이블에 name 컬럼이 있어 정상이며, 문제는 name 컬럼이 없는 :record(그리고 name 없는 다른 조상) 에 국한된다.
Technical Analysis#
Code Path#
- Entry point: entity 인덱싱(save/update 후 재인덱싱 콜백) →
_entity_ancestry - Failure point:
app/models/concerns/entity_indexable.rb:155pick(:name)
_entity_ancestry 가 조상별로 _add_ancestor 를 호출한다. :record 는 records 테이블을 가리킨다.
def _entity_ancestry(cache = nil)
h = {}
_add_ancestor(h, :team, :team_id, cache)
_add_ancestor(h, :workspace, :workspace_id, cache)
_add_facility_ancestor(h, cache)
_add_ancestor(h, :record, :record_id, cache) # records 테이블엔 name 컬럼 없음
_add_ancestor(h, :capture, :capture_id, cache)
_add_ancestor(h, :level, :level_id, cache)
_add_ancestor(h, :bim, :bim_id, cache)
_add_ancestor(h, :review, :review_id, cache)
_add_ancestor(h, :annotation_layer, :annotation_layer_id, cache)
h
end
_ancestor_name 이 조상 클래스를 constantize 한 뒤 pick(:name) 로 이름 컬럼만 조회한다. key=:record 이면 klass=Record 가 되고, pick(:name) 은 SELECT name FROM records WHERE records.id = ? LIMIT 1 을 생성한다.
def _ancestor_name(key, ancestor_id, cache = nil)
return cache.ancestor_name(key, ancestor_id) if cache
klass = key.to_s.camelize.safe_constantize
return nil unless klass
klass.where(id: ancestor_id).pick(:name) # records 에 name 없음 → Unknown column 'name'
rescue StandardError
nil # 예외를 삼킴 → error.handling: handled
end
기대 동작: 조상 record 의 name 을 읽어 entity 문서에 넣는다. 실제 동작: records 테이블에 name 컬럼이 없어 MySQL 이 즉시 Unknown column 'name' 을 반환하고, rescue StandardError 가 nil 을 반환한다. entity 문서에는 record 이름이 항상 누락되지만 인덱싱 자체는 실패하지 않는다.
records 테이블 스키마 — name 컬럼 부재 (발췌):
t.text "description"
t.string "note"
t.string "uuid"
# ... t.string "name" 은 존재하지 않음
대조군 — captures 테이블에는 name 컬럼이 있어 :capture ancestor 는 정상:
t.string "name"
Log Evidence#
status:error 레벨 로그는 없다 (예외가 rescue 되어 애플리케이션 로그로 남지 않음). 증거는 mysql2 APM instrumentation span 이다. 사용한 Datadog spans 쿼리:
service:cupixworks-mysql2 status:error
last_seen 근처(2026-08-04) 표본 span 전체가 동일한 실패 SQL 이었다:
2026-08-04T09:55:05.123Z cupixworks-mysql2 | SELECT name FROM records WHERE records . id = ? LIMIT ?
2026-08-04T09:55:05.092Z cupixworks-mysql2 | SELECT name FROM records WHERE records . id = ? LIMIT ?
2026-08-04T09:55:04.923Z cupixworks-mysql2 | SELECT name FROM records WHERE records . id = ? LIMIT ?
span custom attributes (원문):
{
"base_service": "cupixworks-api",
"component": "active_record",
"db": {
"instance": "tesla_production",
"statement": "SELECT name FROM records WHERE records . id = ? LIMIT ?",
"system": "mysql2"
},
"env": "production",
"error": {
"file": "/var/app/current/vendor/bundle/ruby/3.3.0/gems/mysql2-0.5.4/lib/mysql2/client.rb",
"handling": "handled",
"message": "Mysql2::Error: Unknown column 'name' in 'field list'",
"stack": "... Mysql2::Error: Unknown column 'name' in 'field list' (ActiveRecord::StatementInvalid) ..."
}
}
빈도 확인 — 최근 1시간 errored mysql2 span 표본(약 2050건)이 전부 이 쿼리:
service:cupixworks-mysql2 status:error (from now-1h)
→ { "SELECT name FROM records WHERE records . id = ? LIMIT ?": 2050 }
Unknown column 'capture_ids' 대표 메시지 재검색은 로그/스팬 모두 0건:
"Unknown column 'capture_ids'" → 0 spans
service:cupixworks-mysql2 "Unknown column" (log) → 0 logs (rescue 되어 로그 없음, span 만 존재)
이는 대표 메시지가 stale 하고 실제 현재 발생은 name 변종임을 확정한다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 현재 발생 메시지는 대표값 capture_ids 가 아니라 name 이며, records 테이블에 name 컬럼이 없어 pick(:name) 이 실패한다 |
last_seen 근처 span 전량이 SELECT name FROM records ... + error.message: Unknown column 'name'; entity_indexable.rb:155 pick(:name); schema 에 records.name 부재 |
— | Confirmed |
| H2 | RevisionRequest 의 capture_ids 를 컬럼처럼 쿼리하여 Unknown column 'capture_ids' 발생 (대표 메시지 그대로) |
대표 메시지에 capture_ids 명시 |
capture_ids 는 Properties::RevisionRequest 의 가상 attribute(sys[:capture_ids], Metable serialize)로 DB 컬럼 아님; schema/migration 에 capture_ids 컬럼 없음; span/log 검색 0건 |
Rejected |
| H3 | 사용자 요청을 500 으로 실패시키는 기능 버그 | 대량 발생 | error.handling: handled (rescue StandardError → nil); status:error 애플리케이션 로그 0건; 인덱싱은 계속 진행됨 |
Rejected |
| H4 | 외부 의존성/인프라 장애 | — | status-board scope svc:cupixworks-mysql2::unknown, active null / recent 없음; MySQL 자체는 정상 (schema 오류일 뿐) |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
app/models/concerns/entity_indexable.rb:155—_ancestor_name이name컬럼이 없는 클래스에 대해pick(:name)을 호출하지 않도록 한다. 접근 방식(둘 중 하나):klass.column_names.include?('name')(또는klass.has_attribute?('name')) 로 가드한 뒤에만pick(:name)실행. 없으면 nil 반환.records등name이 없는 모델은 표시용 이름으로 다른 컬럼(예:records의 경우note/uuid여부는 제품 요구사항 확인 필요)을 쓰거나 ancestor name 을 아예 생략. 근거: 예외는 삼켜지지만 record entity 재인덱싱마다 실패 SQL round-trip 이 발생해 DB 부하와 APM error span 노이즈를 만든다. 컬럼 존재 여부는 정적으로 알 수 있으므로 DB 왕복 자체를 없애는 것이 정답이다.
단기 개선 (1주 이내)#
_entity_ancestry(entity_indexable.rb:108-120) 의 조상 목록 중 대상 테이블에name컬럼이 없는 조상(:record등)을 점검하고, 이름이 필요 없다면_add_ancestor에서 name 조회를 건너뛰도록 정리한다.- batch preload 경로(
5e8521b9f, TSLA-1895 의 cache 버전cache.ancestor_name)도 동일한 컬럼 부재 문제를 갖는지 확인 — cache 를 채우는 preload 쿼리도records.name을 select 하면 같은 오류가 난다.
장기 개선 (재발 방지)#
- APM mysql2 instrumentation 이 rescue 된 예외까지 error span 으로 태깅하므로, "handled" span 은 alarm/트래킹에서 필터하거나, 애초에 실패가 예상되는 쿼리를 코드에서 제거한다.
- ancestor name 조회를 컬럼 메타데이터 기반으로 일반화(예: 모델별
display_name_column선언)하여 신규 조상 추가 시 동일 실수를 방지한다.
Monitoring#
record.name 실패 SQL 재발을 추적하는 timeseries 쿼리:
service:cupixworks-mysql2 status:error @db.statement:"SELECT name FROM records WHERE records . id = ? LIMIT ?"
entity 인덱싱 관련 mysql2 error span 전반 추적:
service:cupixworks-mysql2 status:error @component:active_record
Risk Assessment#
- Risk level: low (사용자 영향 없음, rescue 로 삼켜짐 — 다만 DB round-trip / APM 노이즈 지속)
- 예상 복잡도: trivial (컬럼 존재 가드 추가 또는 name 조회 생략)
Noise Verdict#
bug — records 테이블에 없는 name 컬럼을 pick(:name) 로 조회하는 명백한 schema/SQL 불일치 결함으로, rescue 로 삼켜지지만 record 재인덱싱마다 실패 SQL 을 계속 전송하므로 코드 수정(컬럼 가드 또는 name 조회 생략)이 필요하다.