Search Performance
WorkDesk search is designed for fast, real-time results — typically under 100ms. This is achieved through actor-scoped data, indexed fields, result limits, and debouncing. Understanding these design decisions helps engineers extend the search system correctly.
Performance Design Principles
- Actor scoping first — every query is filtered by
actorId + tenantIdbefore full-text matching, drastically reducing the search corpus - Indexed fields — search targets only indexed columns; no full-table scans
- Result limits enforced — maximum 20 results per section per request — no unbounded queries
- Client debouncing — 300ms debounce on the frontend prevents query-per-keystroke
- Rate limiting — 60 search requests/minute per user enforced at the API gateway
Indexed Fields by Section
| Section | Indexed Fields | Index Type |
|---|---|---|
| Tasks | title, workflowName, status, dueAt, actorId | Full-text on title; B-tree on status, dueAt, actorId |
| History | workflowName, status, triggeredAt, actorId | Full-text on workflowName; B-tree on status, triggeredAt |
| Notifications | title, body, type, isRead, createdAt, userId | Full-text on title+body; B-tree on type, isRead, userId |
Performance SLOs
| Query Type | P50 Target | P99 Target |
|---|---|---|
| Global search (all sections) | < 80ms | < 300ms |
| Section-scoped search | < 40ms | < 150ms |
| Filter-only (no text query) | < 20ms | < 80ms |
Pagination for Large Result Sets
When a search returns more than the limit per section (hasMore: true), the WorkDesk UI shows a "Show more from Tasks" link below the results. Clicking it fetches the next page of results for that section with section=tasks&page=2. This prevents overloading the UI with hundreds of results at once.
Search Scope Boundaries
Search results are always bounded to the actor's visible data. The query planner applies WHERE UserId = @actorId AND TenantId = @tenantId as the outermost condition before full-text matching. This is enforced server-side and cannot be bypassed by URL manipulation.
Extending Search
To add a new searchable section (e.g., a custom app's data), implement the IWorkDeskSearchProvider interface and register it in the DI container. The unified search endpoint will automatically include results from all registered providers:
// IWorkDeskSearchProvider — interface for extending search
public interface IWorkDeskSearchProvider
{
string SectionKey { get; } // e.g., "my-custom-section"
Task<SearchSectionResult> SearchAsync(
string query,
Guid actorId,
Guid tenantId,
int limit,
CancellationToken ct);
}
// Register in DI
services.AddWorkDeskSearchProvider<MyCustomSearchProvider>();