OZO 「お象」
Boost.Asio and libpq based asynchronous PostgreSQL unofficial header-only C++17 client library.
Role-Based Execution & Fallback

Description

Failover operation by role-based fallback.

This type of failover strategy is dedicated to a DBM cluster with several hosts are playing different roles like Master, Replica, and so on.

For example, you have a high-load and high-availability system which should provide newest data from a master host under normal conditions, but it is ok to have outdated data from a replica host if the master is down or under some overload conditions.

For that kind of systems, the Role-Based Execution & Fallback strategy is.

The base entities are Roles which are represented by ozo::failover::role template specializations. Every role can recover several error conditions which should be defined via ozo::failover::can_recover customization. So if a new recover condition set is needed - a new role should be defined. This mechanism of static recovery conditions definition is a trade-off between framework complexity and usability.

There is a strategy which uses the roles. It is implemented via ozo::failover::role_based_strategy class and can be instantiated via ozo::failover::role_based function.

The strategy may work only with a special type of ConnectionProvider which allows being bound to a specific role. Such provider is modelled by ozo::failover::role_based_connection_provider. It has an additional method ozo::failover::role_based_connection_provider::rebind_role which bind the provider to a specific role. Such a mechanism allows to switch between roles and get connections to their hosts during the execution of the strategy.

The example of the strategy use can be found in examples/role_based_request.cpp .

#include <ozo/connection_info.h>
#include <ozo/request.h>
#include <ozo/shortcuts.h>
#include <ozo/failover/role_based.h>
#include <boost/asio/io_service.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>
namespace asio = boost::asio;
namespace hana = boost::hana;
namespace failover = ozo::failover;
const auto print_error = [](ozo::error_code ec, const auto& conn) {
std::cout << "error code message: \"" << ec.message();
// Here we should check if the connection is in null state to avoid UB.
if (!ozo::is_null_recursive(conn)) {
std::cout << "\", libpq error message: \"" << ozo::error_message(conn)
<< "\", error context: \"" << ozo::get_error_context(conn);
}
std::cout << "\"";
};
const auto print_fallback = [](ozo::error_code ec, const auto& conn, const auto& fallback) {
print_error(ec, conn);
// We can print information about fallback will be used for next try
if constexpr (decltype(fallback.role() == ozo::failover::master)::value) {
std::cout << " fallback is \"master\"" << std::endl;
} else {
std::cout << " fallback is \"replica\"" << std::endl;
}
};
int main(int argc, char **argv) {
std::cout << "OZO role-based request failover example" << std::endl;
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " <master connection string> <replica connection string>\n";
return 1;
}
asio::io_context io;
// Here we provide a mapping of roles to connection strings
);
asio::spawn(io, [&] (asio::yield_context yield) {
using namespace ozo::literals;
using namespace std::chrono_literals;
// Here we will try operation on master first and then replica if any problem will take place
// Each try will have its own time constraint
// master try will be limited by 1/2 sec.
// replica try will be limited by (1 - t(1st try)) / 2 sec, which is not less than 1/2 sec.
// We want to print out information about retries
.set(opt::on_fallback = print_fallback);
// Here a request call with role based failover strategy
auto conn = ozo::request[roles](conn_info[io], "SELECT 1"_SQL, 1s, ozo::into(result), yield[ec]);
// When request is completed we check is there an error.
if (ec) {
std::cout << "Request failed; ";
print_error(ec, conn);
std::cout << std::endl;
return;
}
// Just print request result
std::cout << "Selected:" << std::endl;
for (auto value : result) {
std::cout << std::get<0>(value) << std::endl;
}
});
io.run();
return 0;
}

Classes

struct  ozo::failover::role_based_options
 Options for role-based failover. More...
 
class  ozo::failover::role_based_connection_provider< Source >
 
struct  ozo::failover::role_based_connection_source< SourcesMap, Role >
 
class  ozo::failover::role_based_strategy< Options >
 Role-based strategy. More...
 

Functions

template<typename Role >
constexpr auto ozo::failover::can_recover (const Role &role, const error_code &ec)
 Determines if an error can be recovered by executing an operation on host with a given role. More...
 
template<typename Key , typename Value , typename ... Pairs>
constexpr decltype(auto) ozo::failover::make_role_based_connection_source (hana::pair< Key, Value > p1, Pairs &&...ps)
 
template<typename ... Roles>
constexpr auto ozo::failover::role_based (Roles ...roles)
 

Variables

constexpr role< class master_tag > ozo::failover::master
 
constexpr role< class replica_tag > ozo::failover::replica
 

Function Documentation

◆ can_recover()

template<typename Role >
constexpr auto ozo::failover::can_recover ( const Role &  role,
const error_code ec 
)
constexpr

Determines if an error can be recovered by executing an operation on host with a given role.

Parameters
role— role which should be checked
ec— error code from the previous try of an operation execution
Returns
true — a role can recover given error code and should be tried to recover an operation
false — a role can not recover given error code and should not be tried to recover an operation

Customization Point

This function should be customized for a new role via ozo::failover::can_recover_impl template specialization.

E.g. for ozo::failover::replica role the specialization may look like this:

template <>
struct can_recover_impl<std::decay_t<decltype(replica)>> {
static constexpr auto apply(const std::decay_t<decltype(replica)>&, const error_code& ec) {
}
};

Such a customization provides an ability to use roles objects which contains some additional compile-time or run-time information. E.g.:

namespace demo {
struct master_with_custom_error_conditions {
std::vector<ozo::errc::code> conditions;
};
constexpr master_with_custom_error_conditions master;
} // namespace demo
namespace ozo::failover {
template <>
struct can_recover_impl<std::decay_t<decltype(demo::master)>> {
static constexpr auto apply(const std::decay_t<decltype(demo::master)>& r, const error_code& ec) {
return std::find_if(r.conditions.begin(), r.conditions.end(),
[&](const auto& errc){ retrun ec == errc;}) != r.conditions.end();
}
};
} // namespace ozo::failover

◆ make_role_based_connection_source()

template<typename Key , typename Value , typename ... Pairs>
constexpr decltype(auto) ozo::failover::make_role_based_connection_source ( hana::pair< Key, Value >  p1,
Pairs &&...  ps 
)
constexpr

Creates connection source which dispatches given connection sources for respective roles.

Example

Specify different ozo::connection_info objects for different roles.

#include <ozo/failover/role_based.h>
//...
);

◆ role_based()

template<typename ... Roles>
constexpr auto ozo::failover::role_based ( Roles ...  roles)
constexpr

Try to perform an operation on first role with fallback to next items of specified sequence which should recover an error. E.g., in case of sequence consists of role1, role2, role3 and role4. If role1 cause an error which could be recovered with role3 and can not be recovered with role2 then the role2 will be skipped and operation failover continues from fallback role3.

Parameters
roles— variadic of roles to try operation on.
Returns
ozo::failover::role_based_strategy specialization.
Note
This strategy works only with ozo::failover::role_based_connection_provider ConnectionProvider implementation.

Time Constraint

Operation time constraint interval will be divided between tries as follow.

Given operation has a time constraint interval T, each try would have its own time constraint according to the rule:

Try number Time constraint
1 T/n
2 (T - t1) / (n - 1)
3 (T - (t1 + t2)) / (n - 2)
...
N (T - (t1 + t2 + ... + tn-1)))

, there

  • ti — actual time of ith try,
  • n - total number of roles.

Example

Try to perform the request on master once and then twice on replica if error code is recoverable.

Each try has own time duration constraint calculated as:

  • for the 1st try: 0,5/3 sec.
  • for the 2nd try: (0,5 - t1) / 2 >= 0,5/3 sec.
  • for the 3rd try: 0,5 - (t1 + t2) >= 0,5/3 sec.
);
//...
ozo::request[fallback](conn_info[io], query, .5s, out, yield);
See also
ozo::failover::role_based_strategy, ozo::failover::role_based_connection_provider

Variable Documentation

◆ master

constexpr role<class master_tag> ozo::failover::master
constexpr

General purpose master (read/write) host role.

This role can recover these error conditions

  • ozo::errc::connection_error,
  • ozo::errc::type_mismatch,
  • ozo::errc::protocol_error,
  • ozo::errc::database_readonly
Note
In most cases, the user should define custom roles to specify conditions to recover.
See also
ozo::failover::can_recover()

◆ replica

constexpr role<class replica_tag> ozo::failover::replica
constexpr

General purpose replica (read-only) host role.

This role can recover these error conditions

  • ozo::errc::connection_error,
  • ozo::errc::type_mismatch,
  • ozo::errc::protocol_error
Note
In most cases, the user should define custom roles to specify conditions to recover.
See also
ozo::failover::can_recover()
ozo::request
decltype(auto) request(ConnectionProvider &&provider, BinaryQueryConvertible &&query, TimeConstraint time_constraint, Out out, CompletionToken &&token)
Executes query and retrives a result from a database with time constraint.
ozo::failover::make_role_based_connection_source
constexpr decltype(auto) make_role_based_connection_source(hana::pair< Key, Value > p1, Pairs &&...ps)
Definition: role_based.h:419
ozo::error_code
boost::system::error_code error_code
Error code representation of the library.
Definition: error.h:38
ozo::error_message
std::string_view error_message(const Connection &conn)
Get native libpq error message.
Definition: connection.h:97
ozo::rows_of
std::vector< typed_row< Ts... > > rows_of
Shortcut for easy result container definition.
Definition: shortcuts.h:37
ozo::errc::connection_error
@ connection_error
connection-related error condition, incorporates ozo, libpq and Boost.Asio connection errors
Definition: error.h:399
ozo::into
constexpr auto into(T &v)
Shortcut for create result container back inserter.
Definition: shortcuts.h:85
ozo::is_null_recursive
constexpr bool is_null_recursive(T &&v) noexcept
Indicates if one of unwrapped values is in null state.
Definition: recursive.h:78
ozo::connection_info
Connection source to a single host.
Definition: connection_info.h:28
ozo::failover::replica
constexpr role< class replica_tag > replica
Definition: role_based.h:122
ozo::failover::master
constexpr role< class master_tag > master
Definition: role_based.h:93
ozo::failover::role_based
constexpr auto role_based(Roles ...roles)
Definition: role_based.h:652
ozo::failover::role_based_options
Options for role-based failover.
Definition: role_based.h:58
ozo::errc::type_mismatch
@ type_mismatch
result type mismatch, indicates types mismatch between result of query and expected result
Definition: error.h:402
ozo::errc::protocol_error
@ protocol_error
specific protocol-related errors
Definition: error.h:403
ozo::result
basic_result< pg::result > result
Database raw result representation.
Definition: result.h:444
ozo::get_error_context
const auto & get_error_context(const Connection &conn)
Get additional error context.
Definition: connection.h:124