ES /docs

Cupix::Errors::Argument: Result window is too large, from + size must be less than or equal to: [10000] but was [10200].

RCA: Cupix::Errors::Argument: Result window is too large

Overview#

What Happened#

cupixworks-api(tesla)의 GET /api/v1/admin/editings 목록 조회에서, 클라이언트가 Elasticsearch 의 index.max_result_window(기본 10,000) 를 넘어서는 깊은 페이지를 요청했다. ES 가 from + size = 10200 > 10000 이라며 BadRequest 를 던지고, tesla 는 이를 Cupix::Errors::Argument(ARG13000) 로 감싼 뒤 HTTP 500 으로 응답했다. 클라이언트 페이지네이션 입력 문제가 서버 500 으로 잘못 매핑된 케이스다.

Quick Facts#

Field Value
exception.class Cupix::Errors::Argument
exception.message Result window is too large, from + size must be less than or equal to: [10000] but was [10200].
error.code ARG13000
top_frame app/repositories/base_repository.rb:87
entry point app/controllers/api/v1/admin/editings_controller.rb:12 (Api::V1::Admin::EditingsController#index)
http.status 500
env production, cupixworks-api

Affected Teams#

Team / Domain Error Count Impact
admin (editing 목록 조회 UI) 14일 내 9건 깊은 페이지 요청 시 목록 조회 500. 첫 페이지 이후 계속 페이징하는 관리자에게만 발생

Timeline#

  1. 2025-02-03 13:06 KST — 최초 발생 (first_seen).
  2. 2026-08-02 09:18-09:20 KST — 동일 엔드포인트에서 4건 연속 발생 (같은 사용자가 반복 페이징한 것으로 추정되는 버스트).
  3. 2026-08-02 19:11 KST — 가장 최근 발생 (last_seen).

Error Log#

Datadog Logs

text
Result window is too large, from + size must be less than or equal to: [10000] but was [10200]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting.

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 130 (전체 기간), 14일 내 9건
  • 최초 발생: 2025-02-03 13:06 KST
  • 최근 발생: 2026-08-02 19:11 KST

Root Cause Summary#

Api::V1::Admin::EditingsController#index 는 클라이언트의 pageper_page 파라미터로 QueryOption 을 만들어 Elasticsearch 검색을 수행한다. Cupix::QueryOption::Basepage >= 1, per_page <= 300 만 검증할 뿐 from + size = (page - 1) * per_page + per_page 값이 ES 의 index.max_result_window(기본 10,000) 를 넘는지는 검사하지 않는다. 클라이언트가 깊은 페이지(예: page=34, per_page=300 → 10,200)를 요청하면 ES 가 illegal_argument_exception 을 담은 BadRequest 를 반환하고, base_repository.rb:83-87 이 이를 Cupix::Errors::Argument(ARG13000) 로 감싼다. 이 예외는 server_error_controller.rb:7-8system_500_error 로 rescue 되어 HTTP 500 이 된다. 즉 근본 원인은 서버 로직 결함이 아니라 클라이언트 페이지네이션 한계 입력이며, 여기에 클라이언트 오류(4xx)가 서버 오류(500)로 매핑되는 상태 코드 결함이 겹친 것이다.

Technical Analysis#

Code Path#

  • Entry point: app/controllers/api/v1/admin/editings_controller.rb:12
app/controllers/api/v1/admin/editings_controller.rb:12-14ruby
def index
  editing_query_option = Cupix::QueryOption::Editing.new(get_query_option(enable_current_team: false), params)
  editings = repository_instance.search(editing_query_option)
  • get_query_option 이 클라이언트 page/per_page 를 그대로 QueryOption 에 전달한다.
app/controllers/concerns/searchable_controller.rb:18-27ruby
def get_query_option(enable_current_team: true)
  _query_option = QueryOption.new(
    per_page: params[:per_page],
    page: params[:page],
    ...
  )
  • QueryOption::Basepage 하한(>= 1)과 per_page 상한(<= 300)만 검증한다. page 상한이나 from + size 검증은 없다.
lib/cupix/query_option/base.rb:55-83ruby
raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: "Invalid value in page: #{opts[:page]}") if !opts[:page].nil? && opts[:page].to_i < 1
raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: "Invalid value in per_page: #{opts[:per_page]}") if !opts[:per_page].nil? && opts[:per_page].to_i < 1
# ...
@page = opts[:page].nil? ? default_page : opts[:page].to_i
@per_page = opts[:per_page].nil? ? default_per_page : opts[:per_page].to_i
# ...
if @per_page.present? && @per_page > 300
  raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: "Invalid per_page: #{@per_page}; maximum is 300")
end
  • Failure point: app/repositories/base_repository.rb:83-87. search 실행 중 permission_joins/default_joins 가 ES 쿼리를 lazy 하게 materialize 할 때 ES 가 BadRequest 를 던지고, root_cause[0]['reason']("Result window is too large...")을 Cupix::Errors::Argument(ARG13000)로 감싼다.
app/repositories/base_repository.rb:83-87ruby
rescue Elasticsearch::Transport::Transport::Errors::BadRequest => e
  _message = JSON.parse(e.message.split(/\[\d{3}\]\s/i)[1]) rescue {}
  _reason = _message['error']['root_cause'][0]['reason'] rescue nil

  raise Cupix::Errors::Argument.new(code: 'ARG13000', reason: _reason)
  • Cupix::Errors::ArgumentCupix::Errors::System 과 함께 system_500_error 로 rescue 되어 500 이 된다. 클라이언트 페이지네이션 오류인데 서버 500 으로 노출되는 지점이다.
app/controllers/concerns/server_error_controller.rb:7-8,38-40ruby
rescue_from Cupix::Errors::System,
            Cupix::Errors::Argument, with: :system_500_error
# ...
def system_500_error(exception)
  raise_error(500, exception)
end

기대 동작: 페이지네이션 한계를 넘는 요청은 4xx(예: 400/422)로 거절되거나, 애초에 from + size <= 10000 범위로 제한되어야 한다. 실제 동작: 10200 요청이 ES 를 통과 시도 → BadRequestARG13000 → HTTP 500 + Error Tracking 집계.

Log Evidence#

Datadog 쿼리 (재현용):

text
service:cupixworks-api "Result window is too large"
text
service:cupixworks-api "[500] GET /api/v1/admin/editings"

최근 발생 로그 (원문, status:info 로 기록된 500 request 로그):

json
{
  "timestamp": "2026-08-02 10:11:03",
  "status": "info",
  "message": "[500] GET /api/v1/admin/editings (Api::V1::Admin::EditingsController#index)",
  "error": {
    "reason": "Result window is too large, from + size must be less than or equal to: [10000] but was [10200]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting.",
    "code": "ARG13000",
    "message": "Result window is too large, from + size must be less than or equal to: [10000] but was [10200]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting.",
    "class": "Cupix::Errors::Argument"
  }
}

14일 내 service:cupixworks-api "[500] GET /api/v1/admin/editings" 발생 타임스탬프 (UTC):

text
2026-08-02 10:11:03
2026-08-02 00:20:13
2026-08-02 00:19:45
2026-08-02 00:19:11
2026-08-02 00:18:41
2026-08-01 17:26:41
2026-08-01 17:23:55
2026-08-01 16:30:27
2026-07-25 11:44:31

패턴: 2026-08-02 00:18-00:20 UTC 사이 4건이 30초 간격으로 연속 발생 — 동일 사용자가 목록을 계속 페이징하며 매번 500 을 받은 버스트로 해석된다. 모든 발생이 동일 엔드포인트(admin/editings)와 동일 10200 값을 가진다. Representative Error 와 최근 로그가 정확히 일치하므로 대표 샘플은 stale 하지 않다.

주의: 같은 "Result window is too large" 검색은 다른 엔드포인트(GET /api/v1/element_traces, max_result_window: 200000, 292800, [502]/[400])의 로그도 섞여 나온다. 그 변형은 다른 코드 경로(entity/element_traces 검색, 상한 200,000)이며 본 클러스터(admin/editings, 상한 10,000, ARG13000, 500)와 근본 원인은 같은 계열이나 별개 이슈다. 본 RCA 는 admin/editings 500 경로에 한정한다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 클라이언트가 ES max_result_window(10,000)를 넘는 깊은 페이지를 요청 → ES BadRequestARG13000 → 500. page/from+size 상한 미검증 + 4xx→500 매핑 결함 Datadog: [500] GET /api/v1/admin/editings 로그의 error.code:ARG13000, from + size ... [10000] but was [10200]. 코드: base.rb:55-83page 상한/from+size 미검증, base_repository.rb:87ARG13000 로 wrap, server_error_controller.rb:7-8 가 500 매핑 없음 Confirmed
H2 서버측 검색 로직/인덱스 결함으로 항상 실패 10200page/per_page 조합에서만 나오는 클라이언트 결정값. 첫 페이지 요청은 정상(에러가 깊은 페이지 버스트에 국한). 특정 데이터/인덱스가 아니라 페이지 깊이에만 의존 Rejected
H3 ES 클러스터 장애/max_result_window 설정 변경 status-board svc:cupixworks-api::unknown 에 관련 active 인시던트 없음. 10000 은 ES 기본값이며 변경 흔적 없음. 메시지가 결정적(deterministic) Rejected
H4 대표 샘플이 stale 하고 실제 최근 메시지는 다른 값 최근 로그(2026-08-02)와 대표 메시지가 문자 그대로 일치(10200) Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 상태 코드 매핑 교정: admin/editings(및 동일 검색 경로)에서 ES max_result_window 초과는 클라이언트 요청 오류이므로 4xx 로 응답해야 한다. base_repository.rb:83-87Cupix::Errors::Argument(ARG13000) 는 server_error_controller.rb:7-8 에서 500 으로 매핑되므로, 이 페이지네이션 초과 케이스만 4xx(예: Cupix::Errors::Parameter 계열)로 분류하도록 좁혀야 한다. Cupix::Errors::Argument 를 전역으로 4xx 로 옮기면 다른 사용처에 영향을 주므로 금지 (ARG10060/to_i 에피소드와 동일한 주의).

단기 개선 (1주 이내)#

  • 페이지네이션 상한 사전 검증: lib/cupix/query_option/base.rb 에서 per_page > 300 검증과 함께 (page - 1) * per_page + per_page > ES_DEFAULT_MAX_RESULT_WINDOW 인 경우를 사전에 Cupix::Errors::Parameter(4xx) 로 거절하도록 추가한다. 이렇게 하면 ES 로 요청이 나가기 전에 명확한 클라이언트 오류로 응답하고 Error Tracking 노이즈도 제거된다. ES_DEFAULT_MAX_RESULT_WINDOW 상수(app/models/concerns/searchable/* 에서 사용)를 참조.
  • 프런트엔드 협의 필요: admin editing 목록 UI 가 10,000 건 이상 페이징을 시도하지 않도록 페이저 상한을 두거나, 깊은 탐색이 필요하면 필터/검색으로 유도한다. 이 항목은 클라이언트 계약 변경이므로 백엔드 자동 수정 범위 밖.

장기 개선 (재발 방지)#

  • 대량 조회가 실제로 필요하면 from/size 대신 ES search_after(scroll 대체) 기반 커서 페이지네이션 도입을 검토한다. 오류 메시지도 "See the scroll api" 를 권한다.

Monitoring#

admin/editings 페이지네이션 초과 500 추이:

text
service:cupixworks-api "[500] GET /api/v1/admin/editings" "Result window is too large"

ES result-window 초과 계열 전반(엔드포인트 무관) 추이:

text
service:cupixworks-api "Result window is too large"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard (상태 코드 매핑 교정 + query_option 사전 검증은 좁은 범위 변경이나, 프런트엔드 페이저 상한은 별도 트랙 조율 필요)

Noise Verdict#

noise — ES max_result_window(10,000)를 초과하는 클라이언트의 깊은 페이지 요청이 원인이며 서버 로직 결함이 아니라 클라이언트 입력 검증/상태 코드 매핑 문제이므로 noise 로 판정한다.