Query hint to influence Consistent Critical Read Filter

MXS-1067. The CCRFilter will now look at the hints in a write-type
query. The parameter-value combinations "ccr=match" and "ccr=ignore"
are recognized and will override any regex match and ignore settings.

For the hints to work, the HintFilter needs to be present in the filter
chain before the CCRFilter.

Also ran astyle and updated documentation.
This commit is contained in:
Esa Korhonen 2017-06-07 11:51:49 +03:00
parent 2778056212
commit bff5e96b1a
2 changed files with 125 additions and 23 deletions

View File

@ -12,6 +12,27 @@ a routing hint to all following statements. This routing hint guides the routing
module to route the statement to the master server where data is guaranteed to
be in an up-to-date state.
The triggering of the filter can be limited further by adding MaxScale supported
comments to queries and/or by using regular expressions. The query comments take
precedence: if a comment is found it is obayed even if a regular expression
parameter might give a different result. Even a comment cannot cause a
SELECT-query to trigger the filter. Such a comment is considered an error and
ignored.
The comments must follow the [MaxScale hint syntax](../Reference/Hint-Syntax.md)
and the *HintFilter* needs to be in the filter chain before the CCR-filter. If a
query has a MaxScale supported comment line which defines the parameter `ccr`,
that comment is caught by the CCR-filter. Parameter values `match` and `ignore`
are supported, causing the filter to trigger (`match`) or not trigger (`ignore`)
on receiving the write query. For example, the query
```
INSERT INTO departments VALUES ('d1234', 'NewDepartment'); -- maxscale ccr=ignore
```
would normally cause the filter to trigger, but does not because of the
comment. The `match`-comment typically has no effect, since write queries by
default trigger the filter anyway. It can be used to override an ignore-type
regular expression that would othewise prevent triggering.
## Filter Options
The CCR filter accepts the following options.
@ -62,7 +83,9 @@ _count_.
An optional parameter that can be used to control which statements trigger the
statement re-routing. The parameter value is a regular expression that is used
to match against the SQL text. Only non-SELECT statements are inspected.
to match against the SQL text. Only non-SELECT statements are inspected. If this
parameter is defined, *only* matching SQL-queries will trigger the filter
(assuming no ccr hint comments in the query).
```
match=.*INSERT.*

View File

@ -103,6 +103,15 @@ static const MXS_ENUM_VALUE option_values[] =
{NULL}
};
typedef enum ccr_hint_value_t
{
CCR_HINT_NONE,
CCR_HINT_MATCH,
CCR_HINT_IGNORE
} CCR_HINT_VALUE;
static CCR_HINT_VALUE search_ccr_hint(GWBUF* buffer);
/**
* The module entry point routine. It is this routine that
* must populate the structure that is referred to as the
@ -148,11 +157,11 @@ MXS_MODULE* MXS_CREATE_MODULE()
{"match", MXS_MODULE_PARAM_STRING},
{"ignore", MXS_MODULE_PARAM_STRING},
{
"options",
MXS_MODULE_PARAM_ENUM,
"ignorecase",
MXS_MODULE_OPT_NONE,
option_values
"options",
MXS_MODULE_PARAM_ENUM,
"ignorecase",
MXS_MODULE_OPT_NONE,
option_values
},
{MXS_END_MODULE_PARAMS}
}
@ -299,28 +308,49 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue)
{
if ((sql = modutil_get_SQL(queue)) != NULL)
{
if (my_instance->nomatch == NULL ||
(my_instance->nomatch && regexec(&my_instance->nore, sql, 0, NULL, 0) != 0))
bool trigger_ccr = true;
bool decided = false; // Set by hints to take precedence.
CCR_HINT_VALUE ccr_hint_val = search_ccr_hint(queue);
if (ccr_hint_val == CCR_HINT_IGNORE)
{
if (my_instance->match == NULL ||
(my_instance->match && regexec(&my_instance->re, sql, 0, NULL, 0) == 0))
trigger_ccr = false;
decided = true;
}
else if (ccr_hint_val == CCR_HINT_MATCH)
{
decided = true;
}
if (!decided)
{
if (my_instance->nomatch &&
regexec(&my_instance->nore, sql, 0, NULL, 0) == 0)
{
if (my_instance->count)
{
my_session->hints_left = my_instance->count;
MXS_INFO("Write operation detected, next %d queries routed to master", my_instance->count);
}
if (my_instance->time)
{
my_session->last_modification = now;
MXS_INFO("Write operation detected, queries routed to master for %d seconds", my_instance->time);
}
my_instance->stats.n_modified++;
// Nomatch was present and sql matched it.
trigger_ccr = false;
}
else if (my_instance->match &&
(regexec(&my_instance->re, sql, 0, NULL, 0) != 0))
{
// Match was present but sql did *not* match it.
trigger_ccr = false;
}
}
if (trigger_ccr)
{
if (my_instance->count)
{
my_session->hints_left = my_instance->count;
MXS_INFO("Write operation detected, next %d queries routed to master", my_instance->count);
}
if (my_instance->time)
{
my_session->last_modification = now;
MXS_INFO("Write operation detected, queries routed to master for %d seconds", my_instance->time);
}
my_instance->stats.n_modified++;
}
MXS_FREE(sql);
}
}
@ -429,3 +459,52 @@ static uint64_t getCapabilities(MXS_FILTER* instance)
{
return RCAP_TYPE_NONE;
}
/**
* Find the first CCR filter hint. The hint is removed from the buffer and the
* contents returned.
*
* @param buffer Input buffer
* @return The found ccr hint value
*/
static CCR_HINT_VALUE search_ccr_hint(GWBUF* buffer)
{
const char CCR[] = "ccr";
CCR_HINT_VALUE rval = CCR_HINT_NONE;
bool found_ccr = false;
HINT** prev_ptr = &buffer->hint;
HINT* hint = buffer->hint;
while (hint && !found_ccr)
{
if (hint->type == HINT_PARAMETER && strcasecmp(hint->data, CCR) == 0)
{
found_ccr = true;
if (strcasecmp(hint->value, "match") == 0)
{
rval = CCR_HINT_MATCH;
}
else if (strcasecmp(hint->value, "ignore") == 0)
{
rval = CCR_HINT_IGNORE;
}
else
{
MXS_ERROR("Unknown value for hint parameter %s: '%s'.",
CCR, (char*)hint->value);
}
}
else
{
prev_ptr = &hint->next;
hint = hint->next;
}
}
// Remove the ccr-hint from the hint chain. Otherwise rwsplit will complain.
if (found_ccr)
{
*prev_ptr = hint->next;
hint_free(hint);
}
return rval;
}