Skip to main content
Version: Next

GraphQL Data Connector Deployment Guide

Production operating guide for the GraphQL data connector covering authentication, pagination, and operational tuning.

Authentication & Secretsโ€‹

Authentication is endpoint-specific. The connector supports bearer tokens, custom headers via graphql_auth_header, and HTTP Basic Auth:

ParameterDescription
graphql_auth_headerCustom authorization header name. The value of graphql_auth_token is sent as this header's value.
graphql_auth_tokenBearer token for GraphQL requests. Typically "${secrets:api_token}".
graphql_auth_userUsername for HTTP Basic Auth.
graphql_auth_passPassword for HTTP Basic Auth.
graphql_queryThe GraphQL query to execute.
json_pointerRFC-6901 JSON pointer to the row collection inside the response (e.g. /data/repository/issues/nodes).

Tokens must be sourced from a secret store in production.

TLSโ€‹

Use HTTPS endpoints in production. Self-signed certificates require a trusted CA bundle in the container / host OS trust store.

Resilience Controlsโ€‹

Retry Behaviorโ€‹

Each GraphQL request is retried up to 5 times after the initial attempt, so a request fails after at most 6 tries. The retries apply to every page of a refresh and to the request the connector sends when a dataset loads to infer or validate its schema. Retries use Fibonacci backoff of about 1, 2, 3, 5, and 8 seconds with ยฑ30% jitter. When a retried response carries a Retry-After header, or a RateLimit reset header while no requests remain, the connector waits that long instead, and the retry still counts against the 5-retry budget. Each retry is logged at WARN, and the error is returned once the budget is spent.

Before decoding a response as JSON, the connector classifies the body from its Content-Type header and its first characters as JSON, HTML, text, empty, or incomplete (shorter than its Content-Length header). The classification and the HTTP status decide whether a failure is retried:

ResponseRetried
HTTP 5xx, 408, or 429, with any bodyYes
HTTP 2xx with an empty or incomplete bodyYes
HTTP 2xx whose JSON body is truncated or invalidYes
HTTP 4xx with a JSON error message that mentions a rate limitYes
Connection errors, timeouts, and errors reading the response bodyYes
HTTP 2xx with an HTML or text bodyNo
Other HTTP 4xx, including 401 and 403No
GraphQL errors returned in an HTTP 200 JSON bodyNoยน

ยน Except an error whose message contains Something went wrong while executing your query, which the GitHub GraphQL API returns when its backend times out on a query. The connector retries that error with a smaller page, as described below.

An HTML or text body on HTTP 200 usually means the endpoint URL is wrong or the request was redirected to a login page, so it fails without a retry. For a response that is not JSON, the error message names the HTTP status and body format and includes a short preview with HTML tags removed. An empty, incomplete, or invalid JSON body on HTTP 200 is retried on a new connection, because the pooled connection is likely half-closed. The GitHub GraphQL API returns an empty HTTP 200 intermittently.

A 502 or 504 response also closes the pooled connection before the retry. For a paginated query, the retry after a 502, a 504, or the GitHub backend-timeout error requests a smaller page, stepping down a reverse Fibonacci sequence (for example, 100, 55, 34, 21), so an upstream that times out on large pages can still complete the refresh.

Separately from retries, the connector's per-origin rate limiter waits before each request while a Retry-After or RateLimit reset cooldown from an earlier response is in effect. See Rate Control Parameters to limit request rate and concurrency. When a rate limit is configured, a 408, 429, or 5xx response, a connection failure, and a timeout also count as failures for adaptive rate control, which admits fewer requests to an origin whose error rate exceeds rate_control_failure_threshold.

Paginationโ€‹

The connector supports cursor-based pagination. Each page is a separate HTTP request; pagination errors mid-sequence cause the entire refresh to fail. Use json_pointer to select the row collection and configure the pagination variables to match the upstream schema's cursor fields.

Server Rate Limitsโ€‹

GraphQL APIs (GitHub, Shopify, etc.) typically enforce query-cost-based rate limits rather than request count. A rate limit reported with HTTP 429, or with a 4xx response whose JSON error message mentions a rate limit, is retried as described in Retry Behavior. A cost or rate-limit error reported in the GraphQL errors array of an HTTP 200 response fails the request without a retry. Reduce refresh frequency or narrow the query to stay within budget.

Capacity & Sizingโ€‹

  • Throughput: Bounded by the upstream rate limit, typical GraphQL endpoints cap at 100s-1000s of requests per minute.
  • Query cost: Design graphql_query to request only the fields you need. Request fewer nested fields to reduce query cost.
  • Pagination depth: Large datasets requiring hundreds of pages extend refresh duration linearly; plan refresh intervals accordingly.

Metricsโ€‹

When used as a dataset connector, GraphQL exposes per-origin HTTP rate-control metrics under the graphql component. They are registered automatically for every GraphQL dataset โ€” no metrics configuration is required โ€” and the limit gauges report 0 when the corresponding limit is not configured. Catalog components expose none:

Metric NameTypeDescription
inflight_operationsGaugeCurrent number of HTTP requests holding a rate-control permit.
rate_control_max_concurrent_requestsGaugeConfigured maximum concurrent HTTP requests for this upstream origin; 0 means disabled.
rate_control_requests_per_second_limitGaugeConfigured HTTP request-per-second limit for this upstream origin; 0 means disabled.
rate_control_requests_per_minute_limitGaugeConfigured HTTP request-per-minute limit for this upstream origin; 0 means disabled.
rate_control_jitter_min_msGaugeConfigured minimum rate-control jitter (ms) before HTTP requests.
rate_control_jitter_max_msGaugeConfigured maximum rate-control jitter (ms) before HTTP requests.
rate_control_available_permitsGaugeCurrent available permits in the HTTP request concurrency semaphore; 0 when concurrency is disabled.
rate_control_acquisitions_totalCounterTotal HTTP request rate-control permits acquired.
rate_control_acquire_errors_totalCounterTotal HTTP request rate-control permit acquisition errors.
rate_control_wait_duration_msCounterCumulative time (ms) spent waiting for HTTP rate-control permits, quotas, and jitter.
rate_limit_retry_after_updates_totalCounterTotal upstream cooldown hints accepted from Retry-After or RateLimit reset headers.
rate_limit_retry_after_waits_totalCounterTotal waits caused by Retry-After or RateLimit reset headers.
rate_limit_retry_after_wait_duration_msCounterCumulative time (ms) spent waiting because of Retry-After or RateLimit reset headers.
rate_limit_retry_after_remaining_msGaugeCurrent remaining Retry-After / RateLimit cooldown (ms) for this upstream origin.
rate_control_adaptive_admission_ratioGaugeShare of the configured limits that adaptive rate control currently admits for this upstream origin; 1 admits the full limits. Absent when the origin has no limit configured.
rate_control_adaptive_throttled_totalCounterTotal requests that adaptive rate control throttled because the origin was failing. Absent when the origin has no limit configured.

These metrics are auto-registered โ€” no configuration is required to export them. To turn one off for a dataset, set enabled: false in the dataset's metrics section:

datasets:
- from: graphql:https://api.example.com/graphql
name: api_data
metrics:
- name: rate_control_wait_duration_ms
enabled: false

Instruments are exposed with the prefix dataset_graphql_, and each carries an origin attribute (scheme://host:port) identifying the upstream origin instead of a dataset name, because datasets sharing an origin share one rate controller. See Component Metrics for general configuration.

For broader observability, also monitor:

  • Spice query execution metrics (query_duration_ms, query_returned_rows, query_failures) from runtime.metrics.
  • The upstream GraphQL provider's rate-limit dashboards.

Task Historyโ€‹

GraphQL requests participate in task history through the HTTP client's span. Each page fetch is a child of the enclosing sql_query or acceleration_refresh task.

Known Limitationsโ€‹

  • Read-only: Only GraphQL queries (not mutations or subscriptions) are supported.
  • Single query per dataset: Each dataset is one GraphQL query. Multi-query datasets require separate dataset definitions.
  • Schema inference: The connector infers schema from the first response; schemas with deeply-nested optional fields may require an explicit dataset schema override.
  • Batching: GraphQL query batching (multiple operations in one HTTP request) is not exposed.

Troubleshootingโ€‹

SymptomLikely causeResolution
401 UnauthorizedWrong or expired token in graphql_auth_token.Rotate the token; verify the header format (Bearer prefix, etc.).
Rows missing from the datasetWrong json_pointer.Inspect the response payload; JSON pointer must navigate to the array of rows.
Refresh fails mid-paginationRate-limit or transient network failure.Transient errors are retried up to 5 times per page before the refresh fails. Reduce refresh frequency or narrow the query.
returned HTML instead of JSON (HTTP 200 OK)Wrong endpoint URL, or a redirect to a login page.Verify the dataset from URL points at the GraphQL endpoint and that authentication is set.
upstream returned an empty response body (HTTP 200)The endpoint returned an empty HTTP 200 on every try.Check the upstream service status, then retry the refresh.
Query cost exceededQuery requests too many nested fields.Simplify the query; fetch only required fields.
Inferred schema differs between refreshesOptional fields appear/disappear in responses.Provide an explicit dataset schema to lock down types.