MXS-2555 Implement cache eviction (or query re-measurment) strategy
This commit is contained in:
@ -13,44 +13,6 @@
|
|||||||
|
|
||||||
#include "performance.hh"
|
#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)
|
std::string show_some(const std::string& str, int nchars)
|
||||||
{
|
{
|
||||||
int sz = str.length();
|
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.
|
// 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
|
// 1. Read the file once at startup. There might also be a need to do cleanup
|
||||||
// of the file if the configuration has changed.
|
// 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.
|
// probably be dropped immediately.
|
||||||
// 6. Save all data at shutdown.
|
// 6. Save all data at shutdown.
|
||||||
// Start using xxhash
|
// 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.
|
|
||||||
|
|||||||
@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
/** Class PerformanceInfo is a basic structure for storing a Host and Duration pair, along with
|
/** PerformanceInfo is a class that on the one hand provides routeQuery() with performance/routing
|
||||||
* the time it was created.
|
* information and on the other has data for class SmartRouter to manage the life-time of a measurment.
|
||||||
*/
|
*/
|
||||||
class PerformanceInfo
|
class PerformanceInfo
|
||||||
{
|
{
|
||||||
@ -40,38 +40,24 @@ public:
|
|||||||
/** Duration since this PerformanceInfo was created
|
/** Duration since this PerformanceInfo was created
|
||||||
*/
|
*/
|
||||||
maxbase::Duration age() const;
|
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:
|
private:
|
||||||
maxbase::Host m_host;
|
maxbase::Host m_host;
|
||||||
maxbase::Duration m_duration;
|
maxbase::Duration m_duration;
|
||||||
|
|
||||||
|
int m_eviction_schedule = 0;
|
||||||
|
bool m_updating = false;
|
||||||
|
|
||||||
maxbase::TimePoint m_creation_time = maxbase::Clock::now();
|
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
|
// For logging. Shortens str to nchars and adds "..." TODO move somewhere more appropriate
|
||||||
std::string show_some(const std::string& str, int nchars = 70);
|
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;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@ -197,29 +197,69 @@ uint64_t SmartRouter::getCapabilities()
|
|||||||
return RCAP_TYPE_TRANSACTION_TRACKING | RCAP_TYPE_CONTIGUOUS_INPUT | RCAP_TYPE_CONTIGUOUS_OUTPUT;
|
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)
|
PerformanceInfo SmartRouter::perf_find(const std::string& canonical)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> guard(m_perf_mutex);
|
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);
|
if (perf_it->second.age() > eviction_schedules[perf_it->second.eviction_schedule()])
|
||||||
return PerformanceInfo();
|
{
|
||||||
|
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);
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
class SmartRouterSession;
|
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>
|
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.
|
/** 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:
|
private:
|
||||||
SmartRouter(SERVICE* service);
|
SmartRouter(SERVICE* service);
|
||||||
|
|
||||||
Config m_config;
|
Config m_config;
|
||||||
CanonicalPerformance m_performance;
|
|
||||||
std::mutex m_perf_mutex;
|
using Perfs = std::unordered_map<std::string, PerformanceInfo>;
|
||||||
|
|
||||||
|
std::mutex m_perf_mutex;
|
||||||
|
Perfs m_perfs;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user