MXS-2555 Implement cache eviction (or query re-measurment) strategy

This commit is contained in:
Niclas Antti
2019-06-18 17:02:23 +03:00
parent f5615cc174
commit a950715176
4 changed files with 92 additions and 90 deletions

View File

@ -13,44 +13,6 @@
#include "performance.hh"
#include <cstdio>
#include <fstream>
#include <mutex>
const std::string file_version = "Alpha"; // if a file has a different version string, discard it.
CanonicalPerformance::CanonicalPerformance()
: m_nChanges(0)
{
}
bool CanonicalPerformance::insert(const std::string& canonical, const PerformanceInfo& perf)
{
bool saved = m_perfs.insert({canonical, perf}).second;
m_nChanges += saved;
return saved;
}
bool CanonicalPerformance::remove(const std::string& canonical)
{
auto erased = m_perfs.erase(canonical);
m_nChanges += erased;
return erased;
}
PerformanceInfo CanonicalPerformance::find(const std::string& canonical)
{
auto it = m_perfs.find(canonical);
return it == m_perfs.end() ? PerformanceInfo() : it->second;
}
void CanonicalPerformance::clear()
{
m_perfs.clear();
m_nChanges = 0;
}
std::string show_some(const std::string& str, int nchars)
{
int sz = str.length();
@ -64,7 +26,6 @@ std::string show_some(const std::string& str, int nchars)
}
}
// These are TODOs for the GA version. The Beta version will not have persistence.
// 1. Read the file once at startup. There might also be a need to do cleanup
// of the file if the configuration has changed.
@ -78,11 +39,3 @@ std::string show_some(const std::string& str, int nchars)
// probably be dropped immediately.
// 6. Save all data at shutdown.
// Start using xxhash
//
// Expiration rules. At least these rules should be implemented:
// 1. Since the various engines have different setup times during the first few queries,
// this should be taken into account (not implemented).
// 2. Expire entries after X minutes.
// 3. If the measured time is very different from the stored one (+/20%),
// expire the entry (not implemented).
// More rules can be found out by testing.

View File

@ -19,8 +19,8 @@
#include <unordered_map>
/** Class PerformanceInfo is a basic structure for storing a Host and Duration pair, along with
* the time it was created.
/** PerformanceInfo is a class that on the one hand provides routeQuery() with performance/routing
* information and on the other has data for class SmartRouter to manage the life-time of a measurment.
*/
class PerformanceInfo
{
@ -40,38 +40,24 @@ public:
/** Duration since this PerformanceInfo was created
*/
maxbase::Duration age() const;
/** Managed and used only by class SmartRouter. */
void set_eviction_schedule(size_t es);
size_t eviction_schedule() const;
/** Managed and used only by class SmartRouter. */
void set_updating(bool val);
bool is_updating() const;
private:
maxbase::Host m_host;
maxbase::Duration m_duration;
int m_eviction_schedule = 0;
bool m_updating = false;
maxbase::TimePoint m_creation_time = maxbase::Clock::now();
};
/** class CanonicalPerformance holds the performance
* info gathered since the start of Maxscale.
* The Beta release will not perist to file.
*/
class CanonicalPerformance
{
public:
explicit CanonicalPerformance();
/** Insert if not already inserted and return true, else false. */
bool insert(const std::string& canonical, const PerformanceInfo& perf);
/** Remove if entry exists and return true, else false. */
bool remove(const std::string& canonical);
/** If entry does not exists, returned PerformanceInfo::is_valid()==false */
PerformanceInfo find(const std::string& canonical);
void clear();
private:
std::unordered_map<std::string, PerformanceInfo> m_perfs;
mutable int m_nChanges;
};
// For logging. Shortens str to nchars and adds "..." TODO move somewhere more appropriate
std::string show_some(const std::string& str, int nchars = 70);
@ -106,3 +92,23 @@ inline maxbase::Duration PerformanceInfo::age() const
{
return maxbase::Clock::now() - m_creation_time;
}
inline void PerformanceInfo::set_eviction_schedule(size_t es)
{
m_eviction_schedule = es;
}
inline size_t PerformanceInfo::eviction_schedule() const
{
return m_eviction_schedule;
}
inline void PerformanceInfo::set_updating(bool val)
{
m_updating = val;
}
inline bool PerformanceInfo::is_updating() const
{
return m_updating;
}

View File

@ -197,29 +197,69 @@ uint64_t SmartRouter::getCapabilities()
return RCAP_TYPE_TRANSACTION_TRACKING | RCAP_TYPE_CONTIGUOUS_INPUT | RCAP_TYPE_CONTIGUOUS_OUTPUT;
}
// Eviction schedule
// Two reasons to evict, and re-measure canonicals.
// 1. When connections are initially created there is more overhead in maxscale and at the server,
// which can (and does) lead to the wrong performance conclusions.
// 2. Depending on the contents and number of rows in tables, different database engines
// have different performance advantages (InnoDb is always very fast for small tables).
//
// TODO make configurable, maybe.
static std::array<maxbase::Duration, 4> eviction_schedules =
{
std::chrono::minutes(2),
std::chrono::minutes(5),
std::chrono::minutes(10),
std::chrono::minutes(20)
};
// TODO need to add the default db to the key (or hash)
PerformanceInfo SmartRouter::perf_find(const std::string& canonical)
{
std::unique_lock<std::mutex> guard(m_perf_mutex);
auto perf = m_performance.find(canonical);
if (perf.is_valid() && perf.age() > std::chrono::minutes(1)) // TODO to config, but not yet
auto perf_it = m_perfs.find(canonical);
if (perf_it != end(m_perfs) && !perf_it->second.is_updating())
{
m_performance.remove(canonical);
return PerformanceInfo();
if (perf_it->second.age() > eviction_schedules[perf_it->second.eviction_schedule()])
{
MXS_SINFO("Trigger re-measure, schedule "
<< eviction_schedules[perf_it->second.eviction_schedule()]
<< ", perf: " << perf_it->second.host()
<< ", " << perf_it->second.duration() << ", "
<< show_some(canonical));
// Not actually evicting, but trigger a re-measure only for this caller (this worker).
perf_it->second.set_updating(true);
perf_it = end(m_perfs);
}
}
return perf;
return perf_it != end(m_perfs) ? perf_it->second : PerformanceInfo();
}
bool SmartRouter::perf_update(const std::string& canonical, const PerformanceInfo& perf)
void SmartRouter::perf_update(const std::string& canonical, const PerformanceInfo& perf)
{
std::unique_lock<std::mutex> guard(m_perf_mutex);
auto ret = m_performance.insert(canonical, perf);
if (ret)
auto perf_it = m_perfs.find(canonical);
if (perf_it != end(m_perfs))
{
MXS_SDEBUG("Stored perf " << perf.duration() << ' ' << perf.host() << ' ' << show_some(canonical));
}
MXS_SINFO("Update perf: from "
<< perf_it->second.host() << ", " << perf_it->second.duration()
<< " to " << perf.host() << ", " << perf.duration()
<< ", " << show_some(canonical));
return ret;
size_t schedule = perf_it->second.eviction_schedule();
perf_it->second = perf;
perf_it->second.set_eviction_schedule(std::min(++schedule, eviction_schedules.size() - 1));
perf_it->second.set_updating(false);
}
else
{
m_perfs.insert({canonical, perf});
MXS_SDEBUG("Stored new perf: " << perf.host() << ", " << perf.duration()
<< ", " << show_some(canonical));
}
}

View File

@ -26,7 +26,7 @@
class SmartRouterSession;
/** class Smartrouter. Only defines the mxs::Router<> functions needed for all routers.
/** class Smartrouter. Contains and manages the performance info.
*/
class SmartRouter : public mxs::Router<SmartRouter, SmartRouterSession>
{
@ -83,12 +83,15 @@ public:
/** Thread safe update/insert a PerformanceInfo. Some entry expiration handling is done here.
*/
bool perf_update(const std::string& canonical, const PerformanceInfo& perf);
void perf_update(const std::string& canonical, const PerformanceInfo& perf);
private:
SmartRouter(SERVICE* service);
Config m_config;
CanonicalPerformance m_performance;
std::mutex m_perf_mutex;
Config m_config;
using Perfs = std::unordered_map<std::string, PerformanceInfo>;
std::mutex m_perf_mutex;
Perfs m_perfs;
};