10#include <userver/cache/cache_statistics.hpp>
11#include <userver/cache/caching_component_base.hpp>
12#include <userver/cache/mongo_cache_type_traits.hpp>
13#include <userver/components/component_context.hpp>
14#include <userver/formats/bson/document.hpp>
15#include <userver/formats/bson/inline.hpp>
16#include <userver/formats/bson/value_builder.hpp>
17#include <userver/storages/mongo/collection.hpp>
18#include <userver/storages/mongo/operations.hpp>
19#include <userver/storages/mongo/operators.hpp>
20#include <userver/storages/mongo/options.hpp>
21#include <userver/tracing/span.hpp>
22#include <userver/utils/cpu_relax.hpp>
23#include <userver/yaml_config/merge_schemas.hpp>
25USERVER_NAMESPACE_BEGIN
29inline const std::string kFetchAndParseStage =
"fetch_and_parse";
31inline constexpr std::chrono::milliseconds kCpuRelaxThreshold{10};
32inline constexpr std::chrono::milliseconds kCpuRelaxInterval{2};
36std::chrono::milliseconds GetMongoCacheUpdateCorrection(
const ComponentConfig&);
38template <
class MongoCacheTraits>
41 const std::chrono::system_clock::time_point& last_update,
42 const std::chrono::system_clock::time_point& now,
43 const std::chrono::system_clock::duration& correction
56template <
class MongoCacheTraits>
59 MongoCacheFindOperationBase(
const ComponentConfig& config,
const ComponentContext& context)
65 const std::chrono::system_clock::time_point& last_update,
66 const std::chrono::system_clock::time_point& now,
67 const std::chrono::system_clock::duration& correction
75template <
class MongoCacheTraits>
76requires mongo_cache::impl::HasFindOperationInTraits<MongoCacheTraits>
79 MongoCacheFindOperationBase(
const ComponentConfig& config,
const ComponentContext& context)
85 const std::chrono::system_clock::time_point& last_update,
86 const std::chrono::system_clock::time_point& now,
87 const std::chrono::system_clock::duration& correction
89 return impl::MakeDefaultFindOperation<MongoCacheTraits>(type, last_update, now, correction);
190template <
class MongoCacheTraits>
192 using CollectionsType = mongo_cache::impl::CollectionsType<
decltype(MongoCacheTraits::kMongoCollectionsField)>;
196 static constexpr std::string_view kName = MongoCacheTraits::kName;
198 MongoCache(
const ComponentConfig&,
const ComponentContext&);
200 static yaml_config::Schema GetStaticConfigSchema();
205 const std::chrono::system_clock::time_point& last_update,
206 const std::chrono::system_clock::time_point& now,
207 cache::UpdateStatisticsScope& stats_scope
210 typename MongoCacheTraits::ObjectType DeserializeObject(
const formats::
bson::
Document& doc)
const;
214 const std::chrono::system_clock::time_point& last_update,
215 const std::chrono::system_clock::time_point& now,
216 const std::chrono::system_clock::duration& correction
219 std::unique_ptr<
typename MongoCacheTraits::DataType> GetData(
cache::
UpdateType type);
221 const std::shared_ptr<CollectionsType> mongo_collections_;
222 const storages::mongo::
Collection*
const mongo_collection_;
223 const std::chrono::system_clock::duration correction_;
224 std::size_t cpu_relax_iterations_{0};
227template <
class MongoCacheTraits>
228inline constexpr bool kHasValidate<MongoCache<MongoCacheTraits>> =
true;
230template <
class MongoCacheTraits>
233 const std::chrono::system_clock::time_point& last_update,
234 const std::chrono::system_clock::time_point& now,
235 const std::chrono::system_clock::duration& correction
237 namespace bson = formats::
bson;
238 namespace sm = storages::mongo;
240 if constexpr (mongo_cache::impl::HasFindOperation<MongoCacheTraits>) {
241 return MongoCacheTraits::GetFindOperation(type, last_update, now, correction);
244 if constexpr (mongo_cache::impl::HasUpdateFieldName<MongoCacheTraits>) {
246 query_builder[MongoCacheTraits::kMongoUpdateFieldName] =
254template <
class MongoCacheTraits>
255MongoCache<MongoCacheTraits>::MongoCache(
const ComponentConfig& config,
const ComponentContext& context)
256 : FindOperationBase(config, context),
257 mongo_collections_(context.FindComponent<
typename MongoCacheTraits::MongoCollectionsComponent>()
258 .
template GetCollectionForLibrary<CollectionsType>()),
259 mongo_collection_(std::addressof(mongo_collections_.get()->*MongoCacheTraits::kMongoCollectionsField)),
260 correction_(impl::GetMongoCacheUpdateCorrection(config))
262 [[maybe_unused]] mongo_cache::impl::CheckTraits<MongoCacheTraits> check_traits;
266 !mongo_cache::impl::HasUpdateFieldName<MongoCacheTraits> &&
267 !mongo_cache::impl::HasFindOperation<MongoCacheTraits> &&
268 mongo_cache::impl::HasDefaultFindOperation<MongoCacheTraits>)
270 throw std::logic_error(fmt::format(
271 "Incremental update support is requested in config but no update field "
272 "name is specified in traits of '{}' cache",
276 if (correction_.count() < 0) {
277 throw std::logic_error(fmt::format(
278 "Refusing to set forward (negative) update correction requested in "
279 "config for '{}' cache",
285template <
class MongoCacheTraits>
288 const std::chrono::system_clock::time_point& last_update,
289 const std::chrono::system_clock::time_point& now,
290 cache::UpdateStatisticsScope& stats_scope
292 namespace sm = storages::mongo;
294 const auto* collection = mongo_collection_;
295 auto find_op = GetFindOperation(type, last_update, now, correction_);
296 auto cursor = collection->Execute(find_op);
299 LOG_INFO() <<
"No changes in cache " << MongoCacheTraits::kName;
305 auto new_cache = GetData(type);
311 std::size_t doc_count = 0;
313 for (
const auto& doc : cursor) {
321 auto object = DeserializeObject(doc);
322 auto key = (object.*MongoCacheTraits::kKeyField);
325 (*new_cache)[key] = std::move(object);
328 <<
"Found duplicate key for 2 items in cache " << MongoCacheTraits::kName <<
", key=" << key;
330 }
catch (
const std::exception& e) {
332 <<
"Failed to deserialize cache item of cache " << MongoCacheTraits::kName
333 <<
", _id=" << doc[
"_id"].
template ConvertTo<std::string>() <<
", what(): " << e;
336 if (!MongoCacheTraits::kAreInvalidDocumentsSkipped) {
343 if (elapsed_time > kCpuRelaxThreshold) {
344 cpu_relax_iterations_ =
static_cast<
345 std::size_t>(
static_cast<
double>(doc_count) / (elapsed_time / kCpuRelaxInterval));
347 "Elapsed time for updating {} {} for {} data items is over threshold. "
348 "Will relax CPU every {} iterations",
350 elapsed_time.count(),
352 cpu_relax_iterations_
358 const auto size = new_cache->size();
359 this->Set(std::move(new_cache));
363template <
class MongoCacheTraits>
364typename MongoCacheTraits::ObjectType
MongoCache<MongoCacheTraits>::DeserializeObject(
const formats::
bson::
Document& doc
366 if constexpr (mongo_cache::impl::HasDeserializeObject<MongoCacheTraits>) {
367 return MongoCacheTraits::DeserializeObject(doc);
369 if constexpr (mongo_cache::impl::HasDefaultDeserializeObject<MongoCacheTraits>) {
370 return doc.As<
typename MongoCacheTraits::ObjectType>();
372 UASSERT_MSG(
false,
"No deserialize operation defined but DeserializeObject invoked");
375template <
class MongoCacheTraits>
378 const std::chrono::system_clock::time_point& last_update,
379 const std::chrono::system_clock::time_point& now,
380 const std::chrono::system_clock::duration& correction
382 namespace sm = storages::mongo;
384 auto find_op =
this->MakeFindOperation(type, last_update, now, correction);
386 if (MongoCacheTraits::kIsSecondaryPreferred) {
392template <
class MongoCacheTraits>
395 auto ptr =
this->Get();
396 return std::make_unique<
typename MongoCacheTraits::DataType>(*ptr);
398 return std::make_unique<
typename MongoCacheTraits::DataType>();
404std::string GetMongoCacheSchema();
408template <
class MongoCacheTraits>
409yaml_config::Schema
MongoCache<MongoCacheTraits>::GetStaticConfigSchema() {
410 return yaml_config::MergeSchemas<