From e0c13518aedc499e1900c4dc0b36593e3011a7a6 Mon Sep 17 00:00:00 2001 From: Nadav Har'El Date: Wed, 11 Mar 2026 10:45:03 +0200 Subject: [PATCH 1/2] config: suppress named_value instantiation in every source file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config.hh is included by a large fraction of the codebase. It pulls in utils/config_file.hh, whose named_value template has its method bodies defined in config_file_impl.hh. Those bodies depend on three of the heaviest Boost headers – boost/program_options.hpp, boost/lexical_cast.hpp, and boost/regex.hpp – as well as yaml-cpp. Because the method bodies are reachable from config.hh, every translation unit that includes config.hh was silently instantiating all of named_value's methods (for each distinct T) and compiling that Boost/yaml-cpp machinery from scratch. Fix this by adding extern template struct declarations for all 32 distinct named_value specialisations used by db::config: - the 14 primitive/stdlib types go into utils/config_file.hh - the 18 db-specific types (enum_option<…>, seed_provider_type, etc.) go into db/config.hh Matching explicit template struct instantiation definitions are added in db/config.cc, which is already the only translation unit that includes config_file_impl.hh. As a result the Boost/yaml-cpp template machinery is compiled exactly once (in config.o) instead of being re-instantiated in every including TU. One subtlety: named_value has an explicit member specialisation of add_command_line_option. Per [temp.expl.spec], such a specialisation must be declared before any extern template declaration of the enclosing class template, so a forward declaration of the specialisation is added to config.hh ahead of the extern template line. Also, for some of the types we explicitly instantiated in db/config.cc, the named_value constructor calls config_type_for(), which we also need to provide explicit specializations - some of them we already had but some were missing. Signed-off-by: Nadav Har'El --- db/config.cc | 68 ++++++++++++++++++++++++++++++++++++++++++++ db/config.hh | 38 +++++++++++++++++++++++++ utils/config_file.hh | 19 +++++++++++++ 3 files changed, 125 insertions(+) diff --git a/db/config.cc b/db/config.cc index 067d2d6de1..ed49008bcc 100644 --- a/db/config.cc +++ b/db/config.cc @@ -273,6 +273,34 @@ const config_type& config_type_for +const config_type& config_type_for>() { + static config_type ct( + "experimental feature", printable_to_json>); + return ct; +} + +template <> +const config_type& config_type_for>() { + static config_type ct( + "replication strategy", printable_to_json>); + return ct; +} + +template <> +const config_type& config_type_for>() { + static config_type ct( + "consistency level", printable_to_json>); + return ct; +} + +template <> +const config_type& config_type_for() { + static config_type ct( + "error injection", printable_to_json); + return ct; +} + template <> const config_type& config_type_for>() { static config_type ct( @@ -1923,6 +1951,46 @@ std::unordered_map db::tablets_mode_t::map() template struct utils::config_file::named_value; +// Explicit instantiation definitions for all named_value specializations +// declared extern in config_file.hh and config.hh. This file is the only +// translation unit that includes config_file_impl.hh (which contains the +// full template bodies), so all the heavy boost / yaml-cpp machinery is +// compiled exactly once here instead of in every TU that includes config.hh. + +// Primitive / standard types (extern-declared in utils/config_file.hh): +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; + +// db-specific types (extern-declared in db/config.hh): +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value>; +template struct utils::config_file::named_value>; +template struct utils::config_file::named_value>; +template struct utils::config_file::named_value>; +template struct utils::config_file::named_value>; +template struct utils::config_file::named_value; +template struct utils::config_file::named_value>>; +template struct utils::config_file::named_value>>; +template struct utils::config_file::named_value>>; +template struct utils::config_file::named_value>; +template struct utils::config_file::named_value>>; +template struct utils::config_file::named_value>; + namespace utils { sstring diff --git a/db/config.hh b/db/config.hh index 6b718fb617..ee629dafcf 100644 --- a/db/config.hh +++ b/db/config.hh @@ -679,3 +679,41 @@ future<> update_relabel_config_from_file(const std::string& name); std::vector split_comma_separated_list(std::string_view comma_separated_list); } // namespace utils + +namespace utils { + +// Declaration of the explicit specialization for seed_provider_type's +// add_command_line_option must appear before the extern template declaration +// below, to satisfy [temp.expl.spec]: a member specialization must be +// declared before any explicit instantiation (including extern template) of +// the enclosing class template. +template <> +void config_file::named_value::add_command_line_option( + boost::program_options::options_description_easy_init&); + +} // namespace utils + +// Explicit instantiation declarations for named_value specializations +// that use db-specific types. The definitions live in db/config.cc. +// Together with the declarations in utils/config_file.hh (for primitive +// types), this ensures the heavy template bodies from config_file_impl.hh +// (boost::program_options, boost::lexical_cast, boost::regex, yaml-cpp) +// are compiled only once. +extern template struct utils::config_file::named_value; +extern template struct utils::config_file::named_value; +extern template struct utils::config_file::named_value; +extern template struct utils::config_file::named_value; +extern template struct utils::config_file::named_value; +extern template struct utils::config_file::named_value; +extern template struct utils::config_file::named_value>; +extern template struct utils::config_file::named_value>; +extern template struct utils::config_file::named_value>; +extern template struct utils::config_file::named_value>; +extern template struct utils::config_file::named_value>; +extern template struct utils::config_file::named_value; +extern template struct utils::config_file::named_value>>; +extern template struct utils::config_file::named_value>>; +extern template struct utils::config_file::named_value>>; +extern template struct utils::config_file::named_value>; +extern template struct utils::config_file::named_value>>; +extern template struct utils::config_file::named_value>; diff --git a/utils/config_file.hh b/utils/config_file.hh index 4ab0d75a88..096d44d044 100644 --- a/utils/config_file.hh +++ b/utils/config_file.hh @@ -336,5 +336,24 @@ const config_file::named_value& operator||(const config_file::named_value& extern template struct config_file::named_value; +// Explicit instantiation declarations for the most common named_value +// specializations. The definitions are in db/config.cc (which is the only +// TU that includes config_file_impl.hh and therefore has the full template +// bodies). This avoids re-compiling the heavy boost::program_options / +// boost::lexical_cast / yaml-cpp machinery in every TU that includes +// config.hh. +extern template struct config_file::named_value; +extern template struct config_file::named_value; +extern template struct config_file::named_value; +extern template struct config_file::named_value; +extern template struct config_file::named_value; +extern template struct config_file::named_value; +extern template struct config_file::named_value; +extern template struct config_file::named_value; +extern template struct config_file::named_value; +extern template struct config_file::named_value; +extern template struct config_file::named_value; +extern template struct config_file::named_value; + } From b411d436de98eb80d8f773a7205b3975aa053230 Mon Sep 17 00:00:00 2001 From: Nadav Har'El Date: Wed, 11 Mar 2026 12:41:44 +0200 Subject: [PATCH 2/2] config: move named_value method bodies out-of-line The previous commit added extern template declarations to suppress named_value instantiation in every translation units, but those only suppress non-inline members. All method bodies defined inside the class body were inline and thus exempt from extern template, so they were still emitted as weak symbols in every TU that used them. Fix this by moving all named_value method definitions out of the class body in config_file.hh and into config_file_impl.hh as out-of-line template definitions. Since config_file_impl.hh is included only by db/config.cc, utils/config_file.cc, sstables/compressor.cc, and ent/encryption/encryption_config.cc, the method bodies are now compiled in only those four TUs. Also add the two missing explicit instantiation pairs that caused linker errors: - named_value> in db/config.cc - named_value in encryption_config.cc --- db/config.cc | 1 + db/config.hh | 1 + ent/encryption/encryption_config.cc | 2 + ent/encryption/encryption_config.hh | 2 + utils/config_file.hh | 94 +++++----------------- utils/config_file_impl.hh | 119 ++++++++++++++++++++++++++++ 6 files changed, 144 insertions(+), 75 deletions(-) diff --git a/db/config.cc b/db/config.cc index ed49008bcc..18d7db8910 100644 --- a/db/config.cc +++ b/db/config.cc @@ -1990,6 +1990,7 @@ template struct utils::config_file::named_value>; template struct utils::config_file::named_value>>; template struct utils::config_file::named_value>; +template struct utils::config_file::named_value>; namespace utils { diff --git a/db/config.hh b/db/config.hh index ee629dafcf..2bd500a0c2 100644 --- a/db/config.hh +++ b/db/config.hh @@ -717,3 +717,4 @@ extern template struct utils::config_file::named_value>; extern template struct utils::config_file::named_value>>; extern template struct utils::config_file::named_value>; +extern template struct utils::config_file::named_value>; diff --git a/ent/encryption/encryption_config.cc b/ent/encryption/encryption_config.cc index 6874dd3ffe..672b7e66c3 100644 --- a/ent/encryption/encryption_config.cc +++ b/ent/encryption/encryption_config.cc @@ -175,3 +175,5 @@ public: }; } } cfg; + +template struct utils::config_file::named_value; diff --git a/ent/encryption/encryption_config.hh b/ent/encryption/encryption_config.hh index 44596c43f0..676e34456d 100644 --- a/ent/encryption/encryption_config.hh +++ b/ent/encryption/encryption_config.hh @@ -32,3 +32,5 @@ public: }; } + +extern template struct utils::config_file::named_value; diff --git a/utils/config_file.hh b/utils/config_file.hh index 096d44d044..c477462f37 100644 --- a/utils/config_file.hh +++ b/utils/config_file.hh @@ -168,96 +168,40 @@ public: config_source _source = config_source::None; value_status _value_status; struct the_value_type final : any_value { - the_value_type(T value) : value(std::move(value)) {} + the_value_type(T value); utils::updateable_value_source value; - virtual std::unique_ptr clone() const override { - return std::make_unique(value()); - } - virtual void update_from(const any_value* source) override { - auto typed_source = static_cast(source); - value.set(typed_source->value()); - } + std::unique_ptr clone() const override; + void update_from(const any_value* source) override; }; liveness _liveness; std::vector _allowed_values; protected: - updateable_value_source& the_value() { - any_value* av = _cf->_per_shard_values[_cf->s_shard_id][_per_shard_values_offset].get(); - return static_cast(av)->value; - } - const updateable_value_source& the_value() const { - return const_cast(this)->the_value(); - } - virtual const void* current_value() const override { - return &the_value().get(); - } + updateable_value_source& the_value(); + const updateable_value_source& the_value() const; + const void* current_value() const override; public: typedef T type; typedef named_value MyType; named_value(config_file* file, std::string_view name, std::string_view alias, liveness liveness_, value_status vs, const T& t = T(), std::string_view desc = {}, - std::initializer_list allowed_values = {}) - : config_src(file, name, alias, &config_type_for(), desc) - , _value_status(vs) - , _liveness(liveness_) - , _allowed_values(std::move(allowed_values)) { - file->add(*this, std::make_unique(std::move(t))); - } + std::initializer_list allowed_values = {}); named_value(config_file* file, std::string_view name, liveness liveness_, value_status vs, const T& t = T(), std::string_view desc = {}, - std::initializer_list allowed_values = {}) - : named_value(file, name, {}, liveness_, vs, t, desc, std::move(allowed_values)) { - } + std::initializer_list allowed_values = {}); named_value(config_file* file, std::string_view name, std::string_view alias, value_status vs, const T& t = T(), std::string_view desc = {}, - std::initializer_list allowed_values = {}) - : named_value(file, name, alias, liveness::MustRestart, vs, t, desc, allowed_values) { - } + std::initializer_list allowed_values = {}); named_value(config_file* file, std::string_view name, value_status vs, const T& t = T(), std::string_view desc = {}, - std::initializer_list allowed_values = {}) - : named_value(file, name, {}, liveness::MustRestart, vs, t, desc, allowed_values) { - } - value_status status() const noexcept override { - return _value_status; - } - config_source source() const noexcept override { - return _source; - } - bool is_set() const { - return _source > config_source::None; - } - MyType & operator()(const T& t, config_source src = config_source::Internal) { - if (!_allowed_values.empty() && std::find(_allowed_values.begin(), _allowed_values.end(), t) == _allowed_values.end()) { - throw std::invalid_argument(fmt::format("Invalid value for {}: got {} which is not inside the set of allowed values {}", name(), t, _allowed_values)); - } - the_value().set(t); - if (src > config_source::None) { - _source = src; - } - return *this; - } - MyType & operator()(T&& t, config_source src = config_source::Internal) { - if (!_allowed_values.empty() && std::find(_allowed_values.begin(), _allowed_values.end(), t) == _allowed_values.end()) { - throw std::invalid_argument(fmt::format("Invalid value for {}: got {} which is not inside the set of allowed values {}", name(), t, _allowed_values)); - } - the_value().set(std::move(t)); - if (src > config_source::None) { - _source = src; - } - return *this; - } - void set(T&& t, config_source src = config_source::None) { - operator()(std::move(t), src); - } - const T& operator()() const { - return the_value().get(); - } + std::initializer_list allowed_values = {}); + value_status status() const noexcept override; + config_source source() const noexcept override; + bool is_set() const; + MyType & operator()(const T& t, config_source src = config_source::Internal); + MyType & operator()(T&& t, config_source src = config_source::Internal); + void set(T&& t, config_source src = config_source::None); + const T& operator()() const; - operator updateable_value() const & { - return updateable_value(the_value()); - } + operator updateable_value() const &; - observer observe(std::function callback) const { - return the_value().observe(std::move(callback)); - } + observer observe(std::function callback) const; void add_command_line_option(bpo::options_description_easy_init&) override; void set_value(const YAML::Node&, config_source) override; diff --git a/utils/config_file_impl.hh b/utils/config_file_impl.hh index e4ce2edba7..2cbf7cb780 100644 --- a/utils/config_file_impl.hh +++ b/utils/config_file_impl.hh @@ -186,6 +186,125 @@ sstring hyphenate(const std::string_view&); } +template +utils::config_file::named_value::the_value_type::the_value_type(T value) + : value(std::move(value)) {} + +template +std::unique_ptr +utils::config_file::named_value::the_value_type::clone() const { + return std::make_unique(value()); +} + +template +void utils::config_file::named_value::the_value_type::update_from(const any_value* source) { + auto typed_source = static_cast(source); + value.set(typed_source->value()); +} + +template +utils::updateable_value_source& utils::config_file::named_value::the_value() { + any_value* av = _cf->_per_shard_values[_cf->s_shard_id][_per_shard_values_offset].get(); + return static_cast(av)->value; +} + +template +const utils::updateable_value_source& utils::config_file::named_value::the_value() const { + return const_cast(this)->the_value(); +} + +template +const void* utils::config_file::named_value::current_value() const { + return &the_value().get(); +} + +template +utils::config_file::named_value::named_value(config_file* file, std::string_view name, std::string_view alias, liveness liveness_, value_status vs, const T& t, std::string_view desc, + std::initializer_list allowed_values) + : config_src(file, name, alias, &config_type_for(), desc) + , _value_status(vs) + , _liveness(liveness_) + , _allowed_values(std::move(allowed_values)) { + file->add(*this, std::make_unique(std::move(t))); +} + +template +utils::config_file::named_value::named_value(config_file* file, std::string_view name, liveness liveness_, value_status vs, const T& t, std::string_view desc, + std::initializer_list allowed_values) + : named_value(file, name, {}, liveness_, vs, t, desc, std::move(allowed_values)) { +} + +template +utils::config_file::named_value::named_value(config_file* file, std::string_view name, std::string_view alias, value_status vs, const T& t, std::string_view desc, + std::initializer_list allowed_values) + : named_value(file, name, alias, liveness::MustRestart, vs, t, desc, allowed_values) { +} + +template +utils::config_file::named_value::named_value(config_file* file, std::string_view name, value_status vs, const T& t, std::string_view desc, + std::initializer_list allowed_values) + : named_value(file, name, {}, liveness::MustRestart, vs, t, desc, allowed_values) { +} + +template +utils::config_file::value_status utils::config_file::named_value::status() const noexcept { + return _value_status; +} + +template +utils::config_file::config_source utils::config_file::named_value::source() const noexcept { + return _source; +} + +template +bool utils::config_file::named_value::is_set() const { + return _source > config_source::None; +} + +template +utils::config_file::named_value& utils::config_file::named_value::operator()(const T& t, config_source src) { + if (!_allowed_values.empty() && std::find(_allowed_values.begin(), _allowed_values.end(), t) == _allowed_values.end()) { + throw std::invalid_argument(fmt::format("Invalid value for {}: got {} which is not inside the set of allowed values {}", name(), t, _allowed_values)); + } + the_value().set(t); + if (src > config_source::None) { + _source = src; + } + return *this; +} + +template +utils::config_file::named_value& utils::config_file::named_value::operator()(T&& t, config_source src) { + if (!_allowed_values.empty() && std::find(_allowed_values.begin(), _allowed_values.end(), t) == _allowed_values.end()) { + throw std::invalid_argument(fmt::format("Invalid value for {}: got {} which is not inside the set of allowed values {}", name(), t, _allowed_values)); + } + the_value().set(std::move(t)); + if (src > config_source::None) { + _source = src; + } + return *this; +} + +template +void utils::config_file::named_value::set(T&& t, config_source src) { + operator()(std::move(t), src); +} + +template +const T& utils::config_file::named_value::operator()() const { + return the_value().get(); +} + +template +utils::config_file::named_value::operator utils::updateable_value() const & { + return updateable_value(the_value()); +} + +template +utils::observer utils::config_file::named_value::observe(std::function callback) const { + return the_value().observe(std::move(callback)); +} + template void utils::config_file::named_value::add_command_line_option(boost::program_options::options_description_easy_init& init) { const auto hyphenated_name = hyphenate(name());