> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sensorup.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Performance & Best Practices

> Learn how to optimize GraphQL queries, improve performance, and follow best practices for the SensorUp API.

# Performance & Best Practices

This guide covers strategies for optimizing your use of the SensorUp GraphQL API to achieve the best performance and reliability.

## Query Optimization

### Request Only What You Need

GraphQL's strength is precise field selection. Use it:

**Bad:**

```graphql theme={null}
query {
  assets {
    all(first: 100) {
      edges {
        node {
          id
          profile
          displayName
          secondaryDisplayName
          modifiedAt
          geometry { type coordinates bbox }
          properties           # Potentially large!
          observation         # Very large!
          geoJSONFeature      # Redundant with above
          related             # Expensive!
        }
      }
    }
  }
}
```

**Good:**

```graphql theme={null}
query {
  assets {
    all(first: 100) {
      edges {
        node {
          id
          displayName
          modifiedAt
        }
      }
    }
  }
}
```

**Impact**: Reduced payload size by 90%+, faster response times

### Use Pagination Appropriately

Don't request more data than you can display:

**Bad:**

```graphql theme={null}
query {
  assets {
    all(first: 10000) {  # Too many!
      edges {
        node {
          id
          displayName
        }
      }
    }
  }
}
```

**Good:**

```graphql theme={null}
query {
  assets {
    all(first: 20) {    # One page
      edges {
        node {
          id
          displayName
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}
```

**Recommended page sizes**:

* UI lists: 20-50 items
* Background processing: 100-500 items
* Exports: Use export endpoints, not pagination

### Batch Related Queries

GraphQL allows querying multiple resources in one request:

**Bad:**

```javascript theme={null}
// 3 separate requests
const session = await getSession()
const assets = await getAssets()
const issues = await getIssues()
```

**Good:**

```javascript theme={null}
// 1 request
const result = await query(`
  query {
    session {
      username
      authenticated
    }
    assetProfiles {
      all(first: 20) {
        edges {
          node {
            profile
            title
          }
        }
      }
    }
    issues {
      all(first: 20) {
        edges {
          node {
            id
            title
          }
        }
      }
    }
  }
`)
```

**Impact**: Reduced latency by \~66%, fewer round trips

### Use Fragments for Reusability

Define reusable fragments for common field sets:

```graphql theme={null}
fragment AssetSummary on Asset {
  id
  displayName
  modifiedAt
  geometry {
    type
    coordinates
  }
}

fragment IssueSummary on Issue {
  id
  title
  status
  severity
  dueAt
}

query GetData {
  assets {
    all(first: 20) {
      edges {
        node {
          ...AssetSummary
        }
      }
    }
  }
  issues {
    all(first: 20) {
      edges {
        node {
          ...IssueSummary
        }
      }
    }
  }
}
```

## Federation Performance

### Understand Query Plans

Cross-subgraph queries require multiple fetches. Minimize hops:

**Less Efficient:**

```graphql theme={null}
query {
  issues {
    all(first: 100) {
      edges {
        node {
          subject {
            assetReference {    # Fetch from su-assets (100 times)
              asset {
                displayName
                assetProfile {   # Another fetch (100 times)
                  title
                }
              }
            }
          }
        }
      }
    }
  }
}
```

**More Efficient:**

```graphql theme={null}
query {
  issues {
    all(first: 100) {
      edges {
        node {
          id
          title
          status
        }
      }
    }
  }
}

// If you need asset details, fetch them separately with specific IDs
```

### Leverage Parallel Execution

Federation executes independent root fields in parallel:

```graphql theme={null}
query {
  session {          # su-auth - parallel
    username
  }
  assetProfiles {    # su-assets - parallel
    all(first: 10) {
      edges {
        node {
          title
        }
      }
    }
  }
  issues {           # su-issues - parallel
    all(first: 10) {
      edges {
        node {
          title
        }
      }
    }
  }
}
```

All three subgraphs are queried simultaneously.

## Filtering & Searching

### Use Server-Side Filtering

Filter on the server, not the client:

**Bad:**

```javascript theme={null}
// Fetch everything, filter client-side
const all = await getAssets({ first: 1000 })
const filtered = all.filter(a => a.modifiedAt > cutoff)
```

**Good:**

```javascript theme={null}
// Filter server-side
const filtered = await getAssets({
  first: 100,
  filter: { modifiedAfter: cutoff }
})
```

### Specific Lookups vs. List Filtering

Use ID lookups when you know the ID:

**Less Efficient:**

```graphql theme={null}
query {
  assets {
    all(first: 1000, filter: { id: "asset-123" }) {
      edges {
        node {
          id
          displayName
        }
      }
    }
  }
}
```

**More Efficient:**

```graphql theme={null}
query {
  assetProfiles {
    byId(profile: "wells") {
      assetById(id: "asset-123") {
        id
        displayName
      }
    }
  }
}
```

## Caching Strategies

### Cache Static Data

Some data changes infrequently:

| Data Type          | Change Frequency | Cache Duration            |
| ------------------ | ---------------- | ------------------------- |
| Asset Profiles     | Rarely           | 1 hour - 1 day            |
| Catalog Entries    | Rarely           | 1 day - 1 week            |
| Form Templates     | Occasionally     | 1 hour                    |
| Map Configurations | Rarely           | 1 day                     |
| User Session       | Often            | No cache (always fetch)   |
| Assets             | Often            | No cache or short (5 min) |

### HTTP Caching

Configure appropriate cache headers:

```javascript theme={null}
const response = await fetch(url, {
  headers: {
    'Cache-Control': 'max-age=300' // 5 minutes
  }
})
```

### Application-Level Caching

Use a caching client like Apollo Client:

```javascript theme={null}
import { ApolloClient, InMemoryCache } from '@apollo/client'

const client = new ApolloClient({
  uri: 'https://customer-demo.sensorup.com/api/graphql',
  cache: new InMemoryCache({
    typePolicies: {
      AssetProfile: {
        keyFields: ['profile']
      },
      Asset: {
        keyFields: ['profile', 'id']
      }
    }
  })
})
```

## Rate Limiting & Throttling

### Implement Exponential Backoff

When encountering rate limits or errors:

```javascript theme={null}
async function fetchWithRetry(query, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fetch(query)
    } catch (error) {
      if (i === maxRetries - 1) throw error

      const delay = Math.min(1000 * Math.pow(2, i), 10000)
      await new Promise(resolve => setTimeout(resolve, delay))
    }
  }
}
```

### Batch Updates

Instead of many small mutations, batch when possible:

**Less Efficient:**

```javascript theme={null}
for (const issue of issues) {
  await updateIssue(issue.id, { status: 'CLOSED' })
}
```

**More Efficient:**

```javascript theme={null}
await bulkUpdateIssues({
  filter: { issueIds: issues.map(i => i.id) },
  updates: { status: 'CLOSED' }
})
```

## Query Complexity

### Avoid Deep Nesting

Deep queries can be expensive:

**High Complexity:**

```graphql theme={null}
query {
  sites {
    all {
      assets {
        all {
          issueSubject {
            issues {
              all {
                annotations {
                  all {
                    # Very expensive!
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

**Lower Complexity:**

```graphql theme={null}
query {
  sites {
    all(first: 20) {
      edges {
        node {
          id
          name
        }
      }
    }
  }
}

# Then fetch assets for specific sites
# Then fetch issues for specific assets
```

### Limit List Sizes

Always use pagination limits:

```graphql theme={null}
query {
  assets {
    all(first: 20) {  # Always specify!
      edges {
        node {
          id
        }
      }
    }
  }
}
```

## Monitoring & Debugging

### Use Correlation IDs

Include correlation IDs for tracking:

```javascript theme={null}
const correlationId = uuid()

await mutation({
  variables: {
    input: { ... },
    correlationId
  }
})

console.log(`Request ID: ${correlationId}`)
```

### Log Query Performance

Track slow queries:

```javascript theme={null}
const start = Date.now()
const result = await client.query({ query })
const duration = Date.now() - start

if (duration > 1000) {
  console.warn(`Slow query: ${duration}ms`, query)
}
```

### Monitor Error Rates

Track and alert on error patterns:

```javascript theme={null}
let errorCount = 0
let requestCount = 0

try {
  await query()
  requestCount++
} catch (error) {
  errorCount++
  requestCount++

  const errorRate = errorCount / requestCount
  if (errorRate > 0.05) {
    alert('High error rate detected!')
  }
}
```

## Network Optimization

### Use Compression

Enable gzip/brotli compression:

```javascript theme={null}
fetch(url, {
  headers: {
    'Accept-Encoding': 'gzip, deflate, br'
  }
})
```

### Minimize Payload Size

* Use fragments to avoid duplication
* Omit `__typename` if not needed
* Use aliases to simplify response structure

### Connection Pooling

Reuse HTTP connections:

```javascript theme={null}
const agent = new http.Agent({
  keepAlive: true,
  maxSockets: 50
})

fetch(url, { agent })
```

## Best Practices Checklist

### Query Design

* [ ] Request only needed fields
* [ ] Use pagination (first: 20-100)
* [ ] Apply server-side filters
* [ ] Use specific lookups vs. list queries
* [ ] Batch related queries in one request
* [ ] Use fragments for reusability

### Performance

* [ ] Cache static data (profiles, catalogs)
* [ ] Implement exponential backoff
* [ ] Monitor query performance
* [ ] Use correlation IDs
* [ ] Minimize cross-subgraph hops
* [ ] Limit query depth (≤ 4 levels)

### Error Handling

* [ ] Check GraphQL-level errors
* [ ] Check mutation-level errors
* [ ] Log errors with context
* [ ] Implement retry logic
* [ ] Handle partial failures gracefully

### Security

* [ ] Store credentials securely
* [ ] Rotate API keys regularly
* [ ] Use HTTPS always
* [ ] Validate input data
* [ ] Sanitize user-provided content

## Performance Metrics

### Target Metrics

| Metric              | Target   | Good     | Needs Improvement |
| ------------------- | -------- | -------- | ----------------- |
| Query latency (p50) | \< 200ms | \< 500ms | > 500ms           |
| Query latency (p99) | \< 1s    | \< 2s    | > 2s              |
| Error rate          | \< 0.1%  | \< 1%    | > 1%              |
| Cache hit rate      | > 80%    | > 60%    | \< 60%            |

### Measuring Performance

```javascript theme={null}
const metrics = {
  queryCount: 0,
  errorCount: 0,
  totalDuration: 0,
  cacheHits: 0
}

function recordQuery(duration, fromCache, error) {
  metrics.queryCount++
  metrics.totalDuration += duration
  if (fromCache) metrics.cacheHits++
  if (error) metrics.errorCount++
}

function getMetrics() {
  return {
    avgDuration: metrics.totalDuration / metrics.queryCount,
    errorRate: metrics.errorCount / metrics.queryCount,
    cacheHitRate: metrics.cacheHits / metrics.queryCount
  }
}
```

## Common Anti-Patterns

### 1. Over-fetching

```graphql theme={null}
# Don't fetch everything "just in case"
query {
  assets {
    all(first: 100) {
      edges {
        node {
          # Every possible field...
        }
      }
    }
  }
}
```

### 2. N+1 in Application Code

```javascript theme={null}
// Don't do this!
const issues = await getIssues()
for (const issue of issues) {
  const asset = await getAsset(issue.assetId)  // N queries!
}
```

### 3. Polling Too Frequently

```javascript theme={null}
// Don't poll every second!
setInterval(async () => {
  await fetchData()
}, 1000)

// Use reasonable intervals
setInterval(async () => {
  await fetchData()
}, 30000)  // 30 seconds
```

### 4. Ignoring Errors

```javascript theme={null}
// Don't ignore errors!
try {
  await mutation()
} catch (e) {
  // Silence!
}

// Handle them properly
try {
  await mutation()
} catch (error) {
  console.error('Mutation failed:', error)
  showErrorToUser(error.message)
  reportToMonitoring(error)
}
```

## Resources

* **[Federation Concepts](./federation)** - Understanding query planning
* **[Common Patterns](./common-patterns)** - API patterns and conventions
* **[Apollo Client Performance](https://www.apollographql.com/docs/react/performance/performance/)** - Client-side optimization
* **[GraphQL Best Practices](https://graphql.org/learn/best-practices/)** - General GraphQL guidelines

## Support

For performance issues or optimization assistance:

* Review query plans in Apollo Studio
* Contact your SensorUp account team
* Provide correlation IDs for specific slow queries
