Skip to content

Commit 697cf26

Browse files
committed
perf(country): optimize database queries and caching
- Refactor country queries to use sub-pipelines for filtering active sources and headlines, improving efficiency. - Enhance caching by generating canonical cache keys that represent query parameters in a consistent and unique manner. - These changes reduce database load and improve the performance and scalability of country-related operations.
1 parent 6a22180 commit 697cf26

File tree

1 file changed

+38
-13
lines changed

1 file changed

+38
-13
lines changed

lib/src/services/country_query_service.dart

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -129,40 +129,52 @@ class CountryQueryService {
129129

130130
// --- Stage 2: Handle `hasActiveSources` filter ---
131131
if (filter['hasActiveSources'] == true) {
132+
// This lookup uses a sub-pipeline to filter for active sources *before*
133+
// joining, which is more efficient than a post-join match.
132134
pipeline.add({
133135
r'$lookup': {
134136
'from': 'sources',
135-
'localField': '_id',
136-
'foreignField': 'headquarters._id',
137+
'let': {'countryId': r'$_id'},
138+
'pipeline': [
139+
{
140+
r'$match': {
141+
r'$expr': {r'$eq': [r'$headquarters._id', r'$$countryId']},
142+
'status': ContentStatus.active.name,
143+
}
144+
}
145+
],
137146
'as': 'matchingSources',
138147
},
139148
});
140149
pipeline.add({
141150
r'$match': {
142-
'matchingSources': {
143-
r'$ne': <dynamic>[],
144-
}, // Ensure there's at least one source
145-
'matchingSources.status': ContentStatus.active.name,
151+
'matchingSources': {r'$ne': <dynamic>[]},
146152
},
147153
});
148154
}
149155

150156
// --- Stage 3: Handle `hasActiveHeadlines` filter ---
151157
if (filter['hasActiveHeadlines'] == true) {
158+
// This lookup uses a sub-pipeline to filter for active headlines *before*
159+
// joining, which is more efficient than a post-join match.
152160
pipeline.add({
153161
r'$lookup': {
154162
'from': 'headlines',
155-
'localField': '_id',
156-
'foreignField': 'eventCountry._id',
163+
'let': {'countryId': r'$_id'},
164+
'pipeline': [
165+
{
166+
r'$match': {
167+
r'$expr': {r'$eq': [r'$eventCountry._id', r'$$countryId']},
168+
'status': ContentStatus.active.name,
169+
}
170+
}
171+
],
157172
'as': 'matchingHeadlines',
158173
},
159174
});
160175
pipeline.add({
161176
r'$match': {
162-
'matchingHeadlines': {
163-
r'$ne': <dynamic>[],
164-
}, // Ensure there's at least one headline
165-
'matchingHeadlines.status': ContentStatus.active.name,
177+
'matchingHeadlines': {r'$ne': <dynamic>[]},
166178
},
167179
});
168180
}
@@ -250,7 +262,20 @@ class CountryQueryService {
250262
return pipeline;
251263
}
252264

253-
/// Generates a unique cache key from the query parameters.
265+
/// Generates a unique, canonical cache key from the query parameters.
266+
///
267+
/// A canonical key is essential for effective caching. If two different
268+
/// sets of parameters represent the same logical query (e.g., filters in a
269+
/// different order), they must produce the exact same cache key.
270+
///
271+
/// This implementation achieves this by:
272+
/// 1. Using a [SplayTreeMap] for the `filter` map, which automatically
273+
/// sorts the filters by their keys.
274+
/// 2. Sorting the `sort` options by their field names.
275+
/// 3. Combining these sorted structures with pagination details into a
276+
/// standard map.
277+
/// 4. Encoding the final map into a JSON string, which serves as the
278+
/// reliable and unique cache key.
254279
String _generateCacheKey(
255280
Map<String, dynamic> filter,
256281
PaginationOptions? pagination,

0 commit comments

Comments
 (0)