MXS-1502: Prune session commands

The session command history is now compacted to contain only the first and
last execution of a session command. This should still allow most of the
more eccentric use-cases of user variables while keeping the session
command history smaller.

Added some convenience functions into the SessionCommand class to make the
pruning process easier.
This commit is contained in:
Markus Mäkelä
2018-03-28 15:20:28 +03:00
parent ce853d3f7f
commit e57ac4b0a3
4 changed files with 97 additions and 5 deletions

View File

@ -23,6 +23,10 @@
namespace maxscale
{
class SessionCommand;
typedef std::tr1::shared_ptr<SessionCommand> SSessionCommand;
typedef std::list<SSessionCommand> SessionCommandList;
class SessionCommand
{
SessionCommand(const SessionCommand&);
@ -78,6 +82,29 @@ public:
*/
std::string to_string();
/**
* @brief Equality comparison
*
* @return True if @c rhs is equal
*/
bool eq(const SessionCommand& rhs) const;
class Equals: public std::unary_function<const SSessionCommand&, bool>
{
public:
Equals(const SSessionCommand& base):
m_base(base)
{}
bool operator ()(const SSessionCommand& rhs)
{
return m_base->eq(*rhs);
}
private:
const SSessionCommand& m_base;
};
private:
mxs::Buffer m_buffer; /**< The buffer containing the command */
uint8_t m_command; /**< The command being executed */
@ -85,7 +112,14 @@ private:
bool m_reply_sent; /**< Whether the session command reply has been sent */
};
typedef std::tr1::shared_ptr<SessionCommand> SSessionCommand;
typedef std::list<SSessionCommand> SessionCommandList;
inline bool operator ==(const SessionCommand& lhs, const SessionCommand& rhs)
{
return lhs.eq(rhs);
}
inline bool operator !=(const SessionCommand& lhs, const SessionCommand& rhs)
{
return !lhs.eq(rhs);
}
}

View File

@ -123,7 +123,7 @@ bool Backend::execute_session_command()
void Backend::append_session_command(GWBUF* buffer, uint64_t sequence)
{
m_session_commands.push_back(SSessionCommand(new SessionCommand(buffer, sequence)));
append_session_command(SSessionCommand(new SessionCommand(buffer, sequence)));
}
void Backend::append_session_command(const SSessionCommand& sescmd)

View File

@ -16,7 +16,8 @@
#include <maxscale/modutil.h>
#include <maxscale/protocol/mysql.h>
using namespace maxscale;
namespace maxscale
{
void SessionCommand::mark_reply_received()
{
@ -62,6 +63,11 @@ SessionCommand::~SessionCommand()
{
}
bool SessionCommand::eq(const SessionCommand& rhs) const
{
return rhs.m_buffer.compare(m_buffer) == 0;
}
std::string SessionCommand::to_string()
{
std::string str;
@ -80,3 +86,5 @@ std::string SessionCommand::to_string()
return str;
}
}

View File

@ -23,6 +23,7 @@
#include <maxscale/modutil.h>
#include <maxscale/router.h>
#include <maxscale/server.h>
#include <maxscale/session_command.hh>
#include "routeinfo.hh"
#include "rwsplit_internal.hh"
@ -239,6 +240,54 @@ bool route_single_stmt(RWSplit *inst, RWSplitSession *rses, GWBUF *querybuf, con
return succp;
}
/**
* Purge session command history
*
* @param rses Router session
* @param sescmd Executed session command
*/
static void purge_history(RWSplitSession* rses, mxs::SSessionCommand& sescmd)
{
/**
* We can try to purge duplicate text protocol session commands. This
* makes the history size smaller but at the cost of being able to handle
* the more complex user variable modifications. To keep the best of both
* worlds, keeping the first and last copy of each command should be
* an adequate compromise. This way executing the following SQL will still
* produce the correct result.
*
* USE test;
* SET @myvar = (SELECT COUNT(*) FROM t1);
* USE test;
*
* Another option would be to keep the first session command but that would
* require more work to be done in the session command response processing.
* This would be a better alternative but the gain might not be optimal.
*/
// As the PS handles map to explicit IDs, we must retain all COM_STMT_PREPARE commands
if (sescmd->get_command() != MXS_COM_STMT_PREPARE)
{
auto first = std::find_if(rses->sescmd_list.begin(), rses->sescmd_list.end(),
mxs::SessionCommand::Equals(sescmd));
if (first != rses->sescmd_list.end())
{
// We have at least one of these commands. See if we have a second one
auto second = std::find_if(std::next(first), rses->sescmd_list.end(),
mxs::SessionCommand::Equals(sescmd));
if (second != rses->sescmd_list.end())
{
// We have a total of three commands, remove the middle one
auto old_cmd = *second;
rses->sescmd_responses.erase(old_cmd->get_position());
rses->sescmd_list.erase(second);
}
}
}
}
/**
* Execute in backends used by current router session.
* Save session variable commands to router session property
@ -315,7 +364,7 @@ bool route_session_write(RWSplitSession *rses, GWBUF *querybuf,
}
if (rses->rses_config.max_sescmd_history > 0 &&
rses->sescmd_count >= rses->rses_config.max_sescmd_history)
rses->sescmd_list.size() >= rses->rses_config.max_sescmd_history)
{
MXS_WARNING("Router session exceeded session command history limit. "
"Slave recovery is disabled and only slave servers with "
@ -338,6 +387,7 @@ bool route_session_write(RWSplitSession *rses, GWBUF *querybuf,
}
else
{
purge_history(rses, sescmd);
rses->sescmd_list.push_back(sescmd);
}