userver: userver/storages/postgres/cluster.hpp Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
cluster.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/cluster.hpp
4/// @brief @copybrief storages::postgres::Cluster
5
6#include <memory>
7
8#include <userver/clients/dns/resolver_fwd.hpp>
9#include <userver/dynamic_config/source.hpp>
10#include <userver/engine/task/task_processor_fwd.hpp>
11#include <userver/engine/task/task_with_result.hpp>
12#include <userver/error_injection/settings_fwd.hpp>
13#include <userver/testsuite/postgres_control.hpp>
14#include <userver/testsuite/tasks.hpp>
15
16#include <userver/storages/postgres/cluster_types.hpp>
17#include <userver/storages/postgres/database.hpp>
18#include <userver/storages/postgres/detail/non_transaction.hpp>
19#include <userver/storages/postgres/notify.hpp>
20#include <userver/storages/postgres/options.hpp>
21#include <userver/storages/postgres/query.hpp>
22#include <userver/storages/postgres/query_queue.hpp>
23#include <userver/storages/postgres/statistics.hpp>
24#include <userver/storages/postgres/transaction.hpp>
25
26/// @page pg_topology uPg: Cluster topology discovery
27///
28/// @par Principles of PgaaS role determination
29/// - Every host except master is in recovery state from PostgreSQL's POV.
30/// This means the check 'select pg_is_in_recovery()' returns `false` for the
31/// master and `true` for every other host type.
32/// - Some hosts are in sync slave mode. This may be determined by executing
33/// 'show synchronous_standby_names' on the master.
34/// See
35/// https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-SYNCHRONOUS-STANDBY-NAMES
36/// for more information.
37///
38/// @par PgaaS sync slaves lag
39/// By default, PgaaS synchronous slaves are working with 'synchronous_commit'
40/// set to 'remote_apply'. Therefore, sync slave may be lagging behind the
41/// master and thus is not truly 'synchronous' from the reader's POV,
42/// but things may change with time.
43///
44/// @par Implementation
45/// Topology update runs every second.
46///
47/// Every host is assigned a connection with special ID (4100200300).
48/// Using this connection we check for host availability, writability
49/// (master detection) and perform RTT measurements.
50///
51/// After the initial check we know about master presence and RTT for each host.
52/// Master host is queried about synchronous replication status. We use this
53/// info to identify synchronous slaves and to detect "quorum commit" presence.
54///
55///
56/// ----------
57///
58/// @htmlonly <div class="bottom-nav"> @endhtmlonly
59/// ⇦ @ref pg_errors | @ref scripts/docs/en/userver/pg_connlimit_mode_auto.md ⇨
60/// @htmlonly </div> @endhtmlonly
61
62USERVER_NAMESPACE_BEGIN
63
64namespace components {
65class Postgres;
66} // namespace components
67
68namespace storages::postgres {
69
70namespace detail {
71
72class ClusterImpl;
73using ClusterImplPtr = std::unique_ptr<ClusterImpl>;
74
75} // namespace detail
76
77/// @ingroup userver_clients
78///
79/// @brief Interface for executing queries on a cluster of PostgreSQL servers
80///
81/// See @ref pg_user_row_types "Typed PostgreSQL results" for usage examples of
82/// the storages::postgres::ResultSet.
83///
84/// Usually retrieved from components::Postgres component.
85///
86/// @todo Add information about topology discovery
87class Cluster {
88public:
89 /// Cluster constructor
90 /// @param dsns List of DSNs to connect to
91 /// @param resolver asynchronous DNS resolver
92 /// @param bg_task_processor task processor for blocking connection operations
93 /// @param cluster_settings struct with settings fields:
94 /// task_data_keys_settings - settings for per-handler command controls
95 /// topology_settings - settings for host discovery
96 /// pool_settings - settings for connection pools
97 /// conn_settings - settings for individual connections
98 /// @param default_cmd_ctls default command execution options
99 /// @param testsuite_pg_ctl command execution options customizer for testsuite
100 /// @param ei_settings error injection settings
101 /// @param testsuite_tasks see @ref testsuite::TestsuiteTasks
102 /// @param config_source see @ref dynamic_config::Source
103 /// @param metrics metrics storage for alerts
104 /// @param shard_number shard number
105 /// @note When `max_connection_pool_size` is reached, and no idle connections
106 /// available, `PoolError` is thrown for every new connection
107 /// request
109 DsnList dsns,
110 clients::dns::Resolver* resolver,
111 engine::TaskProcessor& bg_task_processor,
112 const ClusterSettings& cluster_settings,
113 DefaultCommandControls&& default_cmd_ctls,
114 const testsuite::PostgresControl& testsuite_pg_ctl,
115 const error_injection::Settings& ei_settings,
116 testsuite::TestsuiteTasks& testsuite_tasks,
117 dynamic_config::Source config_source,
118 USERVER_NAMESPACE::utils::statistics::MetricsStoragePtr metrics,
119 int shard_number
120 );
121 ~Cluster();
122
123 /// Get cluster statistics
124 ///
125 /// The statistics object is too big to fit on stack
126 ClusterStatisticsPtr GetStatistics() const;
127
128 /// @name Transaction start
129 /// @{
130
131 /// Start a transaction in any available connection depending on transaction
132 /// options.
133 ///
134 /// If the transaction is RW, will start transaction in a connection
135 /// to master. If the transaction is RO, will start trying connections
136 /// starting with slaves.
137 /// @throws ClusterUnavailable if no hosts are available
138 Transaction Begin(const TransactionOptions&, OptionalCommandControl = {});
139
140 /// Start a transaction in a connection with specified host selection rules.
141 ///
142 /// If the requested host role is not available, may fall back to another
143 /// host role, see ClusterHostType.
144 /// If the transaction is RW, only master connection can be used.
145 /// @throws ClusterUnavailable if no hosts are available
146 Transaction Begin(ClusterHostTypeFlags, const TransactionOptions&, OptionalCommandControl = {});
147
148 /// Start a named transaction in any available connection depending on
149 /// transaction options.
150 ///
151 /// If the transaction is RW, will start transaction in a connection
152 /// to master. If the transaction is RO, will start trying connections
153 /// starting with slaves.
154 /// `name` is used to set command control in config at runtime.
155 /// @throws ClusterUnavailable if no hosts are available
156 Transaction Begin(std::string name, const TransactionOptions&);
157
158 /// Start a named transaction in a connection with specified host selection
159 /// rules.
160 ///
161 /// If the requested host role is not available, may fall back to another
162 /// host role, see ClusterHostType.
163 /// If the transaction is RW, only master connection can be used.
164 /// `name` is used to set command control in config at runtime.
165 /// @throws ClusterUnavailable if no hosts are available
166 Transaction Begin(std::string name, ClusterHostTypeFlags, const TransactionOptions&);
167 /// @}
168
169 /// Start a query queue with specified host selection rules and timeout for
170 /// acquiring a connection.
171 [[nodiscard]] QueryQueue CreateQueryQueue(ClusterHostTypeFlags flags);
172
173 /// Start a query queue with specified host selection rules and timeout for
174 /// acquiring a connection.
175 [[nodiscard]] QueryQueue CreateQueryQueue(ClusterHostTypeFlags flags, TimeoutDuration acquire_timeout);
176
177 /// @name Single-statement query in an auto-commit transaction
178 /// @{
179
180 /// @brief Execute a statement at host of specified type.
181 /// @note You must specify at least one role from ClusterHostType here
182 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
183 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
184 ///
185 /// @snippet storages/postgres/tests/landing_test.cpp Exec sample
186 ///
187 /// @warning Do NOT create a query string manually by embedding arguments!
188 /// It leads to vulnerabilities and bad performance. Either pass arguments
189 /// separately, or use storages::postgres::ParameterScope.
190 template <typename... Args>
191 ResultSet Execute(ClusterHostTypeFlags, const Query& query, const Args&... args);
192
193 /// @brief Execute a statement with specified host selection rules and command
194 /// control settings.
195 /// @note You must specify at least one role from ClusterHostType here
196 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
197 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
198 ///
199 /// @warning Do NOT create a query string manually by embedding arguments!
200 /// It leads to vulnerabilities and bad performance. Either pass arguments
201 /// separately, or use storages::postgres::ParameterScope.
202 template <typename... Args>
203 ResultSet Execute(ClusterHostTypeFlags, OptionalCommandControl, const Query& query, const Args&... args);
204
205 /// @brief Execute a statement with stored arguments and specified host
206 /// selection rules.
207 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
208 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
209 ///
210 /// @warning Do NOT create a query string manually by embedding arguments!
211 /// It leads to vulnerabilities and bad performance. Either pass arguments
212 /// separately, or use storages::postgres::ParameterScope.
213 ResultSet Execute(ClusterHostTypeFlags flags, const Query& query, const ParameterStore& store);
214
215 /// @brief Execute a statement with stored arguments, specified host selection
216 /// rules and command control settings.
217 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
218 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
219 ///
220 /// @warning Do NOT create a query string manually by embedding arguments!
221 /// It leads to vulnerabilities and bad performance. Either pass arguments
222 /// separately, or use storages::postgres::ParameterScope.
224 ClusterHostTypeFlags flags,
225 OptionalCommandControl statement_cmd_ctl,
226 const Query& query,
227 const ParameterStore& store
228 );
229 /// @}
230
231 /// @brief Listen for notifications on channel
232 /// @warning Each NotifyScope owns a single connection taken from the pool,
233 /// which effectively decreases the number of usable connections
234 NotifyScope Listen(std::string_view channel, OptionalCommandControl = {});
235
236 /// Replaces globally updated command control with a static user-provided one
238
239 /// Returns current default command control
241
242 void SetHandlersCommandControl(CommandControlByHandlerMap handlers_command_control);
243
244 void SetQueriesCommandControl(CommandControlByQueryMap queries_command_control);
245
246 /// @cond
247 /// Updates default command control from global config (if not set by user)
248 void ApplyGlobalCommandControlUpdate(CommandControl);
249 /// @endcond
250
251 /// Replaces cluster connection settings.
252 ///
253 /// Connections with an old settings will be dropped and reestablished.
255
256 void SetPoolSettings(const PoolSettings& settings);
257
258 void SetTopologySettings(const TopologySettings& settings);
259
260 void SetStatementMetricsSettings(const StatementMetricsSettings& settings);
261
262 void SetDsnList(const DsnList&);
263
264private:
265 detail::NonTransaction Start(ClusterHostTypeFlags, OptionalCommandControl);
266
267 OptionalCommandControl GetQueryCmdCtl(const std::string& query_name) const;
268 OptionalCommandControl GetHandlersCmdCtl(OptionalCommandControl cmd_ctl) const;
269
270 detail::ClusterImplPtr pimpl_;
271};
272
273template <typename... Args>
274ResultSet Cluster::Execute(ClusterHostTypeFlags flags, const Query& query, const Args&... args) {
275 return Execute(flags, OptionalCommandControl{}, query, args...);
276}
277
278template <typename... Args>
280 ClusterHostTypeFlags flags,
281 OptionalCommandControl statement_cmd_ctl,
282 const Query& query,
283 const Args&... args
284) {
285 if (!statement_cmd_ctl && query.GetName()) {
286 statement_cmd_ctl = GetQueryCmdCtl(query.GetName()->GetUnderlying());
287 }
288 statement_cmd_ctl = GetHandlersCmdCtl(statement_cmd_ctl);
289 auto ntrx = Start(flags, statement_cmd_ctl);
290 return ntrx.Execute(statement_cmd_ctl, query, args...);
291}
292
293} // namespace storages::postgres
294
295USERVER_NAMESPACE_END