MXS-1507: Make transaction replay configurable

The transaction retrying behavior is now configurable and documented. The
`transaction_replay` parameter implicitly enables the required
functionality in the router that it needs.
This commit is contained in:
Markus Mäkelä
2018-04-20 08:59:58 +03:00
parent c1c942a058
commit 2848f96945
4 changed files with 60 additions and 9 deletions

View File

@ -329,6 +329,36 @@ execution is an acceptable risk.
The number of seconds to wait until an error is returned to the client when
`delayed_retry` is enabled. The default value is 10 seconds.
### `transaction_replay`
Replay interrupted transactions. This parameter was added in MaxScale 2.3.0 and
is disabled by default. Enabling this parameter implicitly enables both the
`delayed_retry` and `master_reconnection` parameters.
When the server where the transaction is in progress fails, readwritesplit can
migrate the transaction to a replacement server. This can completely hide the
failure of a master node without any visible effects to the client.
If no replacement node becomes available before the timeout controlled by
`delayed_retry_timeout` is exceeded, the client connection is closed.
Not all transactions can be safely replayed. Only when the following criteria
are met, the transaction can be safely replayed.
* Transaction contains only data modification (`INSERT`, `UPDATE`, `DELETE`
etc.) or `SELECT ... FOR UPDATE` statements.
* The replacement server where the transaction is applied returns results
identical to the original partial transaction.
If the results from the replacement server are not identical when the
transaction is replayed, the client connection is closed.
Performing MVCC reads (`SELECT` queries without `FOR UPDATE` or `LOCK IN SHARE MODE`)
with transaction replay is discouraged. If such statements are executed
but the results of each reply are identical, the transaction is replayed but the results
are not guaranteed to be consistent on the database level.
## Routing hints
The readwritesplit router supports routing hints. For a detailed guide on hint

View File

@ -290,6 +290,16 @@ RWSplit* RWSplit::create(SERVICE *service, char **options)
config.max_sescmd_history = 0;
}
if (config.transaction_replay)
{
/**
* Replaying transactions requires that we are able to do delayed query
* retries and reconnect to a master.
*/
config.delayed_retry = true;
config.master_reconnection = true;
}
return new (std::nothrow) RWSplit(service, config);
}
@ -496,6 +506,7 @@ MXS_MODULE *MXS_CREATE_MODULE()
{"master_reconnection", MXS_MODULE_PARAM_BOOL, "false"},
{"delayed_retry", MXS_MODULE_PARAM_BOOL, "false"},
{"delayed_retry_timeout", MXS_MODULE_PARAM_COUNT, "10"},
{"transaction_replay", MXS_MODULE_PARAM_BOOL, "false"},
{MXS_END_MODULE_PARAMS}
}
};

View File

@ -163,7 +163,8 @@ struct Config
causal_read_timeout(config_get_string(params, "causal_read_timeout")),
master_reconnection(config_get_bool(params, "master_reconnection")),
delayed_retry(config_get_bool(params, "delayed_retry")),
delayed_retry_timeout(config_get_integer(params, "delayed_retry_timeout"))
delayed_retry_timeout(config_get_integer(params, "delayed_retry_timeout")),
transaction_replay(config_get_bool(params, "transaction_replay"))
{
if (enable_causal_read)
{
@ -193,6 +194,7 @@ struct Config
bool master_reconnection; /**< Allow changes in master server */
bool delayed_retry; /**< Delay routing if no target found */
uint64_t delayed_retry_timeout; /**< How long to delay until an error is returned */
bool transaction_replay; /**< Replay failed transactions */
};
/**

View File

@ -437,7 +437,7 @@ void RWSplitSession::clientReply(GWBUF *writebuf, DCB *backend_dcb)
return;
}
if (session_trx_is_active(m_client->session))
if (m_config.transaction_replay && session_trx_is_active(m_client->session))
{
m_trx.add_stmt(m_current_query.release());
m_trx.add_result(writebuf);
@ -464,13 +464,14 @@ void RWSplitSession::clientReply(GWBUF *writebuf, DCB *backend_dcb)
if (m_is_replay_active)
{
ss_dassert(m_config.transaction_replay);
handle_trx_replay();
// Ignore the response, the client doesn't need it
gwbuf_free(writebuf);
return;
}
else if (session_trx_is_ending(m_client->session))
else if (m_config.transaction_replay && session_trx_is_ending(m_client->session))
{
m_trx.close();
}
@ -630,12 +631,19 @@ void RWSplitSession::handleError(GWBUF *errmsgbuf, DCB *problem_dcb,
can_continue = false;
}
else if (session_trx_is_active(session))
{
if (m_config.transaction_replay)
{
// Stash any interrupted queries while we replay the transaction
m_interrupted_query.reset(m_current_query.release());
can_continue = true;
start_trx_replay();
}
else
{
can_continue = false;
}
}
*succp = can_continue;
backend->close();