Performance Implications of `mcp_server_search` versus `datasource_search` in Coco App
The primary performance difference stems from caching: datasource_search writes results to an in-memory DATASOURCE_CACHE (protected by RwLock) enabling faster subsequent reads, while mcp_server_search intentionally disables caching, incurring full network latency on every call but avoiding lock contention.
The Coco App backend, implemented in the infinilabs/coco-app repository, provides two distinct search commands for retrieving server information. Understanding the performance implications of mcp_server_search versus datasource_search is critical for optimizing UI responsiveness and backend scalability. Both functions share identical network request patterns but diverge significantly in their caching strategies and concurrency characteristics.
Backend Architecture and Request Flow
Both commands follow the same high-level workflow implemented in src-tauri/src/server/datasource.rs. They issue HTTP POST requests to their respective endpoints, validate responses using status_code_check, and parse JSON via parse_search_results.
datasource_search: Posts to/datasource/_searchmcp_server_search: Posts to/mcp_server/_search
The shared HttpClient::post implementation in src-tauri/src/server/http_client.rs handles the actual network I/O for both commands, meaning initial request latency is identical until the post-processing phase.
Caching Strategy: The Critical Performance Difference
The core performance divergence occurs after JSON parsing, where datasource_search implements a caching layer that mcp_server_search deliberately omits.
datasource_search: In-Memory Cache Writes
After parsing results, this command calls save_datasource_to_cache to store the Vec<DataSource> in a global DATASOURCE_CACHE protected by an RwLock:
// src-tauri/src/server/datasource.rs (lines 90-111)
#[tauri::command]
pub async fn datasource_search(
id: &str,
query_params: Option<Vec<String>>,
) -> Result<Vec<DataSource>, String> {
let resp = HttpClient::post(id, "/datasource/_search", query_params, None).await?;
status_code_check(&resp, &[StatusCode::OK, StatusCode::CREATED])?;
let datasources = parse_search_results(resp).await?;
save_datasource_to_cache(&id, datasources.clone()); // ← cache write enabled
Ok(datasources)
}
This adds a small overhead on the first call but enables subsequent reads from memory instead of issuing another network request, significantly reducing UI latency during repeated queries.
mcp_server_search: Stateless Execution
The cache write line is intentionally commented out, forcing every request to hit the remote service:
// src-tauri/src/server/datasource.rs (lines 113-133)
#[tauri::command]
pub async fn mcp_server_search(
id: &str,
query_params: Option<Vec<String>>,
) -> Result<Vec<DataSource>, String> {
let resp = HttpClient::post(id, "/mcp_server/_search", query_params, None).await?;
status_code_check(&resp, &[StatusCode::OK, StatusCode::CREATED])?;
let mcp_server = parse_search_results(resp).await?;
// save_datasource_to_cache(&id, mcp_server.clone()); // ← intentionally disabled
Ok(mcp_server)
}
This design ensures always-fresh results but means each search pays the full round-trip cost, making it suitable for ad-hoc user-triggered searches rather than frequent UI refresh cycles.
Concurrency Characteristics and Lock Contention
The caching decision creates fundamentally different concurrency behaviors under high load.
Write Lock Bottlenecks in datasource_search
The DATASOURCE_CACHE.write() lock can become a bottleneck if many concurrent data-source searches are performed. Multiple simultaneous calls must serialize on the write lock before the cache updates, potentially creating contention points in high-traffic scenarios.
Lock-Free Scaling in mcp_server_search
Without cache writes, mcp_server_search takes no locks and remains fully asynchronous. This allows better horizontal scaling under high concurrency, though it may overload the remote service if called excessively since every request generates unique network traffic.
Frontend Adapter Implementation
Both commands are exposed to the frontend through identical adapter patterns in src/utils/webAdapter.ts. The adapters perform simple response transformation, mapping the Elasticsearch-style hits to flat item lists with id and name properties.
MCP Server Adapter:
// src/utils/webAdapter.ts (lines 73-91)
async searchMCPServers(_serverId, queryParams) {
const [error, res] = await Post(
`/mcp_server/_search?${queryParams?.join("&")}`,
undefined
);
if (error) { console.error("_search", error); return []; }
return res?.hits?.hits?.map(item => ({
...item,
id: item._source.id,
name: item._source.name,
})) ?? [];
}
Data Source Adapter:
// src/utils/webAdapter.ts (lines 93-111)
async searchDataSources(_serverId, queryParams) {
const [error, res] = await Post(
`/datasource/_search?${queryParams?.join("&")}`,
undefined
);
if (error) { console.error("_search", error); return []; }
return res?.hits?.hits?.map(item => ({
...item,
id: item._source.id,
name: item._source.name,
})) ?? [];
}
Since both adapters perform the same transformation logic, the performance gap is dictated entirely by the backend caching behavior described above.
Performance Comparison Summary
| Aspect | datasource_search |
mcp_server_search |
|---|---|---|
| First Call Cost | Network I/O + JSON parsing + cache write overhead | Network I/O + JSON parsing only |
| Repeated Calls | Memory read (fast) | Full network round-trip (slow) |
| Concurrency | Subject to RwLock contention |
Lock-free, better parallel scaling |
| Data Freshness | Potentially stale until cache refresh | Always fresh from source |
| Typical Use Case | Frequently accessed UI lists | Ad-hoc user-triggered queries |
Summary
- Initial request cost is comparable for both functions, involving network I/O and JSON parsing through
HttpClient::postandparse_search_results. - Subsequent reads favor
datasource_searchdue to in-memory caching viaDATASOURCE_CACHE, whilemcp_server_searchconsistently incurs network latency. - Concurrency scaling favors
mcp_server_searchbecause it avoids theRwLockwrite contention present indatasource_search. - Data staleness is a trade-off: cached data sources may become outdated unless refreshed via
refresh_all_datasources, whereas MCP server results are always current. - Implementation location: Both commands reside in
src-tauri/src/server/datasource.rs, with frontend adapters insrc/utils/webAdapter.ts.
Frequently Asked Questions
Why does mcp_server_search disable caching?
The cache write line is intentionally commented out in src-tauri/src/server/datasource.rs because MCP server searches are typically ad-hoc operations triggered by user input rather than repeated UI refresh cycles. Disabling caching ensures users always see current server states without requiring explicit cache invalidation logic, trading performance for data freshness.
Can datasource_search cache cause memory pressure?
The global DATASOURCE_CACHE uses an RwLock for thread-safe access, but memory pressure depends on the size of the Vec<DataSource> being stored and the number of unique server IDs cached. The implementation in src-tauri/src/server/datasource.rs does not show explicit cache eviction policies, suggesting potential growth under heavy usage patterns with many unique data sources.
Which function should I use for real-time search interfaces?
For real-time interfaces requiring immediate feedback on every keystroke, mcp_server_search provides more predictable latency without lock contention, though you must account for consistent network overhead. For static lists that refresh periodically, datasource_search delivers superior performance after the initial population due to memory-resident results.
How does lock contention manifest in high-concurrency scenarios?
When multiple datasource_search calls execute simultaneously, they serialize on the DATASOURCE_CACHE.write() lock in src-tauri/src/server/datasource.rs. This creates a bottleneck where threads queue to acquire the write lock before updating the cache, potentially degrading throughput under heavy parallel load compared to the lock-free mcp_server_search implementation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →