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
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:
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:
query {
assets {
all(first: 100) {
edges {
node {
id
displayName
modifiedAt
}
}
}
}
}
Impact: Reduced payload size by 90%+, faster response times
Don’t request more data than you can display:
Bad:
query {
assets {
all(first: 10000) { # Too many!
edges {
node {
id
displayName
}
}
}
}
}
Good:
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
GraphQL allows querying multiple resources in one request:
Bad:
// 3 separate requests
const session = await getSession()
const assets = await getAssets()
const issues = await getIssues()
Good:
// 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:
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
}
}
}
}
}
Understand Query Plans
Cross-subgraph queries require multiple fetches. Minimize hops:
Less Efficient:
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:
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:
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:
// Fetch everything, filter client-side
const all = await getAssets({ first: 1000 })
const filtered = all.filter(a => a.modifiedAt > cutoff)
Good:
// 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:
query {
assets {
all(first: 1000, filter: { id: "asset-123" }) {
edges {
node {
id
displayName
}
}
}
}
}
More Efficient:
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:
const response = await fetch(url, {
headers: {
'Cache-Control': 'max-age=300' // 5 minutes
}
})
Application-Level Caching
Use a caching client like Apollo Client:
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:
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:
for (const issue of issues) {
await updateIssue(issue.id, { status: 'CLOSED' })
}
More Efficient:
await bulkUpdateIssues({
filter: { issueIds: issues.map(i => i.id) },
updates: { status: 'CLOSED' }
})
Query Complexity
Avoid Deep Nesting
Deep queries can be expensive:
High Complexity:
query {
sites {
all {
assets {
all {
issueSubject {
issues {
all {
annotations {
all {
# Very expensive!
}
}
}
}
}
}
}
}
}
}
Lower Complexity:
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:
query {
assets {
all(first: 20) { # Always specify!
edges {
node {
id
}
}
}
}
}
Monitoring & Debugging
Use Correlation IDs
Include correlation IDs for tracking:
const correlationId = uuid()
await mutation({
variables: {
input: { ... },
correlationId
}
})
console.log(`Request ID: ${correlationId}`)
Track slow queries:
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:
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:
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:
const agent = new http.Agent({
keepAlive: true,
maxSockets: 50
})
fetch(url, { agent })
Best Practices Checklist
Query Design
Error Handling
Security
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% |
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
# Don't fetch everything "just in case"
query {
assets {
all(first: 100) {
edges {
node {
# Every possible field...
}
}
}
}
}
2. N+1 in Application Code
// 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
// Don't poll every second!
setInterval(async () => {
await fetchData()
}, 1000)
// Use reasonable intervals
setInterval(async () => {
await fetchData()
}, 30000) // 30 seconds
4. Ignoring Errors
// 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
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