chore: add documentation on unified search (#108847)

* chore: add documentation on unified search

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>

* fix: add additional needed feature flags for unified search

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>

* fix: add docs about index_min_count and index_max_count

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>

* fix: add documentation about sortable fields and the discrepancy with search/sortable

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>

* fix: kubernetesClientDashboardsFolders feature flag is no more, remove it from the docs

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>

* fix: simplify request flow diagrams

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>

---------

Signed-off-by: Bruno Abrantes <bruno@brunoabrantes.com>
This commit is contained in:
Bruno Abrantes
2025-07-31 07:18:29 +00:00
committed by GitHub
parent ef80129bd9
commit 91e5e8019d
+509
View File
@@ -777,3 +777,512 @@ The dual writer system provides metrics for monitoring:
- `dual_writer_mode_transitions_total`: Counter of mode transitions
Use these metrics to monitor the health of your migration and identify any issues with the dual writer system.
---
## Unified Search System
The Unified Search system provides a scalable, distributed search capability for Grafana's Unified Storage. It uses a ring-based architecture to distribute search requests across multiple search server instances, with namespace-based sharding for optimal performance and data distribution.
### System Architecture
The search system provides both unified and legacy search capabilities, with routing based on dual writer mode configuration:
```mermaid
graph TB
subgraph "Request Sources"
A[Grafana UI<br/>User Search]
B[Dashboard Service<br/>Resource Searches]
C[Folder Service<br/>Resource Searches]
D[Alerting Service<br/>Resource Searches]
E[Provisioning Service<br/>Resource Searches]
F[API Endpoints<br/>Search Operations]
end
subgraph "API Gateway Layer"
G[Grafana API Server<br/>Search Endpoint]
H[Search Client<br/>Dual Writer Aware]
end
subgraph "Routing Decision"
I{Dual Writer Mode<br/>Check}
end
subgraph "Unified Search Path (Mode 3+)"
J[Search Distributor<br/>Ring-based Routing]
K[Ring<br/>Consistent Hashing]
L[Search API Server 1<br/>Namespace Sharding<br/>+ Embedded Bleve Backend]
M[Search API Server 2<br/>Namespace Sharding<br/>+ Embedded Bleve Backend]
N[Search API Server 3<br/>Namespace Sharding<br/>+ Embedded Bleve Backend]
O[Unified Storage<br/>K8s-style Resources]
end
subgraph "Legacy Search Path (Mode 0-2)"
Q[Legacy Search Service<br/>SQL-based Search]
R[Legacy Storage<br/>Traditional Tables]
S[Shadow Traffic<br/>Mode 1-2 + Flag Enabled]
end
A --> G
B --> H
C --> H
D --> H
E --> H
F --> G
G --> H
H --> I
I -->|Mode 3+| J
I -->|Mode 0-2| Q
J --> K
K --> L
K --> M
K --> N
L -.->|Just-in-Time<br/>Indexing| O
M -.->|Just-in-Time<br/>Indexing| O
N -.->|Just-in-Time<br/>Indexing| O
Q --> R
Q -.->|Shadow Traffic<br/>Mode 1-2 + Flag| S
S -.-> J
```
### Search Backend Routing
The search client routes requests based on the dual writer mode configuration for each resource type:
#### Dual Writer Mode → Backend Routing
- **Mode 0-2**: Route to **Legacy Search**
- Mode 1-2: Shadow traffic to Unified Search (if `unifiedStorageSearchDualReaderEnabled` is enabled)
- Mode 0: No shadow traffic
- **Mode 3+**: Route to **Unified Search**
- No shadow traffic needed (unified is primary)
### Feature Flags
Unified Search requires several feature flags to be enabled depending on the desired functionality:
#### Prerequisites (Required for Unified Storage)
| Feature Flag | Purpose | Stage | Required For |
|--------------|---------|-------|--------------|
| `grafanaAPIServerWithExperimentalAPIs` | Allow experimental API groups | Development | Access to v0alpha1 APIs (including search) |
#### Unified Search Specific Flags
| Feature Flag | Purpose | Stage | Required For |
|--------------|---------|-------|--------------|
| `unifiedStorageSearch` | Core search functionality | Experimental | Search API servers, indexing |
| `unifiedStorageSearchUI` | Frontend search interface | Experimental | Grafana UI search |
| `unifiedStorageSearchPermissionFiltering` | User permission filtering | GA | Access control in search results |
| `unifiedStorageSearchSprinkles` | Usage insights integration | Experimental | Dashboard usage sorting (Enterprise) |
| `unifiedStorageSearchDualReaderEnabled` | Shadow traffic to unified search | Experimental | Shadow traffic during migration |
#### Basic Configuration
```ini
[feature_toggles]
; Prerequisites for unified storage (required)
grafanaAPIServerWithExperimentalAPIs = true
; Core search functionality (required)
unifiedStorageSearch = true
; Enable search UI (required for frontend)
unifiedStorageSearchUI = true
; Enable permission filtering (recommended)
unifiedStorageSearchPermissionFiltering = true
; Enable shadow traffic during migration (optional)
unifiedStorageSearchDualReaderEnabled = true
; Enable usage insights sorting (Enterprise only)
unifiedStorageSearchSprinkles = true
```
### Request Flow Diagrams
#### Search Request Flow with Dual Writer Mode Routing
Search requests originate from multiple sources, and the search client routes based on dual writer mode configuration:
```mermaid
flowchart TD
A[Search Request] --> B{Dual Writer Mode}
B -->|Mode 3+| C[Unified Search]
B -->|Mode 0-2| D[Legacy Search]
C --> E[Return Results]
D --> E
```
#### Search Request Flow with Shadow Traffic
When `unifiedStorageSearchDualReaderEnabled` is enabled and resource is in dual writer Mode 1-2 (legacy primary), shadow traffic is generated:
```mermaid
flowchart TD
A[Search Request] --> B{Shadow Traffic Enabled?}
B -->|Yes| C[Primary: Legacy Search]
B -->|No| D[Single Search Path]
C --> E[Background: Unified Search]
C --> F[Return Legacy Results]
E --> G[Log Results for Comparison]
D --> H[Return Results]
```
### Distributor Architecture
The Search Distributor acts as a smart proxy that routes search requests to the appropriate search API server based on namespace hashing:
```mermaid
flowchart TD
A[Incoming Search Request] --> B[Hash Namespace]
B --> C[Select Random Instance]
C --> D[Proxy Request]
D --> E[Return Response]
```
#### Key Features:
- **Namespace-based routing**: Each request is routed based on the target namespace
- **Load balancing**: Random selection among available replicas for the namespace
- **Health awareness**: Only routes to `ACTIVE` ring instances
- **Connection pooling**: Reuses gRPC connections for efficiency
- **Proxy headers**: Adds metadata for debugging and tracing
### Ring Architecture
The hash ring provides consistent, distributed assignment of namespaces to search API servers:
```mermaid
flowchart TD
A[Namespace] --> B[Hash Function]
B --> C[Ring Position]
C --> D[Assigned Instance]
D --> E[Search Processing]
```
#### Ring Properties:
- **Consistent hashing**: Uses FNV32 hash function for namespace distribution
- **128 tokens per instance**: Provides good distribution across the ring
- **Replication factor**: Configurable redundancy (default based on cluster size)
- **State management**: Instances transition through JOINING → ACTIVE → LEAVING
- **Automatic rebalancing**: Ring adjusts when instances join/leave
### Namespace-Based Sharding
Unified Search uses namespace-based sharding to distribute search indexes across multiple search API servers:
```mermaid
flowchart LR
A[Namespaces] --> B[Hash Ring]
B --> C[Search Server 1]
B --> D[Search Server 2]
B --> E[Search Server 3]
C --> F[Indexes for Assigned Namespaces]
D --> G[Indexes for Assigned Namespaces]
E --> H[Indexes for Assigned Namespaces]
```
#### Sharding Benefits:
1. **Horizontal scalability**: Add more search servers to handle more namespaces
2. **Resource isolation**: Each namespace's index is independent
3. **Parallel processing**: Multiple searches can run simultaneously across different servers
4. **Fault tolerance**: Namespace availability depends only on its assigned server(s)
#### Sharding Algorithm:
```go
func getSearchServer(namespace string) string {
hash := fnv.New32a()
hash.Write([]byte(namespace))
// Get replication set from ring
replicationSet := ring.GetWithOptions(
hash.Sum32(),
searchRingRead,
ring.WithReplicationFactor(ring.ReplicationFactor())
)
// Random load balancing within replication set
instance := replicationSet.Instances[rand.Intn(len(replicationSet.Instances))]
return instance.Id
}
```
### Search Index Management
Each search API server contains an embedded Bleve search engine that manages indexes for its assigned namespaces:
```mermaid
flowchart TD
A[Search Request] --> B{Index Ready?}
B -->|Yes| C[Query Index]
B -->|No| D[Build Index]
D --> E[Fetch Resources]
E --> F[Create Index]
F --> C
C --> G[Return Results]
H[Resource Changes] --> I[Update Index]
I --> F
```
#### Index Architecture Details:
**Embedded Bleve Backend**: Each Search API Server contains its own Bleve search engine instance, not a shared external service.
**Just-in-Time Indexing**: When a search request arrives for a namespace that doesn't have an index (or has an outdated index):
1. The Search API Server fetches all resources for that namespace from Unified Storage
2. Builds search documents in memory
3. Creates either a memory-based or disk-based Bleve index depending on size
4. Executes the search query against the newly built index
5. Returns results to the user
**Index Storage Strategy**:
- **Memory indexes**: For small datasets (< `index_file_threshold` documents)
- **Disk indexes**: For large datasets (≥ `index_file_threshold` documents)
- Indexes are stored per Search API Server instance, not globally shared
**Background Updates**: In addition to just-in-time indexing, Search API Servers also maintain indexes through background watch events for incremental updates.
#### Index Configuration:
```ini
[unified_storage]
; Path for disk-based search indexes
index_path = /var/lib/grafana/unified-search/bleve
; Threshold for file-based vs memory indexes
index_file_threshold = 1000
; Maximum batch size for indexing
index_max_batch_size = 100
; Number of worker threads for indexing
index_workers = 4
; Cache TTL for indexes
index_cache_ttl = 1h
; Periodic rebuild interval (for usage insights)
index_rebuild_interval = 24h
; Minimum resource count required to build an index (default: 1)
; If a namespace has fewer resources than this threshold, no index will be created
index_min_count = 1
; Maximum resource count before creating an empty index (default: 0 = no limit)
; When exceeded, creates an empty index instead of indexing all resources for performance
index_max_count = 0
```
### Search Request Sources
Unified Search serves multiple types of consumers within the Grafana ecosystem:
#### 1. User-Initiated Searches
- **Source**: Grafana UI search interface
- **Purpose**: Interactive dashboard and folder discovery
- **Characteristics**: Real-time, user-facing, latency-sensitive
- **Endpoint**: `/api/v1/search` (legacy search UI) or `/apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search` (when `unifiedStorageSearchUI` is enabled)
#### 2. Internal Service Searches
Internal services use different search backends depending on dual writer mode configuration:
- **Dashboard Service**:
- Find related dashboards based on tags, folders, or content
- Discover dashboards for playlist creation
- Validate dashboard references during operations
- **Backend**: Depends on dashboard dual writer mode (Legacy for Mode 0-2, Unified for Mode 3+)
- **Folder Service**:
- Retrieve folder contents and nested structures
- Resolve folder hierarchy relationships
- Check folder permissions and accessibility
- **Backend**: Depends on folder dual writer mode (Legacy for Mode 0-2, Unified for Mode 3+)
- **Alerting Service**:
- Discover dashboards and panels for alert rule creation
- Find existing alert rules across namespaces
- Resolve dashboard/panel references in alert definitions
- **Backend**: Mixed - dashboard searches use dashboard dual writer mode, alert rule searches typically use legacy
- **Provisioning Service**:
- Check for existing resources before provisioning
- Validate resource uniqueness and naming conflicts
- Discover resources for bulk operations
- **Backend**: Depends on each resource type's dual writer mode configuration
- **API Services**:
- Backend support for various API endpoints
- Resource validation and dependency checking
- **Backend**: Routes based on resource type's dual writer mode
#### 3. Search Operation Types
Unified Search supports multiple types of search operations:
##### Resource Search
- **Purpose**: Find resources (dashboards, folders, etc.) by content
- **Endpoint**: `/api/v1/search` (legacy) or `/apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search` (when `unifiedStorageSearchUI` is enabled)
- **Additional endpoint**: `/apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search/sortable` for retrieving sortable fields
- **Features**: Full-text search, filtering, sorting
**Sortable Fields:**
The `/search/sortable` endpoint currently returns a limited static list:
```json
{
"fields": [
{"field": "title", "display": "Title (A-Z)", "type": "string"},
{"field": "-title", "display": "Title (Z-A)", "type": "string"}
]
}
```
However, the search backend actually supports sorting by many more fields:
**Standard Fields:**
- `title` - Resource display name (uses `title_phrase` for exact sorting)
- `name` - Kubernetes resource name
- `description` - Resource description
- `folder` - Parent folder name
- `created` - Creation timestamp (int64)
- `updated` - Last update timestamp (int64)
- `createdBy` - Creator user ID
- `updatedBy` - Last updater user ID
- `tags` - Resource tags (array)
- `rv` - Resource version (int64)
**Dashboard-Specific Fields** (require `fields.` prefix):
- `fields.schema_version` - Dashboard schema version
- `fields.link_count` - Number of dashboard links
- `fields.panel_types` - Panel types used in dashboard
- `fields.ds_types` - Data source types used
- `fields.transformation` - Transformations used
**Usage Insights Fields** (Enterprise only, require `fields.` prefix):
- `fields.views_total` - Total dashboard views
- `fields.views_last_1_days` / `fields.views_last_7_days` / `fields.views_last_30_days` - Recent views
- `fields.views_today` - Today's views
- `fields.queries_total` - Total queries executed
- `fields.queries_last_1_days` / `fields.queries_last_7_days` / `fields.queries_last_30_days` - Recent queries
- `fields.queries_today` - Today's queries
- `fields.errors_total` - Total errors
- `fields.errors_last_1_days` / `fields.errors_last_7_days` / `fields.errors_last_30_days` - Recent errors
- `fields.errors_today` - Today's errors
**Usage Examples:**
```bash
# Sort by title (ascending)
GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?sortBy=title
# Sort by creation date (descending)
GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?sortBy=-created
# Sort by usage insights (Enterprise)
GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?sortBy=-fields.views_total
```
*Note: There's currently a discrepancy between the limited fields exposed by `/search/sortable` and the full range of fields actually supported by the search backend.*
##### Federated Search
- **Purpose**: Search across multiple resource types simultaneously
- **Features**: Cross-resource queries, unified result ranking, combined sorting and faceting
- **Implementation**: Uses Bleve IndexAlias to combine multiple indexes for unified searching
- **Default behavior**: When no type is specified, automatically federates dashboards and folders
**How Federated Search Works:**
Federated search is implemented using Bleve's IndexAlias feature, which allows searching across multiple indexes as if they were a single unified index. This enables:
1. **Cross-resource queries**: Search for content across dashboards, folders, and other resource types
2. **Unified sorting**: Results from different resource types are merged and sorted together
3. **Combined faceting**: Aggregate facet statistics across all federated resource types
4. **Permission filtering**: Respects user permissions for each resource type independently
**API Usage Examples:**
**1. Default Federation (Dashboards + Folders):**
```bash
# When no type is specified, automatically searches dashboards and folders
GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?query=my-search
```
**2. Single Resource Type Search:**
```bash
# Search only folders (despite the "dashboard" API group, type parameter controls what's searched)
GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?type=folders&query=my-search
# Search only dashboards
GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?type=dashboards&query=my-search
```
**3. Explicit Two-Type Federation:**
```bash
# Search dashboards (primary) with folders federated
GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?type=dashboards&type=folders&query=my-search
```
**4. Protocol Buffer Request Structure:**
```protobuf
message ResourceSearchRequest {
ListOptions options = 1; // Primary resource type to search
repeated ResourceKey federated = 2; // Additional resource types to federate
string query = 3; // Search query applied across all types
// ... other fields
}
```
**Example gRPC/Protocol Buffer Usage:**
```go
searchRequest := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey, // Primary: search dashboards
},
Federated: []*resourcepb.ResourceKey{
folderKey, // Also search folders
},
Query: "monitoring",
Limit: 50,
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "title", Desc: false}, // Sort combined results by title
},
}
```
**5. Unified Results:**
Federated search returns a single result set containing resources from all specified types, with:
- **Unified ranking**: All results scored and ranked together
- **Cross-type sorting**: Resources from different types sorted by common fields (title, tags, etc.)
- **Resource type identification**: Each result includes metadata indicating its resource type
- **Permission-aware filtering**: Only returns resources the user has permission to see
**Limitations:**
- Federation only works across resource types with **common fields** (title, tags, folder, etc.)
- All federated indexes must be of the same search backend type (currently Bleve)
- Currently supports up to 2 resource types in federation via the API endpoint
- **Architectural note**: The search endpoint is under `dashboard.grafana.app` but can search any resource type via the `type` parameter - this is a design choice where the "dashboard search" has evolved into a generic search endpoint
##### Managed Objects
- **Purpose**: Administrative queries for resource management
- **Operations**: Count, list, statistics
##### Stats and Monitoring
- **Purpose**: Index health and performance metrics
- **Metrics**: Document counts, index sizes, search latency
### Monitoring and Observability
Key metrics for monitoring Unified Search:
- `unified_search_requests_total`: Search request counts by type and status
- `unified_search_request_duration_seconds`: Search request latency
- `unified_search_index_size_bytes`: Size of search indexes
- `unified_search_documents_total`: Number of indexed documents
- `unified_search_indexing_duration_seconds`: Time to build/update indexes
- `unified_search_shadow_requests_total`: Shadow traffic request counts
- `unified_search_ring_members`: Number of active search server instances