# Best Practices

## 1. Batch Size Recommendations

* Keep batches to a maximum of 50 mutations to avoid timeouts.
* Instead of one large batch with 50+ mutations, split into smaller batches of mutations each.

```graphql
mutation SmallBatch1 {
  op1: productCreateSimple(input: {...}) { __typename }
  op2: productCreateSimple(input: {...}) { __typename }
  op3: productCreateSimple(input: {...}) { __typename }
  op4: productCreateSimple(input: {...}) { __typename }
  op5: productCreateSimple(input: {...}) { __typename }
}
mutation SmallBatch2 {
  op6: productCreateSimple(input: {...}) { __typename }
  op7: productCreateSimple(input: {...}) { __typename }
  # ... continue with next batch
}
```

* Focus on single resource operations per batch.

```graphql
mutation BatchedMutations {
  create: productCreateSimple(
    input: {sku: "new_product", templateCode: "template"}
  ) {
    __typename
  }
  assignDescription: productAddAttributeValueTranslationsTextarea(
    input: {sku: "new_product", attributeCode: "description", translations: [{value: "Long description", language: "en_GB"}]}
  ) {
    __typename
  }
  assignShortDescription: productAddAttributeValueTranslationsTextarea(
    input: {sku: "new_product", attributeCode: "short_description", translations: [{value: "Short description", language: "en_GB"}]}
  ) {
    __typename
  }
}
```

* Consider the synchronous execution nature.

## 2. Error Handling Strategy

* Each mutation in a batch can succeed/fail independently, with code 200 - read responses for error information.
* Use \_\_typename for minimal successful response validation.
* Implement retry logic for failed operations.

## 3. Aliasing for Duplicate Mutations

```graphql
mutation {
  first: productAddAttributeValueTranslationsText(input: {...}) { __typename }
  second: productAddAttributeValueTranslationsText(input: {...}) { __typename }
  # Aliases required for duplicate mutation types
}
```

## 4. Efficient Data Flow

* Use streams for bulk data reading.

```graphql
query {
  productStream(first: 50, after:"<endCursor>") {
    pageInfo {
      hasNextPage
      endCursor
    }
    edges {
      node {
        sku
        attributeList {
          edges {
            node {
              attribute {
                code
              }
            }
          }
        }
      }
    }
  }
}
```

* Minimize response payload size.
* Leverage cursor-based pagination.

## 5. Input Object Optimization

* Structure your input objects efficiently by grouping related fields.

```graphql
mutation OptimizedAttributeAssignment {
  assignText: productAddAttributeValueTranslationsText(input: {
    sku: "product_sku"
    attributeCode: "name"
    translations: [
      {value: "English Name", language: "en_GB"},
      {value: "Polish Name", language: "pl_PL"},
      {value: "German Name", language: "de_DE"}
    ]
  }) {
    __typename
  }
}
```

## 6. API Keys

### Securing Ergonode PIM API Keys

<table data-full-width="false"><thead><tr><th width="146.2265625" align="center">Area</th><th align="center">Best practice</th><th align="center">Why it matters</th><th align="center">How to implement</th></tr></thead><tbody><tr><td align="center">Secret storage</td><td align="center">Use environment variables or a managed secrets manager</td><td align="center">Prevents leaks from code/repos and enables RBAC, audit, and encryption</td><td align="center">Inject ERGONODE_API_KEY at runtime from AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, or Vault; never commit .env files</td></tr><tr><td align="center">No hardcoding</td><td align="center">Never embed keys in code, front-end, mobile, or docs</td><td align="center">Hardcoded keys are easily leaked via repos, builds, or source maps</td><td align="center">Enable secret scanners in CI and pre-commit; block pushes with detected patterns; review historical commits periodical</td></tr><tr><td align="center">Least privilege</td><td align="center">Scope keys per environment and integration with minimal permissions</td><td align="center">Limits blast radius and reduces misuse risk</td><td align="center">Separate keys for dev/stage/prod and for ERP sync, ecommerce export, DAM/analytics; prefer read-only for exports</td></tr><tr><td align="center">Network restrictions</td><td align="center">Restrict by IP/VPC/hostname where supported</td><td align="center">Stops use from unexpected networks</td><td align="center">Allowlist egress IPs of integration services or gateways calling Ergonode</td></tr><tr><td align="center">Server-side usage</td><td align="center">Keep keys off client devices and browsers</td><td align="center">Client apps are untrusted; keys can be extracted</td><td align="center">Use a backend/edge proxy to call Ergonode; clients receive only necessary data</td></tr><tr><td align="center">Encryption</td><td align="center">Enforce TLS and encrypt at rest (including backups)</td><td align="center">Protects keys in transit and storage</td><td align="center">HTTPS-only to Ergonode; use KMS-backed secret stores and encrypted volumes/backups</td></tr><tr><td align="center">Centralization &#x26; ownership</td><td align="center">Single source of truth with clear ownership</td><td align="center">Avoids duplication, drift, and orphaned secrets</td><td align="center">Maintain a key inventory: owner, purpose, scope, environment, creation/expiry</td></tr><tr><td align="center">Secret scanning</td><td align="center">Continuous scanning of code, logs, artifacts</td><td align="center">Finds leaks quickly</td><td align="center">Scan repos, containers, CI logs, tickets; treat any external appearance as compromise</td></tr><tr><td align="center">RBAC &#x26; audits</td><td align="center">Role-based access and regular reviews</td><td align="center">Prevents unauthorized access; removes stale keys</td><td align="center">Quarterly reviews; disable unused keys; least-privilege access to read/rotate secrets</td></tr><tr><td align="center">Logging hygiene</td><td align="center">Mask secrets in logs and traces</td><td align="center">Prevents secondary leaks</td><td align="center">Redact ERGONODE_API_KEY in app/CI logs; sanitize error payloads</td></tr></tbody></table>

### Revocation (when a key may be compromised)

<table><thead><tr><th width="143.0546875" align="center">Step</th><th align="center">Action</th><th align="center">Goal</th><th align="center">Notes</th></tr></thead><tbody><tr><td align="center">Immediate control</td><td align="center">Disable/revoke the key ASAP</td><td align="center">Stop further misuse</td><td align="center">If disable is delayed, tighten scope, IP allowlists, and quotas temporarily</td></tr><tr><td align="center">Replacement</td><td align="center">Issue a new key and distribute securely</td><td align="center">Maintain availability</td><td align="center">Update secret manager; roll out to all services; verify traffic before final revoke</td></tr><tr><td align="center">Investigation</td><td align="center">Identify exposure vector</td><td align="center">Prevent recurrence</td><td align="center">Check repos, CI/CD, artifacts, screenshots, tickets, tracing/logs, container layers</td></tr><tr><td align="center">Communication</td><td align="center">Notify stakeholders and document</td><td align="center">Coordinated response</td><td align="center">Share migration steps, timelines; capture root cause and corrective actions</td></tr></tbody></table>

### Rotation (regular, safe key changes)

<table><thead><tr><th width="127.453125" align="center">Practice</th><th align="center">Action</th><th align="center">Benefit</th><th align="center">Implementation tips</th></tr></thead><tbody><tr><td align="center">Policy</td><td align="center">Rotate on schedule (e.g., 60–90 days) and on events</td><td align="center">Limits lifetime of leaked keys</td><td align="center">Shorter lifetimes for write/high-privilege keys</td></tr><tr><td align="center">Dual-key overlap</td><td align="center">Support new+old keys during rollout</td><td align="center">Zero/minimal downtime</td><td align="center">Add new key, update consumers, validate, then retire old key</td></tr><tr><td align="center">Drills</td><td align="center">Test rotation in staging</td><td align="center">Confidence and speed</td><td align="center">Run periodic game days; verify end-to-end behavior</td></tr></tbody></table>

By combining strict key handling (no hardcoding), managed secret storage, environment and integration scoping, continuous monitoring, and disciplined revocation/rotation, Ergonode customers can protect product data, sustain uptime, and minimize the blast radius of any credential exposure.<br>

## **7. Strategies for Handling Rate Limits**

Effective rate-limit handling ensures reliability and fairness when integrating with APIs. Below are pragmatic strategies to keep traffic smooth and resilient.

### Exponential Backoff with Jitter

* **What it is**: On receiving rate-limit responses (e.g., 429), retry after increasing delays: baseDelay × 2^attempt, plus random jitter.
* **Why it works**: Spreads retries across time, avoiding thundering herds and synchronized spikes.
* **How to implement**:
  * Use capped backoff (e.g., max 60s) and a max attempts threshold.
  * Prefer “full jitter” (random between 0 and current backoff) to reduce contention.

### Queueing Requests

* **What it is**: Buffer requests and release them at a controlled pace aligned with known limits.
* **Why it works**: Smooths bursts, keeps within quotas, and avoids unnecessary failures.
* **How to implement**:
  * Use per-integration queues (stage/prod) to isolate traffic.
  * Enforce concurrency caps so parallel workers don’t exceed limits collectively.

### Combining Backoff and Queues

* **Queue first**; if the API still returns 429 or 503, apply exponential backoff with jitter.
* **Add circuit breakers**: temporarily halt dispatch if repeated rate-limit errors occur, then probe with a small number of requests.

### Adaptive Throttling

* Continuously measure success rates and latency.
* Increase send rate cautiously when error rates are low; decrease aggressively on 429/5xx spikes.
* Apply exponential decay to recent failures to react quickly.

### Stronger Retry Policy and Control

* Separate retry classes by failure domain:
  * Transport-level: DNS/TLS/connect timeouts, socket errors → retry with jitter.
  * HTTP-level: 429/503 honor Retry-After; 5xx apply jitter; 4xx no retry (except 409 on rare idempotent writes, if applicable).
  * GraphQL-level: retry only specific transient codes (RATE\_LIMITED/TIMEOUT/SERVICE\_UNAVAILABLE/INTERNAL\_ERROR/THROTTLED or extensions.retriable=true).
* Add retry budget per-call and per-process:
  * Keep max\_attempts, but also enforce a per-call deadline and a global budget to prevent retry storms.
* Include a circuit breaker:
  * If the failure ratio exceeds a threshold over a small rolling window (e.g., 50% 429/5xx in the last 20 attempts), open the breaker for a cool-off period, then half-open with probes.

By combining controlled queuing, adaptive throttling, jittered backoff, and strong observability, integrations stay within limits while maximizing throughput and reliability.

## 8. Schema-Aware Pre-Validation and Introspection Caching

* Pre-flight validation:
  * Validate that mutation/query names exist against a cached introspection schema at startup. This avoids GRAPHQL\_ERROR in hot paths (typos like productCreatdeSimple).
* Introspection cache:
  * Cache the schema and refresh periodically or on a rolling schedule.
  * Optionally add a linter for common pitfalls: required variables missing, wrong scalar names, max first exceeded, and alias missing for duplicates.
* Version pinning:
  * Snapshot the schema hash/version at deploy-time. If it changes at runtime (breaking updates), surface alerts.

## 9. Understanding and Avoiding the N+1 Problem in GraphQL

The N+1 problem is a performance pitfall that can make integrations slow and unpredictable, especially when fetching related data at scale. This guide explains the issue in practical terms and offers concrete best practices tailored for Ergonode PIM customers using the GraphQL API.

### What is the N+1 Problem? <a href="#what-is-the-n1-problem" id="what-is-the-n1-problem"></a>

* In plain terms, one request asks for a list of items (the “1”), and then the same request implicitly triggers one additional request for each item in the list (the “N”). The total work grows with the number of items retrieved.
* Example scenario: requesting a list of products and, for each product, requesting multiple related fields (e.g., attributes, categories, multimedia). If the integration retrieves these related pieces one-by-one per product, total work scales linearly with the list size, increasing latency and resource usage.

### Why it matters for Ergonode customers <a href="#why-it-matters-for-ergonode-customers" id="why-it-matters-for-ergonode-customers"></a>

* Slower syncs and exports: large product or attribute lists combined with deeply nested fields can cause long-running jobs.
* Unpredictable performance: the same query may be fast for small result sets and slow for larger ones.
* Higher operational costs: inefficient access patterns increase request counts and processing time in the integration infrastructure.
* User impact: storefronts, channels, or downstream systems, depending on timely updates, may experience delays.

### Recognizing N+1 in your integration <a href="#recognizing-n1-in-your-integration" id="recognizing-n1-in-your-integration"></a>

* Latency grows roughly with the number of items requested (doubling the list doubles the duration).
* Logs show repeated, similar data retrieval steps per item (e.g., identical lookups for related data across all products).
* Deeply nested selections on large lists produce inconsistent run times, especially when not using pagination.

### Best practices to avoid the N+1 problem <a href="#best-practices-to-avoid-the-n1-problem" id="best-practices-to-avoid-the-n1-problem"></a>

* Prefer stream-based pagination and sensible page sizes.
* Use stream queries (e.g., productStream, attributeStream, categoryStream) with first and after.
* Keep page sizes reasonable (often 50–100, per resource limits) to cap work per request.
* Always iterate using pageInfo.hasNextPage and pageInfo.endCursor to continue efficiently.

### Request only what is needed

* Select only the fields required for the use case.
* Avoid pulling heavy subtrees (e.g., large attribute lists, complete multimedia details) when not strictly necessary.

### Flatten data access where possible

* Prefer connection fields that already surface data in a list-friendly way (e.g., attributeList, variantList).
* If a workflow needs multiple related pieces, try grouping them in a single, well-structured selection per page rather than repeating similar selections across multiple small calls.

### Use batching in mutation workflows

* When updating data, batch related mutations for a single resource into one request, and keep batches focused (order matters).
* Limit batch size (aim ≤50) to reduce timeouts and make error handling predictable.
* Use aliases for duplicate mutation types to keep results clear and avoid collisions.

### Keep nested depth under control

* Avoid deep nesting over large lists. Instead of requesting many secondary lists under each product in one go, break the work into phased steps:
  * Step 1: fetch products with essential fields.
  * Step 2: for only those requiring updates, fetch specific related slices (e.g., attributes for a shortlist of SKUs).
* This keeps each request predictable and bounded.

### Leverage targeted queries for expensive fields

* If only a subset of items needs heavy data (e.g., galleries, large attribute sets), isolate that into a dedicated query for those items, not for every item on every page.
* Use codes/skus captured from the stream page to drive targeted follow-ups.

### Embrace idempotent, repeatable workflows

* Design integration steps so they can safely repeat without duplicating work, enabling confident retries with smaller, more efficient selections.
* This reduces the temptation to “fetch everything everywhere” in one pass.

### Monitor and tune

* Track average and p95/p99 durations per operation type and page size.
* Watch for depth and breadth in selections that creep up over time (e.g., new fields added to a shared fragment).
* Adjust page sizes and split queries when p95 grows.

### Practical patterns for use cases <a href="#practical-patterns-for-ergonode-use-cases" id="practical-patterns-for-ergonode-use-cases"></a>

* Continuous catalog sync:
  * Use productStream with a moderate first.
  * Select essential identifiers.
  * Run a targeted query to fetch the specific attributes or media required by the downstream system.
* Attribute-driven exports:
  * Iterate attributeStream; avoid requesting full option details unless needed.
  * If options or translations are required, fetch them for the subset of attributes referenced by the current job.
* Multimedia usage:
  * Do not fetch full multimedia objects for every product by default.
  * Resolve multimedia details only when an item requires them (e.g., newly added or updated images).

### Red flags to avoid <a href="#red-flags-to-avoid" id="red-flags-to-avoid"></a>

* Selecting the full attributeList for every product in a large page when only a few attributes are actually needed.
* Fetching full multimedia details for all products, regardless of whether the images have changed.
* Deep nested fields under large lists without pagination at each level.

### A simple decision checklist <a href="#a-simple-decision-checklist" id="a-simple-decision-checklist"></a>

* Is the selection pulling large nested data for every item? If yes, try splitting into phases.
* Can the result be paginated at the top level? If not, can the nested lists be paginated or reduced?
* Are only a few fields truly needed? Remove or defer the rest.
* Does each page’s work look proportional and bounded? If not, lower first or split the query.

By structuring queries around streams and pagination, limiting nested selections, and targeting detailed lookups only when necessary, Ergonode customers can avoid the N+1 problem and keep integrations fast, reliable, and cost-effective.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ergonode.com/graphql/guides/best-practices.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
