> For the complete documentation index, see [llms.txt](https://docs.graphlit.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.graphlit.dev/api-guides/use-cases/content/content-query-performance-patterns.md).

# Query Performance Patterns

## Content: Query Performance Patterns

### User Intent

"How do I optimize content queries for production?"

### Operation

* **SDK Method**: `queryContents()`
* **GraphQL**: `queryContents` query
* **Entity Type**: Content
* **Common Use Cases**: Performance optimization, pagination, large result sets, production patterns

### Performance Overview

Query performance depends on query type, filters used, result size, and indexing. Understanding these patterns helps optimize for production.

### TypeScript (Canonical)

```typescript
import { Graphlit } from 'graphlit-client';
import { ContentTypes, ObservableTypes, SearchTypes } from 'graphlit-client/dist/generated/graphql-types';

const graphlit = new Graphlit();

// Efficient pagination
const page1 = await graphlit.queryContents({
  search: "query",
  limit: 50,
  offset: 0
});

const page2 = await graphlit.queryContents({
  search: "query",
  limit: 50,
  offset: 50
});

// Use indexed filters for speed
const fast = await graphlit.queryContents({
  search: "machine learning",
  
    types: [ContentTypes.File],           // Indexed
    creationDateRange: { from: '2024-01-01' },  // Indexed
    collections: [{ id: 'collection-id' }]      // Indexed
  
  limit: 20  // Reasonable limit
});

// Narrow scope before querying
const scoped = await graphlit.queryContents({
  
    types: [ContentTypes.Email],
    feeds: [{ id: 'specific-feed' }],  // Narrow to one feed
    createdInLast: 'P7D'               // Recent only
  });
```

### Query Performance Characteristics

#### Fast Queries (<50ms)

```typescript
// 1. Keyword search with indexed filters
const fast1 = await graphlit.queryContents({
  search: "PROJ-1234",
  searchType: SearchTypes.Keyword,
  
    types: [ContentTypes.File],
    creationDateRange: { from: '2024-01-01' }
  
  limit: 20
});

// 2. Metadata-only filter (no search)
const fast2 = await graphlit.queryContents({
  
    types: [ContentTypes.Email],
    feeds: [{ id: 'feed-id' }],
    createdInLast: 'P7D'
  });

// 3. Get by ID (single result)
const fast3 = await graphlit.getContent('content-id');
```

#### Medium Queries (50-200ms)

```typescript
// 1. Vector search
const medium1 = await graphlit.queryContents({
  search: "machine learning applications",
  searchType: SearchTypes.Vector,
  limit: 20
});

// 2. Hybrid search (default)
const medium2 = await graphlit.queryContents({
  search: "AI research",
  searchType: SearchTypes.Hybrid,
  limit: 20
});

// 3. Large result sets
const medium3 = await graphlit.queryContents({
  
    types: [ContentTypes.File]
  
  limit: 100
});
```

#### Slow Queries (200ms+)

```typescript
// 1. Entity filters (graph queries)
const slow1 = await graphlit.queryContents({
  
    observations: [{
      type: ObservableTypes.Person,
      observable: { id: 'person-id' }
    }]
  });

// 2. No filters (full scan)
const slow2 = await graphlit.queryContents({
  limit: 1000  // Large limit
});

// 3. Complex multi-entity filters
const slow3 = await graphlit.queryContents({
  
    observations: [
      { type: ObservableTypes.Person, observable: { id: 'p1' } },
      { type: ObservableTypes.Person, observable: { id: 'p2' } }
    ]
  });
```

### Pagination Best Practices

#### Offset-Based Pagination

```typescript
// Page size
const pageSize = 50;

// Get page function
async function getPage(pageNumber: number) {
  return await graphlit.queryContents({
    search: "query",
    limit: pageSize,
    offset: pageNumber * pageSize
  });
}

// Usage
const page1 = await getPage(0);  // First page
const page2 = await getPage(1);  // Second page
const page3 = await getPage(2);  // Third page
```

#### Progressive Loading

```typescript
// Load results progressively
async function loadAllResults(query: string) {
  const pageSize = 50;
  let offset = 0;
  let allResults: any[] = [];
  let hasMore = true;
  
  while (hasMore) {
    const page = await graphlit.queryContents({
      search: query,
      limit: pageSize,
      offset: offset
    });
    
    allResults = allResults.concat(page.contents.results);
    offset += pageSize;
    
    // Stop if we got fewer results than page size
    hasMore = page.contents.results.length === pageSize;
    
    console.log(`Loaded ${allResults.length} results so far...`);
  }
  
  return allResults;
}
```

#### Limit Best Practices

```typescript
// ✓ Good limits
limit: 10   // UI: Quick preview
limit: 20   // UI: Search results page
limit: 50   // API: Reasonable batch
limit: 100  // API: Large batch (use with caution)

// ✗ Avoid
limit: 1000  // Too large, slow queries
limit: 10000 // Will timeout
```

### Caching Strategies

#### Client-Side Result Caching

```typescript
// Simple in-memory cache
const queryCache = new Map<string, any>();

async function cachedQuery(query: string, ttlMs: number = 60000) {
  const cacheKey = `query:${query}`;
  const cached = queryCache.get(cacheKey);
  
  if (cached && Date.now() - cached.timestamp < ttlMs) {
    console.log('Cache hit');
    return cached.results;
  }
  
  console.log('Cache miss');
  const results = await graphlit.queryContents({ search: query });
  
  queryCache.set(cacheKey, {
    results,
    timestamp: Date.now()
  });
  
  return results;
}

// Usage
const results = await cachedQuery("machine learning", 60000);  // 1 minute TTL
```

#### Query Fingerprinting

```typescript
// Cache by query fingerprint
function getQueryFingerprint(params: any): string {
  return JSON.stringify({
    search: params.search,
    filter: params.filter,
    searchType: params.searchType,
    limit: params.limit,
    offset: params.offset
  });
}

const cache = new Map<string, { results: any; timestamp: number }>();

async function cachedQueryByFingerprint(params: any, ttlMs: number = 60000) {
  const fingerprint = getQueryFingerprint(params);
  const cached = cache.get(fingerprint);
  
  if (cached && Date.now() - cached.timestamp < ttlMs) {
    return cached.results;
  }
  
  const results = await graphlit.queryContents(params);
  cache.set(fingerprint, { results, timestamp: Date.now() });
  
  return results;
}
```

### Indexed vs Non-Indexed Fields

#### Indexed (Fast)

```typescript
// These filters are indexed and fast:
const fast = await graphlit.queryContents({
  
    types: [...],                    // ✓ Indexed
    fileTypes: [...],                // ✓ Indexed
    creationDateRange: {...},        // ✓ Indexed
    dateRange: {...},                // ✓ Indexed
    feeds: [...],                    // ✓ Indexed
    collections: [...],              // ✓ Indexed
    workflows: [...],                // ✓ Indexed
    states: [...],                   // ✓ Indexed
    fileExtensions: [...],           // ✓ Indexed
    formats: [...],                  // ✓ Indexed
    fileSizeRange: {...}             // ✓ Indexed
  });
```

#### Non-Indexed (Slower)

```typescript
// Entity filters require graph database lookup:
const slower = await graphlit.queryContents({
  
    observations: [...]              // ✗ Graph query (slower)
  });

// Custom metadata not indexed:
// Can't filter directly on custom metadata fields
// Must query all and filter client-side
```

### Optimization Patterns

#### Pattern 1: Filter Before Search

```typescript
// ✓ Good: Narrow scope with filters first
const optimized = await graphlit.queryContents({
  search: "query",
  
    collections: [{ id: 'small-collection' }],  // Reduce search space
    createdInLast: 'P30D'                       // Recent only
  });

// ✗ Less optimal: Search everything
const slow = await graphlit.queryContents({
  search: "query"
  // No filters = searches all content
});
```

#### Pattern 2: Use Appropriate Search Type

```typescript
// For exact matching: use keyword (fastest)
const exact = await graphlit.queryContents({
  search: "PROJ-1234",
  searchType: SearchTypes.Keyword  // Fast
});

// For concepts: use vector
const semantic = await graphlit.queryContents({
  search: "machine learning applications",
  searchType: SearchTypes.Vector
});

// For general queries: use hybrid (default, balanced)
const balanced = await graphlit.queryContents({
  search: "AI research papers"
  // Hybrid is default
});
```

#### Pattern 3: Batch Operations

```typescript
// Get multiple content items efficiently
async function batchGetContent(ids: string[]) {
  // Parallel requests
  const results = await Promise.all(
    ids.map(id => graphlit.getContent(id))
  );
  return results;
}

// Better than sequential
// for (const id of ids) {
//   await graphlit.getContent(id);  // Slow!
// }
```

#### Pattern 4: Count vs Results

```typescript
// If you only need count, don't fetch full results
const results = await graphlit.queryContents({
   types: [ContentTypes.Email] 
  limit: 1  // Minimal fetch
});

const count = results.contents.results.length;
// Use count for display, not actual results
```

## Pagination

page1 = await graphlit.queryContents( search="query", limit=50, offset=0 )

page2 = await graphlit.queryContents( search="query", limit=50, offset=50 )

## Optimized query

fast = await graphlit.queryContents( search="machine learning", filter=ContentFilterInput( types=\[ContentTypes.File], creation\_date\_range=DateRangeInput( from\_='2024-01-01' ) ), limit=20 )

````

**C#**:
```csharp
using Graphlit;

var client = new Graphlit();

// Pagination
var page1 = await graphlit.QueryContents(new ContentFilter
{
    Search = "query",
    Limit = 50,
    Offset = 0
});

var page2 = await graphlit.QueryContents(new ContentFilter
{
    Search = "query",
    Limit = 50,
    Offset = 50
});

// Optimized query
var fast = await graphlit.QueryContents(new ContentFilter
{
    Search = "machine learning",
    Filter = new ContentCriteria
    {
        Types = new[] { ContentTypes.File },
        CreationDateRange = new DateRange { From = "2024-01-01" }
    },
    Limit = 20
});
````

### Developer Hints

#### Measure Query Performance

```typescript
async function measureQuery(queryFn: () => Promise<any>) {
  const start = Date.now();
  const results = await queryFn();
  const elapsed = Date.now() - start;
  
  console.log(`Query time: ${elapsed}ms`);
  console.log(`Results: ${results.contents.results.length}`);
  console.log(`Ms per result: ${(elapsed / results.contents.results.length).toFixed(2)}`);
  
  return results;
}

// Usage
await measureQuery(() => graphlit.queryContents({ search: "query" }));
```

#### Parallelize Independent Queries

```typescript
// ✓ Parallel (fast)
const [emails, messages, files] = await Promise.all([
  graphlit.queryContents({  types: [ContentTypes.Email] }),
  graphlit.queryContents({  types: [ContentTypes.Message] }),
  graphlit.queryContents({  types: [ContentTypes.File] })
]);

// ✗ Sequential (slow)
const emails = await graphlit.queryContents({  types: [ContentTypes.Email] });
const messages = await graphlit.queryContents({  types: [ContentTypes.Message] });
const files = await graphlit.queryContents({  types: [ContentTypes.File] });
```

#### Use Reasonable Limits

```typescript
// ✓ Good: Reasonable page size
const results = await graphlit.queryContents({
  search: "query",
  limit: 50  // Good balance
});

// ✗ Bad: Too large
const huge = await graphlit.queryContents({
  search: "query",
  limit: 1000  // Will be slow
});
```

### Common Issues & Solutions

**Issue**: Queries timing out **Solution**: Reduce scope with filters and limits

```typescript
// ✗ Too broad
await graphlit.queryContents({ limit: 1000 });

// ✓ Narrowed
await graphlit.queryContents({
  
    collections: [{ id: 'collection-id' }],
    createdInLast: 'P7D'
  
  limit: 50
});
```

**Issue**: Slow entity-based queries **Solution**: Expected behavior, optimize where possible

```typescript
// Entity queries are slower (graph lookup)
// Optimize by combining with other filters
const optimized = await graphlit.queryContents({
  
    types: [ContentTypes.Email],      // Narrow first
    createdInLast: 'P30D',                  // Then narrow more
    observations: [{                         // Then entity filter
      type: ObservableTypes.Person,
      observable: { id: 'person-id' }
    }]
  });
```

**Issue**: Need all results but hitting limits **Solution**: Use pagination

```typescript
async function getAllResults(filter: any) {
  const pageSize = 50;
  let allResults: any[] = [];
  let offset = 0;
  let hasMore = true;
  
  while (hasMore) {
    const page = await graphlit.queryContents({
      filter,
      limit: pageSize,
      offset
    });
    
    allResults.push(...page.contents.results);
    hasMore = page.contents.results.length === pageSize;
    offset += pageSize;
  }
  
  return allResults;
}
```

### Production Example

```typescript
class ContentQueryService {
  private cache = new Map<string, { data: any; timestamp: number }>();
  
  constructor(private client: Graphlit) {}
  
  // Optimized query with caching
  async query(params: {
    search?: string;
    filter?: any;
    limit?: number;
    offset?: number;
    useCache?: boolean;
    cacheTTL?: number;
  }) {
    const {
      search,
      filter,
      limit = 20,
      offset = 0,
      useCache = true,
      cacheTTL = 60000
    } = params;
    
    // Generate cache key
    const cacheKey = JSON.stringify({ search, filter, limit, offset });
    
    // Check cache
    if (useCache) {
      const cached = this.cache.get(cacheKey);
      if (cached && Date.now() - cached.timestamp < cacheTTL) {
        console.log('✓ Cache hit');
        return cached.data;
      }
    }
    
    // Measure performance
    const start = Date.now();
    
    // Execute query
    const results = await this.graphlit.queryContents({
      search,
      filter,
      limit,
      offset
    });
    
    const elapsed = Date.now() - start;
    console.log(`Query: ${elapsed}ms, Results: ${results.contents.results.length}`);
    
    // Cache results
    if (useCache) {
      this.cache.set(cacheKey, {
        data: results,
        timestamp: Date.now()
      });
    }
    
    return results;
  }
  
  // Paginated query
  async queryPaginated(params: {
    search?: string;
    filter?: any;
    pageSize?: number;
  }) {
    const { search, filter, pageSize = 50 } = params;
    
    let page = 0;
    
    return {
      async next() {
        const results = await this.query({
          search,
          filter,
          limit: pageSize,
          offset: page * pageSize
        });
        
        page++;
        
        return {
          results: results.contents.results,
          hasMore: results.contents.results.length === pageSize
        };
      }
    };
  }
  
  // Clear cache
  clearCache() {
    this.cache.clear();
  }
}

// Usage
const service = new ContentQueryService(client);

// Cached query
const results = await service.query({
  search: "machine learning",
  filter: { types: [ContentTypes.File] },
  useCache: true,
  cacheTTL: 300000  // 5 minutes
});

// Paginated query
const paginator = await service.queryPaginated({
  search: "AI research",
  pageSize: 50
});

let page1 = await paginator.next();
let page2 = await paginator.next();
```
