ES /docs

Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)

RCA: Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)

Overview#

What Happened#

2026-07-28 18:20:15 KST 에 cupixworks-apiPUT /api/v1/phase_metrics/249 요청 1건이 Cupix::Errors::Parameter (ARG10001) 로 400 응답을 반환했다. 원인은 사용자가 저장한 phase metric formula 를 Dentaku 로 파싱하는 과정에서 Dentaku::AST::Multiplication has too many operands (given 3, expected 2) 예외가 발생했기 때문이며, 이는 formula 문자열에 곱셈 노드 하나에 대해 피연산자가 3개인 잘못된 표현식(예: 연속된 dimension token 사이에 연산자 누락)이 포함되었기 때문이다. 같은 endpoint 는 동일 리소스(id=249)에 대해 12초 전(18:20:03)과 12초 후(18:20:27) 요청 모두 200 으로 성공했다.

Quick Facts#

Field Value
exception.class Cupix::Errors::Parameter
exception.message Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)
top_frame lib/cupix/util/formula_parser.rb:22 (Dentaku::Calculator#evaluate!)
logged_by Cupix::Util::FormulaParser.calculate! (lib/cupix/util/formula_parser.rb:24)
endpoint PUT /api/v1/phase_metrics/249
env production, us-west-2
tenant cupix
error_code ARG10001

Affected Teams#

Team / Domain Error Count Impact
phase metrics (formula authoring) 1 단일 요청 400 반환; 동일 유저의 이후 요청은 성공 — 사용자 영향 없음

Timeline#

  1. 2026-07-28 18:17:46 KSTPUT /api/v1/phase_metrics/249 성공 (200)
  2. 2026-07-28 18:20:03 KSTPUT /api/v1/phase_metrics/249 성공 (200)
  3. 2026-07-28 18:20:15 KSTPUT /api/v1/phase_metrics/249 실패 (400, Invalid Formula: Dentaku::AST::Multiplication has too many operands). Cupix::Util::FormulaParser.calculate!Cupix::Logger.error 로 기록
  4. 2026-07-28 18:20:27 KSTPUT /api/v1/phase_metrics/249 성공 (200) — 사용자가 formula 를 수정해 재제출한 것으로 보임

Error Log#

Datadog Logs

text
Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 1
  • 최초 발생: 2026-07-28 18:20:15 KST
  • 최근 발생: 2026-07-28 18:20:15 KST

Root Cause Summary#

Cupix::Util::FormulaParser.calculate!Dentaku::Calculator#evaluate! 로 formula 문자열을 평가하고, 실패 시 모든 StandardError 를 rescue 하여 Cupix::Logger.error 로 로깅한 뒤 Cupix::Errors::Parameter 를 raise 한다. 이번 사건에서 Dentaku 파서는 곱셈 AST 노드 하나에 피연산자가 3개인 표현식을 만나 Dentaku::AST::Multiplication has too many operands (given 3, expected 2) 예외를 던졌으며, 이는 사용자가 저장한 formula 에 연산자 없이 인접한 dimension/property token(예: !{length}!{weight}*!{volume} 형태)이 포함되어 있었기 때문이다. Controller/repository 는 이 예외를 400 응답으로 정상 매핑했다(error.class: Cupix::Errors::Parameter, error.code: ARG10001). 즉 서버 로직 결함이 아니라 입력 검증이 정상 동작한 사례이며, 유일한 실질적 이슈는 사용자 입력 오류가 error 레벨 로그로 기록되어 에러 클러스터로 승격되었다는 점이다.

Technical Analysis#

Code Path#

Entry point: app/controllers/api/v1/phase_metrics_controller.rb:24 (Api::V1::PhaseMetricsController#update)

app/controllers/api/v1/phase_metrics_controller.rb:24-28ruby
def update
  @model = repository_instance.update(params)

  super
end

Repository update 는 set_parameters 를 호출해 formula 를 검증한다:

app/repositories/phase_metric_repository.rb:50-62ruby
def update(params = {})
  super

  set_parameters(params)

  begin
    @model.save!
  rescue StandardError => e
    raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: 'Invalid argument', message: e.message)
  end

  @model
end

Parameter::PhaseMetric concern 이 set_parameters 안에서 FormulaParser.validate! 를 호출:

app/concerns/parameter/phase_metric.rb:10-20ruby
def set_parameters(params = {})
  set_formula(params) if params[:formula].present?

  super
end

def set_formula(params)
  Cupix::Util::FormulaParser.validate!(params[:formula], @model.facility)

  @model.formula = params[:formula]
end

Failure point: lib/cupix/util/formula_parser.rb:19-28validate! 가 dimension/custom_property/constant 토큰을 1 로 치환한 뒤 calculate! 로 Dentaku 파싱을 수행. 사용자 입력이 곱셈 노드에 3개 피연산자를 만들면 여기서 예외 발생:

lib/cupix/util/formula_parser.rb:19-28ruby
def calculate!(expression)
  calculator = ::Dentaku::Calculator.new
  begin
    calculator.evaluate!(expression).to_f!.round(5)
  rescue StandardError => e
    Cupix::Logger.error("Invalid Formula: #{e.message}", class: self.name, function: __method__, formula: expression)

    raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: "Invalid Formula: #{e.message}")
  end
end

validate! 는 위 로직을 사용자 formula 에 그대로 적용한다:

lib/cupix/util/formula_parser.rb:30-34ruby
def validate!(formula, facility)
  calculate!(formula.gsub(/!\{([^}]*)\}/) { |m| '1 ' if validate_dimension!(Regexp.last_match(1)) }
                    .gsub(/\$\{([^}]*)\}/) { |m| '1 ' if validate_custom_property!(Regexp.last_match(1), facility) }
                    .gsub(/@\{([^}]*)\}/) { |m| '1 ' if validate_constant!(Regexp.last_match(1)) })
end

Spec 파일이 정확히 이 실패 케이스를 명시적으로 커버한다 — 즉, "operator 개수가 잘못된 formula" 는 의도된 검증 실패 경로이다:

spec/lib/cupix/util/formula_parser_spec.rb:54-63ruby
it 'return false with invalid number of operands & operators' do
  invalids = []
  invalids << '!{length}!{weight}+!{volume}*!{volume}!{volume}'
  invalids << '!{area}!{area}!{area}!{area}!{area}!{area}!{area}!{area}!{area}!{area}!{area}*!{volume}*!{area}'
  invalids << '!{length}++!{weight}'
  invalids << '!{length}+*!{weight}'

  invalids.each do |exp|
    expect { described_class.validate!(exp, facility) }.to raise_error(Cupix::Errors::Parameter)
  end
end

기대 동작 vs 실제 동작: 잘못된 formula 를 클라이언트가 보내면 validate!calculate! 가 Dentaku 예외를 잡아 Cupix::Errors::Parameter(ARG10001) 로 400 응답을 반환하는 것이 설계된 흐름이다. 실제로도 400 이 반환되었고 곧이어 사용자가 formula 를 수정해 200 성공했다. 유일한 부작용은 calculate! 안의 Cupix::Logger.error 호출이 사용자 입력 실패까지 error 레벨로 기록하여 error-sweeper 클러스터에 포집된다는 점이다.

Log Evidence#

Datadog query (재현용):

text
service:cupixworks-api "phase_metrics/249"

시간대별 요청/에러 로그:

json
{
  "timestamp": "2026-07-28 18:17:46 KST",
  "status": "info",
  "message": "[200] PUT /api/v1/phase_metrics/249 (Api::V1::PhaseMetricsController#update)"
}
json
{
  "timestamp": "2026-07-28 18:20:03 KST",
  "status": "info",
  "message": "[200] PUT /api/v1/phase_metrics/249 (Api::V1::PhaseMetricsController#update)"
}
json
{
  "timestamp": "2026-07-28 18:20:15 KST",
  "status": "info",
  "message": "[400] PUT /api/v1/phase_metrics/249 (Api::V1::PhaseMetricsController#update)",
  "error": {
    "reason": "Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)",
    "code": "ARG10001",
    "message": "Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)",
    "class": "Cupix::Errors::Parameter"
  }
}
json
{
  "timestamp": "2026-07-28 18:20:15 KST",
  "status": "error",
  "message": "Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)",
  "class": "Cupix::Util::FormulaParser",
  "function": "calculate!"
}
json
{
  "timestamp": "2026-07-28 18:20:27 KST",
  "status": "info",
  "message": "[200] PUT /api/v1/phase_metrics/249 (Api::V1::PhaseMetricsController#update)"
}

추가 확인: 최근 14일간 service:cupixworks-api "Dentaku" 검색은 이 사건 1건 외 결과 없음(service:cupixworks-api "FormulaParser" 0건). 재발/광범위 영향 징후 없음.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 사용자가 저장한 formula 문자열이 Dentaku 문법에 어긋나(곱셈 노드에 3-피연산자) 발생한 입력 검증 실패 로그의 error.class: Cupix::Errors::Parameter, error.code: ARG10001, HTTP 400 응답; formula_parser.rb:19-28 의 rescue → raise 흐름; spec 이 동일 케이스(!{length}!{weight}+...)를 커버(formula_parser_spec.rb:54-63); 12초 후 동일 endpoint 200 성공 Confirmed
H2 Dentaku gem 버그 또는 라이브러리 회귀로 인한 서버측 결함 최근 14일간 동일 서비스에서 Dentaku/FormulaParser 관련 에러 총 1건, 동일 리소스(id=249)에 대해 앞뒤 요청 성공. 회귀였다면 유사 로그가 다수 축적되어야 함 Rejected
H3 부분 저장/데이터 손상으로 phase_metric 자체가 이상 상태에 빠짐 repository.updateset_parameters 에서 검증 실패 시 예외를 raise 하므로 @model.save! 에 도달하지 않음(phase_metric_repository.rb:50-62). 이후 200 성공으로 정상 저장 확인 Rejected
H4 Cupix::Logger.error 호출이 클러스터 승격의 실질 이슈 formula_parser.rb:24 가 사용자 입력 오류를 무조건 error 레벨로 기록 → error-sweeper 가 클러스터화. 그러나 재발 빈도 낮음(1건) 로그 레벨 문제는 root cause 가 아니라 signal 노이즈 Confirmed(부수 이슈)

Fix Recommendation#

즉시 조치 (Critical)#

  • 없음. 사용자 입력 검증이 설계대로 동작해 400 을 반환했다. 코드 변경 불필요.

단기 개선 (1주 이내)#

  • lib/cupix/util/formula_parser.rb:24Cupix::Logger.errorwarn 레벨로 낮출지 검토. 이 로그는 calculate! 내부에서 Dentaku 예외를 잡을 때 항상 찍히는데, validate! 경로에서는 곧바로 ARG10001 로 400 응답이 나가는 정상 흐름이다. 사용자 입력 오류(정상 400)와 시스템 오류(진짜 버그)를 구분하지 못해 error-sweeper 클러스터로 승격되어 노이즈를 만든다. 대안:
    • validate! 호출 컨텍스트(사용자 입력 검증)에서는 warn, 내부에서 이미 정상화된 표현식을 계산하는 컨텍스트(예: parse! 이후 계산)에서는 error 로 분기.
    • 또는 rescue 대상을 Dentaku::ParseError/Dentaku::TokenizerError(및 유사 문법 오류) 로 좁혀 warn, 그 외 StandardError 는 기존대로 error 유지. (Sorbet/의존성 확인 필요 — memory 노트 참고)
  • 근거: MEMORY.md "Assess error severity during RCA" 및 "Scope warn-level downgrades to the specific exception class" 원칙을 따라, 광범위 rescue 를 유지하되 문법 오류만 downgrade.

장기 개선 (재발 방지)#

  • 프런트엔드 formula 편집기에 실시간 문법 검증기(Dentaku 와 동일 규칙) 를 도입해 서버 400 왕복 없이 사용자에게 즉시 피드백 제공. 서버 로그에 잡히는 사용자 입력 오류 자체를 줄여 관측 노이즈 감소.

Monitoring#

  • error 레벨로 남은 formula 검증 실패는 error-sweeper 가 자동 클러스터화하므로 별도 알림은 불필요. 단기 개선으로 warn 이동 시, 다음 쿼리로 volume 을 모니터링:
text
service:cupixworks-api status:warn "Invalid Formula"
  • 실제 서버측 회귀(Dentaku 업그레이드 등)를 조기 감지하려면 아래 쿼리로 급증 확인:
text
service:cupixworks-api status:error @class:Cupix\:\:Util\:\:FormulaParser

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (단기 개선 채택 시 단일 파일 로그 레벨 조정)