Troubleshooting - Module 7: Pagination & Result Shaping¶
Common Pagination Issues¶
Issue 1: Poor Performance with Large Skip Values¶
Symptoms: - Slow response times when accessing later pages - Timeouts on deep pagination requests - Increasing latency as page number increases
Causes: - Using skip/top pagination with large skip values - Azure AI Search must process all skipped results internally
Solutions:
# Problem: Deep pagination with skip
GET /indexes/hotels/docs?search=*&$skip=50000&$top=20
# Solution 1: Range-based pagination
GET /indexes/hotels/docs?search=*&$filter=hotelId gt 'last_seen_id'&$orderby=hotelId&$top=20
# Solution 2: Search after pattern
GET /indexes/hotels/docs?search=*&$orderby=rating desc,hotelId&searchAfter=4.5,hotel_123&$top=20
Prevention: - Limit maximum skip values (< 10,000) - Use range-based pagination for large datasets - Implement search after pattern for deep pagination
Issue 2: Inconsistent Results During Pagination¶
Symptoms: - Documents appearing on multiple pages - Missing documents between pages - Different total counts on subsequent requests
Causes: - Concurrent index updates during pagination - Using skip/top without stable sorting
Solutions:
# Problem: Unstable pagination
GET /indexes/hotels/docs?search=*&$skip=20&$top=10
# Solution: Add stable sorting
GET /indexes/hotels/docs?search=*&$orderby=hotelId&$skip=20&$top=10
# Better: Use range-based pagination
GET /indexes/hotels/docs?search=*&$filter=hotelId gt 'hotel_020'&$orderby=hotelId&$top=10
Prevention: - Always include stable sorting fields - Use range-based pagination for consistency - Consider timestamp-based filtering for real-time data
Issue 3: Pagination Parameters Not Working¶
Symptoms: - Skip parameter ignored - Top parameter not limiting results - Unexpected result counts
Causes: - Incorrect parameter syntax - Parameter value limits exceeded - URL encoding issues
Solutions:
# Problem: Incorrect syntax
GET /indexes/hotels/docs?search=*&skip=10&top=20
# Solution: Use $ prefix
GET /indexes/hotels/docs?search=*&$skip=10&$top=20
# Problem: Exceeding limits
GET /indexes/hotels/docs?search=*&$top=10000
# Solution: Respect limits
GET /indexes/hotels/docs?search=*&$top=1000
Validation:
function validatePaginationParams(skip, top) {
if (skip < 0) {
throw new Error('Skip must be non-negative');
}
if (top < 1 || top > 1000) {
throw new Error('Top must be between 1 and 1000');
}
if (skip + top > 100000) {
throw new Error('Cannot retrieve results beyond position 100,000');
}
}
Result Shaping Issues¶
Issue 4: Field Selection Not Working¶
Symptoms: - All fields returned despite $select parameter - Specific fields missing from results - Unexpected field names in response
Causes: - Incorrect field names in $select - Case sensitivity issues - Non-retrievable fields specified
Solutions:
# Problem: Incorrect field names
GET /indexes/hotels/docs?search=*&$select=HotelName,Rating
# Solution: Use exact field names (case-sensitive)
GET /indexes/hotels/docs?search=*&$select=hotelName,rating
# Problem: Non-retrievable field
GET /indexes/hotels/docs?search=*&$select=hotelName,searchableField
# Solution: Check field configuration
GET /indexes/hotels
Debugging:
// Check index schema for field names and retrievability
async function validateFieldSelection(fields) {
const index = await searchClient.getIndex('hotels');
const retrievableFields = index.fields
.filter(f => f.retrievable !== false)
.map(f => f.name);
const invalidFields = fields.filter(f => !retrievableFields.includes(f));
if (invalidFields.length > 0) {
throw new Error(`Non-retrievable fields: ${invalidFields.join(', ')}`);
}
}
Issue 5: Hit Highlighting Not Appearing¶
Symptoms: - No highlighting in search results - Highlighting tags not applied - Partial highlighting only
Causes: - Fields not configured as searchable - Incorrect highlight parameter syntax - Search terms not found in highlighted fields
Solutions:
# Problem: Non-searchable field
GET /indexes/hotels/docs?search=luxury&highlight=rating
# Solution: Use searchable fields only
GET /indexes/hotels/docs?search=luxury&highlight=description,amenities
# Problem: No search terms
GET /indexes/hotels/docs?search=*&highlight=description
# Solution: Use actual search query
GET /indexes/hotels/docs?search=luxury spa&highlight=description,amenities
Field Validation:
async function validateHighlightFields(fields) {
const index = await searchClient.getIndex('hotels');
const searchableFields = index.fields
.filter(f => f.searchable === true)
.map(f => f.name);
const invalidFields = fields.filter(f => !searchableFields.includes(f));
if (invalidFields.length > 0) {
console.warn(`Non-searchable fields for highlighting: ${invalidFields.join(', ')}`);
}
}
Issue 6: Result Count Performance Issues¶
Symptoms: - Slow response times when using $count=true - Timeouts on large result sets - Inconsistent count values
Causes: - Expensive count operations on large indexes - Complex queries requiring full result set evaluation - Concurrent index updates affecting counts
Solutions:
# Problem: Always requesting count
GET /indexes/hotels/docs?search=*&$count=true&$top=20
# Solution: Request count only when needed
GET /indexes/hotels/docs?search=*&$top=20
# For UI that needs approximate counts
GET /indexes/hotels/docs?search=*&$top=20
# Show "20+ results" instead of exact count
Conditional Counting:
async function searchWithOptionalCount(query, options, needsCount = false) {
const searchOptions = {
...options,
includeTotalCount: needsCount
};
const results = await searchClient.search(query, searchOptions);
if (!needsCount && results.results.length === options.top) {
// Estimate: "20+ results"
results.estimatedCount = `${options.top}+`;
}
return results;
}
Performance Issues¶
Issue 7: Large Response Payloads¶
Symptoms: - Slow network transfer times - High bandwidth usage - Client-side memory issues
Causes: - Returning unnecessary fields - Large page sizes - Including binary data in results
Solutions:
# Problem: All fields returned
GET /indexes/hotels/docs?search=*&$top=50
# Solution: Select only needed fields
GET /indexes/hotels/docs?search=*&$select=hotelId,hotelName,rating&$top=50
# Problem: Large binary fields
GET /indexes/hotels/docs?search=*&$select=hotelName,imageData
# Solution: Exclude binary data, use URLs
GET /indexes/hotels/docs?search=*&$select=hotelName,imageUrl
Response Size Monitoring:
class ResponseSizeMonitor {
static monitor(response) {
const size = JSON.stringify(response).length;
if (size > 1024 * 1024) { // 1MB
console.warn(`Large response size: ${(size / 1024 / 1024).toFixed(2)}MB`);
}
return response;
}
}
Issue 8: Memory Issues with Large Result Sets¶
Symptoms: - Out of memory errors - Application crashes during pagination - Slow garbage collection
Causes: - Accumulating results in memory - Large page sizes - Not releasing previous page data
Solutions:
// Problem: Accumulating all results
class BadPaginator {
constructor() {
this.allResults = []; // Memory leak!
}
async loadPage(pageNum) {
const results = await this.search(pageNum);
this.allResults.push(...results); // Accumulating
return results;
}
}
// Solution: Process pages independently
class GoodPaginator {
async loadPage(pageNum) {
const results = await this.search(pageNum);
return results; // No accumulation
}
async *streamResults() {
let pageNum = 0;
let hasMore = true;
while (hasMore) {
const results = await this.loadPage(pageNum);
if (results.length === 0) {
hasMore = false;
} else {
yield results;
pageNum++;
}
}
}
}
Error Handling Issues¶
Issue 9: Rate Limiting Errors (429)¶
Symptoms: - HTTP 429 "Too Many Requests" errors - Intermittent pagination failures - Service unavailable responses
Causes: - Exceeding search service limits - Too many concurrent requests - Rapid pagination requests
Solutions:
class RateLimitHandler {
constructor(maxRetries = 3) {
this.maxRetries = maxRetries;
}
async searchWithRetry(query, options, retryCount = 0) {
try {
return await searchClient.search(query, options);
} catch (error) {
if (error.status === 429 && retryCount < this.maxRetries) {
const delay = Math.pow(2, retryCount) * 1000; // Exponential backoff
await this.delay(delay);
return this.searchWithRetry(query, options, retryCount + 1);
}
throw error;
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Issue 10: Invalid Parameter Errors (400)¶
Symptoms: - HTTP 400 "Bad Request" errors - "Invalid parameter" error messages - Pagination requests failing
Causes: - Invalid skip/top values - Malformed filter expressions - Incorrect field names
Solutions:
class ParameterValidator {
static validatePagination(skip, top) {
const errors = [];
if (typeof skip !== 'number' || skip < 0) {
errors.push('Skip must be a non-negative number');
}
if (typeof top !== 'number' || top < 1 || top > 1000) {
errors.push('Top must be a number between 1 and 1000');
}
if (skip + top > 100000) {
errors.push('Cannot retrieve results beyond position 100,000');
}
if (errors.length > 0) {
throw new Error(`Validation errors: ${errors.join(', ')}`);
}
}
static validateSelect(fields, indexSchema) {
const retrievableFields = indexSchema.fields
.filter(f => f.retrievable !== false)
.map(f => f.name);
const invalidFields = fields.filter(f => !retrievableFields.includes(f));
if (invalidFields.length > 0) {
throw new Error(`Invalid fields for selection: ${invalidFields.join(', ')}`);
}
}
}
Debugging Tools and Techniques¶
Debug Logging¶
class PaginationDebugger {
static logRequest(query, options) {
console.log('Search Request:', {
query,
skip: options.skip,
top: options.top,
select: options.select,
timestamp: new Date().toISOString()
});
}
static logResponse(response, duration) {
console.log('Search Response:', {
resultCount: response.results.length,
totalCount: response.count,
duration: `${duration}ms`,
timestamp: new Date().toISOString()
});
}
}
Performance Monitoring¶
class PerformanceMonitor {
static async measurePagination(searchFn, pages = 10) {
const results = [];
for (let i = 0; i < pages; i++) {
const start = Date.now();
const response = await searchFn(i);
const duration = Date.now() - start;
results.push({
page: i,
duration,
resultCount: response.results.length
});
}
return {
averageDuration: results.reduce((sum, r) => sum + r.duration, 0) / results.length,
maxDuration: Math.max(...results.map(r => r.duration)),
minDuration: Math.min(...results.map(r => r.duration)),
results
};
}
}
Health Checks¶
class PaginationHealthCheck {
static async checkPaginationHealth(searchClient) {
const checks = [];
// Test basic pagination
try {
await searchClient.search('*', { top: 10, skip: 0 });
checks.push({ test: 'basic_pagination', status: 'pass' });
} catch (error) {
checks.push({ test: 'basic_pagination', status: 'fail', error: error.message });
}
// Test field selection
try {
await searchClient.search('*', { select: ['hotelId'], top: 1 });
checks.push({ test: 'field_selection', status: 'pass' });
} catch (error) {
checks.push({ test: 'field_selection', status: 'fail', error: error.message });
}
// Test highlighting
try {
await searchClient.search('test', { highlightFields: ['description'], top: 1 });
checks.push({ test: 'highlighting', status: 'pass' });
} catch (error) {
checks.push({ test: 'highlighting', status: 'fail', error: error.message });
}
return checks;
}
}
Quick Reference¶
Common Error Codes¶
- 400: Invalid parameters (check skip, top, select, highlight values)
- 429: Rate limiting (implement retry with backoff)
- 500: Service error (check service health, retry)
- 503: Service unavailable (temporary issue, retry later)
Parameter Limits¶
- $top: 1-1000 (default: 50)
- $skip: 0-100000 (performance degrades with large values)
- $select: Field names must be retrievable
- highlight: Field names must be searchable
Performance Guidelines¶
- Page size: 10-50 for UI, up to 1000 for APIs
- Skip limit: < 10,000 for good performance
- Field selection: Always use for production
- Caching: Implement for frequently accessed pages
Next Steps¶
After resolving issues: 1. Review Best Practices for prevention strategies 2. Implement monitoring and alerting 3. Test with production-like data volumes 4. Document common issues for your team 5. Move to Module 8: Search Explorer & Portal Tools