nghttpx: Faster configuration loading with lots of backends

This commit is contained in:
Tatsuhiro Tsujikawa 2017-12-01 00:28:36 +09:00
parent 97f1735cf5
commit 1ebb6810a1
5 changed files with 106 additions and 98 deletions

View File

@ -2768,10 +2768,12 @@ namespace {
int process_options(Config *config, int process_options(Config *config,
std::vector<std::pair<StringRef, StringRef>> &cmdcfgs) { std::vector<std::pair<StringRef, StringRef>> &cmdcfgs) {
std::array<char, STRERROR_BUFSIZE> errbuf; std::array<char, STRERROR_BUFSIZE> errbuf;
std::map<StringRef, size_t> pattern_addr_indexer;
if (conf_exists(config->conf_path.c_str())) { if (conf_exists(config->conf_path.c_str())) {
LOG(NOTICE) << "Loading configuration from " << config->conf_path; LOG(NOTICE) << "Loading configuration from " << config->conf_path;
std::set<StringRef> include_set; std::set<StringRef> include_set;
if (load_config(config, config->conf_path.c_str(), include_set) == -1) { if (load_config(config, config->conf_path.c_str(), include_set,
pattern_addr_indexer) == -1) {
LOG(FATAL) << "Failed to load configuration from " << config->conf_path; LOG(FATAL) << "Failed to load configuration from " << config->conf_path;
return -1; return -1;
} }
@ -2785,7 +2787,8 @@ int process_options(Config *config,
std::set<StringRef> include_set; std::set<StringRef> include_set;
for (auto &p : cmdcfgs) { for (auto &p : cmdcfgs) {
if (parse_config(config, p.first, p.second, include_set) == -1) { if (parse_config(config, p.first, p.second, include_set,
pattern_addr_indexer) == -1) {
LOG(FATAL) << "Failed to parse command-line argument."; LOG(FATAL) << "Failed to parse command-line argument.";
return -1; return -1;
} }

View File

@ -23,7 +23,6 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
#include "shrpx_api_downstream_connection.h" #include "shrpx_api_downstream_connection.h"
#include "shrpx_client_handler.h" #include "shrpx_client_handler.h"
#include "shrpx_upstream.h" #include "shrpx_upstream.h"
#include "shrpx_downstream.h" #include "shrpx_downstream.h"
@ -343,6 +342,7 @@ int APIDownstreamConnection::handle_backendconfig() {
downstreamconf->family = src->family; downstreamconf->family = src->family;
std::set<StringRef> include_set; std::set<StringRef> include_set;
std::map<StringRef, size_t> pattern_addr_indexer;
for (auto first = reinterpret_cast<const uint8_t *>(iov[0].iov_base), for (auto first = reinterpret_cast<const uint8_t *>(iov[0].iov_base),
last = first + iov[0].iov_len; last = first + iov[0].iov_len;
@ -376,7 +376,8 @@ int APIDownstreamConnection::handle_backendconfig() {
continue; continue;
} }
if (parse_config(&new_config, optid, opt, optval, include_set) != 0) { if (parse_config(&new_config, optid, opt, optval, include_set,
pattern_addr_indexer) != 0) {
send_reply(400, API_FAILURE); send_reply(400, API_FAILURE);
return 0; return 0;
} }

View File

@ -943,6 +943,7 @@ namespace {
// //
// This function returns 0 if it succeeds, or -1. // This function returns 0 if it succeeds, or -1.
int parse_mapping(Config *config, DownstreamAddrConfig &addr, int parse_mapping(Config *config, DownstreamAddrConfig &addr,
std::map<StringRef, size_t> &pattern_addr_indexer,
const StringRef &src_pattern, const StringRef &src_params) { const StringRef &src_pattern, const StringRef &src_params) {
// This returns at least 1 element (it could be empty string). We // This returns at least 1 element (it could be empty string). We
// will append '/' to all patterns, so it becomes catch-all pattern. // will append '/' to all patterns, so it becomes catch-all pattern.
@ -983,7 +984,6 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
auto &wildcard_patterns = routerconf.wildcard_patterns; auto &wildcard_patterns = routerconf.wildcard_patterns;
for (const auto &raw_pattern : mapping) { for (const auto &raw_pattern : mapping) {
auto done = false;
StringRef pattern; StringRef pattern;
auto slash = std::find(std::begin(raw_pattern), std::end(raw_pattern), '/'); auto slash = std::find(std::begin(raw_pattern), std::end(raw_pattern), '/');
if (slash == std::end(raw_pattern)) { if (slash == std::end(raw_pattern)) {
@ -1010,8 +1010,9 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
*p = '\0'; *p = '\0';
pattern = StringRef{iov.base, p}; pattern = StringRef{iov.base, p};
} }
for (auto &g : addr_groups) { auto it = pattern_addr_indexer.find(pattern);
if (g.pattern == pattern) { if (it != std::end(pattern_addr_indexer)) {
auto &g = addr_groups[(*it).second];
// Last value wins if we have multiple different affinity // Last value wins if we have multiple different affinity
// value under one group. // value under one group.
if (params.affinity.type != AFFINITY_NONE) { if (params.affinity.type != AFFINITY_NONE) {
@ -1029,8 +1030,7 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
} else if (g.affinity.type != params.affinity.type || } else if (g.affinity.type != params.affinity.type ||
g.affinity.cookie.name != params.affinity.cookie.name || g.affinity.cookie.name != params.affinity.cookie.name ||
g.affinity.cookie.path != params.affinity.cookie.path || g.affinity.cookie.path != params.affinity.cookie.path ||
g.affinity.cookie.secure != g.affinity.cookie.secure != params.affinity.cookie.secure) {
params.affinity.cookie.secure) {
LOG(ERROR) << "backend: affinity: multiple different affinity " LOG(ERROR) << "backend: affinity: multiple different affinity "
"configurations found in a single group"; "configurations found in a single group";
return -1; return -1;
@ -1042,15 +1042,11 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
g.redirect_if_not_tls = true; g.redirect_if_not_tls = true;
} }
g.addrs.push_back(addr); g.addrs.push_back(addr);
done = true;
break;
}
}
if (done) {
continue; continue;
} }
auto idx = addr_groups.size(); auto idx = addr_groups.size();
pattern_addr_indexer.emplace(pattern, idx);
addr_groups.emplace_back(pattern); addr_groups.emplace_back(pattern);
auto &g = addr_groups.back(); auto &g = addr_groups.back();
g.addrs.push_back(addr); g.addrs.push_back(addr);
@ -2387,13 +2383,16 @@ int option_lookup_token(const char *name, size_t namelen) {
} }
int parse_config(Config *config, const StringRef &opt, const StringRef &optarg, int parse_config(Config *config, const StringRef &opt, const StringRef &optarg,
std::set<StringRef> &included_set) { std::set<StringRef> &included_set,
std::map<StringRef, size_t> &pattern_addr_indexer) {
auto optid = option_lookup_token(opt.c_str(), opt.size()); auto optid = option_lookup_token(opt.c_str(), opt.size());
return parse_config(config, optid, opt, optarg, included_set); return parse_config(config, optid, opt, optarg, included_set,
pattern_addr_indexer);
} }
int parse_config(Config *config, int optid, const StringRef &opt, int parse_config(Config *config, int optid, const StringRef &opt,
const StringRef &optarg, std::set<StringRef> &included_set) { const StringRef &optarg, std::set<StringRef> &included_set,
std::map<StringRef, size_t> &pattern_addr_indexer) {
std::array<char, STRERROR_BUFSIZE> errbuf; std::array<char, STRERROR_BUFSIZE> errbuf;
char host[NI_MAXHOST]; char host[NI_MAXHOST];
uint16_t port; uint16_t port;
@ -2425,7 +2424,8 @@ int parse_config(Config *config, int optid, const StringRef &opt,
auto params = auto params =
mapping_end == std::end(optarg) ? mapping_end : mapping_end + 1; mapping_end == std::end(optarg) ? mapping_end : mapping_end + 1;
if (parse_mapping(config, addr, StringRef{mapping, mapping_end}, if (parse_mapping(config, addr, pattern_addr_indexer,
StringRef{mapping, mapping_end},
StringRef{params, std::end(optarg)}) != 0) { StringRef{params, std::end(optarg)}) != 0) {
return -1; return -1;
} }
@ -3126,7 +3126,8 @@ int parse_config(Config *config, int optid, const StringRef &opt,
} }
included_set.insert(optarg); included_set.insert(optarg);
auto rv = load_config(config, optarg.c_str(), included_set); auto rv =
load_config(config, optarg.c_str(), included_set, pattern_addr_indexer);
included_set.erase(optarg); included_set.erase(optarg);
if (rv != 0) { if (rv != 0) {
@ -3563,7 +3564,8 @@ int parse_config(Config *config, int optid, const StringRef &opt,
} }
int load_config(Config *config, const char *filename, int load_config(Config *config, const char *filename,
std::set<StringRef> &include_set) { std::set<StringRef> &include_set,
std::map<StringRef, size_t> &pattern_addr_indexer) {
std::ifstream in(filename, std::ios::binary); std::ifstream in(filename, std::ios::binary);
if (!in) { if (!in) {
LOG(ERROR) << "Could not open config file " << filename; LOG(ERROR) << "Could not open config file " << filename;
@ -3585,7 +3587,8 @@ int load_config(Config *config, const char *filename,
*eq = '\0'; *eq = '\0';
if (parse_config(config, StringRef{std::begin(line), eq}, if (parse_config(config, StringRef{std::begin(line), eq},
StringRef{eq + 1, std::end(line)}, include_set) != 0) { StringRef{eq + 1, std::end(line)}, include_set,
pattern_addr_indexer) != 0) {
return -1; return -1;
} }
} }

View File

@ -1139,20 +1139,26 @@ int option_lookup_token(const char *name, size_t namelen);
// stored into the object pointed by |config|. This function returns 0 // stored into the object pointed by |config|. This function returns 0
// if it succeeds, or -1. The |included_set| contains the all paths // if it succeeds, or -1. The |included_set| contains the all paths
// already included while processing this configuration, to avoid loop // already included while processing this configuration, to avoid loop
// in --include option. // in --include option. The |pattern_addr_indexer| contains a pair of
// pattern of backend, and its index in DownstreamConfig::addr_groups.
// It is introduced to speed up loading configuration file with lots
// of backends.
int parse_config(Config *config, const StringRef &opt, const StringRef &optarg, int parse_config(Config *config, const StringRef &opt, const StringRef &optarg,
std::set<StringRef> &included_set); std::set<StringRef> &included_set,
std::map<StringRef, size_t> &pattern_addr_indexer);
// Similar to parse_config() above, but additional |optid| which // Similar to parse_config() above, but additional |optid| which
// should be the return value of option_lookup_token(opt). // should be the return value of option_lookup_token(opt).
int parse_config(Config *config, int optid, const StringRef &opt, int parse_config(Config *config, int optid, const StringRef &opt,
const StringRef &optarg, std::set<StringRef> &included_set); const StringRef &optarg, std::set<StringRef> &included_set,
std::map<StringRef, size_t> &pattern_addr_indexer);
// Loads configurations from |filename| and stores them in |config|. // Loads configurations from |filename| and stores them in |config|.
// This function returns 0 if it succeeds, or -1. See parse_config() // This function returns 0 if it succeeds, or -1. See parse_config()
// for |include_set|. // for |include_set|.
int load_config(Config *config, const char *filename, int load_config(Config *config, const char *filename,
std::set<StringRef> &include_set); std::set<StringRef> &include_set,
std::map<StringRef, size_t> &pattern_addr_indexer);
// Parses header field in |optarg|. We expect header field is formed // Parses header field in |optarg|. We expect header field is formed
// like "NAME: VALUE". We require that NAME is non empty string. ":" // like "NAME: VALUE". We require that NAME is non empty string. ":"

View File

@ -68,51 +68,44 @@ void proc_wev_cb(struct ev_loop *loop, ev_timer *w, int revents) {
} }
} // namespace } // namespace
// DownstreamKey is used to index SharedDownstreamAddr in order to
// find the same configuration.
using DownstreamKey =
std::tuple<std::vector<std::tuple<StringRef, StringRef, size_t, size_t,
shrpx_proto, uint16_t, bool, bool, bool>>,
bool, int, StringRef, StringRef, int>;
namespace { namespace {
bool match_shared_downstream_addr( DownstreamKey create_downstream_key(
const std::shared_ptr<SharedDownstreamAddr> &lhs, const std::shared_ptr<SharedDownstreamAddr> &shared_addr) {
const std::shared_ptr<SharedDownstreamAddr> &rhs) { DownstreamKey dkey;
if (lhs->addrs.size() != rhs->addrs.size()) {
return false;
}
if (lhs->affinity.type != rhs->affinity.type || auto &addrs = std::get<0>(dkey);
lhs->redirect_if_not_tls != rhs->redirect_if_not_tls) { addrs.resize(shared_addr->addrs.size());
return false; auto p = std::begin(addrs);
for (auto &a : shared_addr->addrs) {
std::get<0>(*p) = a.host;
std::get<1>(*p) = a.sni;
std::get<2>(*p) = a.fall;
std::get<3>(*p) = a.rise;
std::get<4>(*p) = a.proto;
std::get<5>(*p) = a.port;
std::get<6>(*p) = a.host_unix;
std::get<7>(*p) = a.tls;
std::get<8>(*p) = a.dns;
++p;
} }
std::sort(std::begin(addrs), std::end(addrs));
if (lhs->affinity.type == AFFINITY_COOKIE && std::get<1>(dkey) = shared_addr->redirect_if_not_tls;
(lhs->affinity.cookie.name != rhs->affinity.cookie.name ||
lhs->affinity.cookie.path != rhs->affinity.cookie.path ||
lhs->affinity.cookie.secure != rhs->affinity.cookie.secure)) {
return false;
}
auto used = std::vector<bool>(lhs->addrs.size()); auto &affinity = shared_addr->affinity;
std::get<2>(dkey) = affinity.type;
std::get<3>(dkey) = affinity.cookie.name;
std::get<4>(dkey) = affinity.cookie.path;
std::get<5>(dkey) = affinity.cookie.secure;
for (auto &a : lhs->addrs) { return dkey;
size_t i;
for (i = 0; i < rhs->addrs.size(); ++i) {
if (used[i]) {
continue;
}
auto &b = rhs->addrs[i];
if (a.host == b.host && a.port == b.port && a.host_unix == b.host_unix &&
a.proto == b.proto && a.tls == b.tls && a.sni == b.sni &&
a.fall == b.fall && a.rise == b.rise && a.dns == b.dns) {
break;
}
}
if (i == rhs->addrs.size()) {
return false;
}
used[i] = true;
}
return true;
} }
} // namespace } // namespace
@ -182,6 +175,8 @@ void Worker::replace_downstream_config(
downstream_addr_groups_ = downstream_addr_groups_ =
std::vector<std::shared_ptr<DownstreamAddrGroup>>(groups.size()); std::vector<std::shared_ptr<DownstreamAddrGroup>>(groups.size());
std::map<DownstreamKey, size_t> addr_groups_indexer;
for (size_t i = 0; i < groups.size(); ++i) { for (size_t i = 0; i < groups.size(); ++i) {
auto &src = groups[i]; auto &src = groups[i];
auto &dst = downstream_addr_groups_[i]; auto &dst = downstream_addr_groups_[i];
@ -268,14 +263,11 @@ void Worker::replace_downstream_config(
// share the connection if patterns have the same set of backend // share the connection if patterns have the same set of backend
// addresses. // addresses.
auto end = std::begin(downstream_addr_groups_) + i;
auto it = std::find_if(
std::begin(downstream_addr_groups_), end,
[&shared_addr](const std::shared_ptr<DownstreamAddrGroup> &group) {
return match_shared_downstream_addr(group->shared_addr, shared_addr);
});
if (it == end) { auto dkey = create_downstream_key(shared_addr);
auto it = addr_groups_indexer.find(dkey);
if (it == std::end(addr_groups_indexer)) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "number of http/1.1 backend: " << num_http1 LOG(INFO) << "number of http/1.1 backend: " << num_http1
<< ", number of h2 backend: " << num_http2; << ", number of h2 backend: " << num_http2;
@ -291,12 +283,15 @@ void Worker::replace_downstream_config(
} }
dst->shared_addr = shared_addr; dst->shared_addr = shared_addr;
addr_groups_indexer.emplace(std::move(dkey), i);
} else { } else {
auto &g = *(std::begin(downstream_addr_groups_) + (*it).second);
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << dst->pattern << " shares the same backend group with " LOG(INFO) << dst->pattern << " shares the same backend group with "
<< (*it)->pattern; << g->pattern;
} }
dst->shared_addr = (*it)->shared_addr; dst->shared_addr = g->shared_addr;
} }
} }
} }