Short answer
Compute cost from the validated, variable-coerced operation, not the raw document. For a paginated list field, use base + perItem × clamp(pageArg, 1, maxPageSize), where pageArg is the argument name your schema uses (commonly first or last). Clamp to the server-enforced maximum page size. Compose nested lists multiplicatively with saturating arithmetic. Make the cost budget the primary gate and depth a secondary guard. Full automatic cost generation is not realistic from schema alone; use shadow-mode measurements and resolver work metrics to fit weights, or use persisted queries/allowlists to avoid most tuning.
Confirmed vs. likely
Confirmed: complexity analysis, depth limiting, and cost directives are ecosystem conventions, not GraphQL spec features. Directive names, defaults, and whether cost is computed before or after variable coercion vary by server/library. Treat “GraphQL 2024.1” as an internal product version label; the GraphQL specification is published as dated editions, not a 2024.1 release. Likely in your case: the analyzer is reading static defaults or raw arguments, so a large page size passed through a variable bypasses the table. Static tables are also bypassable via aliases, fragments, and batched operations; handle each explicitly.
Steps for this case
- Switch the analyzer to log-only/shadow mode. Record computed cost, actual resolver work (rows fetched, latency, DB time), depth, and operation shape for real traffic.
- Make the cost function variable-aware. Read the coerced variable values after validation, including defaults. If your library exposes a cost directive/plugin, verify its name and default clamp in its docs.
- Model list fields as base + perItem × clamp(pageArg, 1, maxPageSize). Apply the multiplier to the field’s own work, then multiply through nested list paths.
- Set the cost threshold above the observed high percentile of legitimate operations, not at the maximum. Re-check after enforcement.
- Return a structured rejection: stable machine-readable code, computed cost, threshold in
extensions, and log it separately from execution errors.
Depth limits and dashboards
Depth caps recursion and fragment nesting; cost caps breadth and per-item work. Dashboards often produce wide, moderately deep queries, while recursive attacks are narrow and very deep. Prefer cost as the primary gate. If you still need depth, set it from the observed depth distribution of real dashboard traffic, and allow a higher limit for known persisted operations.
Automatic cost models
Schema-only heuristics (field count, list multipliers) give a first pass but rarely match backend cost. The practical approach is empirical: instrument data-fetching resolvers to emit a work metric, join it with per-field cost, and fit weights by regression or simple ratios. Recalibrate when resolvers or data volumes change. Persisted queries or an operation allowlist are often a better answer: unknown expensive shapes never reach the analyzer, and complexity limits remain as a fallback.
Verification
- Send the same operation twice: page size hard-coded, then supplied as a variable with a large value. If computed cost differs, the analyzer is not variable-aware.
- In shadow mode, compare computed cost against resolver work for a sample. Weak correlation means weights need recalibration.
- Inspect a rejection: confirm the operation never reaches resolvers and the response carries a stable code plus cost/threshold in a machine-readable location.
One diagnostic detail
If the recommendation changes, tell me whether your server computes cost before or after variable coercion. If it is before, fix that first; no amount of weight tuning will stop variable-based bypasses.