230 lines
6.3 KiB
Markdown
230 lines
6.3 KiB
Markdown
# SmartRouter
|
|
|
|
[TOC]
|
|
|
|
## Overview
|
|
|
|
SmartRouter is the query router of the SmartQuery framework. Based on the type
|
|
of the query, each query is routed to the server or cluster that can best
|
|
handle it.
|
|
|
|
For workloads where both transactional and analytical queries are needed,
|
|
SmartRouter unites the Transactional (OLTP) and Analytical (OLAP) workloads into
|
|
a single entry point in MaxScale. This allows a MaxScale client to freely mix
|
|
transactional and analytical queries using the same connection. This is known
|
|
as Hybrid Transactional and Analytical Processing, HTAP.
|
|
|
|
## Configuration
|
|
|
|
SmartRouter is configured as a service that either routes to other MaxScale
|
|
routers or plain servers. Although one can configure SmartRouter to use a plain
|
|
server directly, we refer to the configured "servers" as clusters.
|
|
|
|
For details about the standard service parameters, refer to the
|
|
[Configuration Guide](../Getting-Started/Configuration-Guide.md).
|
|
|
|
### `master`
|
|
|
|
One of the clusters must be designated as the **`master`**. All writes go to the
|
|
master cluster, which for all practical purposes should be a master-slave
|
|
ReadWriteSplit. This document does not go into details about setting up
|
|
master-slave clusters, but suffice to say, that when setting up the ColumnStore
|
|
servers they should be configured to be slaves of a MariaDB server running an
|
|
InnoDB engine. The ReadWriteSplit [documentation](ReadWriteSplit.md) has more on
|
|
master-slave setup.
|
|
|
|
### Service as a Server
|
|
|
|
Currently the configuration for a router (service) can have a number of servers,
|
|
but not other routers (services). Suppose you have a cluster of servers that are
|
|
managed by ReadWriteSplit. In order for that cluster to be routed-to by
|
|
SmartRouter, the service must be exposed as a server. A server section in the
|
|
configuration file that exposes a service instead of defining an actual server,
|
|
is known as *Service as a Server*.
|
|
|
|
#### Example
|
|
|
|

|
|
|
|
Suppose we have a service like
|
|
```
|
|
[RWS-Row]
|
|
type=service
|
|
router=readwritesplit
|
|
```
|
|
for which we have defined the listener
|
|
```
|
|
[RWS-Row-Listener]
|
|
type=listener
|
|
service=RWS-Row
|
|
socket=/tmp/rws-row.sock
|
|
```
|
|
That is, that service can be accessed using the socket `/tmp/rws-row.sock`.
|
|
|
|
A server section that would expose that service as a server, looks like this:
|
|
```
|
|
[RWS-Row-as-a-server]
|
|
type=server
|
|
socket=/tmp/rws-row.sock
|
|
```
|
|
Assuming we have defined `RWS-Column`, `RWS-Column-Listener` and
|
|
`RWS-Column-as-a-server` similarly, we can define the SmartQuery
|
|
service as follows:
|
|
```
|
|
[SmartQuery]
|
|
type = service
|
|
router = smartrouter
|
|
servers = RWS-Row-as-a-server, RWS-Column-as-a-server
|
|
master = RWS-Row-as-a-server
|
|
user = <user>
|
|
password = <password>
|
|
|
|
[SmartQuery-Listener]
|
|
type = listener
|
|
service = SmartQuery
|
|
protocol = mariadbclient
|
|
port = <port>
|
|
```
|
|
Note that the SmartQuery listener listens on a port, while the Service as a Server
|
|
listeners listen on a Unix domain socket. The reason is that there is a significant
|
|
performance benefit when SmartRouter accesses the services over a Unix domain socket
|
|
compared to accessing them over a TCP/IP socket.
|
|
|
|
Note that RWS-Row-as-a-server is designated as the master.
|
|
|
|
The RWS-Row-as-a-server looks like this:
|
|
```
|
|
[RWS-Row-Listener]
|
|
type = listener
|
|
service = RWS-Row
|
|
protocol = mariadbclient
|
|
socket = /tmp/rws-row.sock
|
|
|
|
[RWS-Row-as-a-server]
|
|
type = server
|
|
socket = /tmp/rws-row.sock
|
|
protocol = MariaDBBackend
|
|
```
|
|
A complete configuration example can be found at the end of this document.
|
|
|
|
## Cluster selection - how queries are routed
|
|
|
|
SmartRouter keeps track of the performance, or the execution time, of queries to
|
|
the clusters. Measurements are stored with the canonical of a query as the key.
|
|
The canonical of a query is the sql with all user-defined constants replaced with
|
|
question marks. When SmartRouter sees a read-query whose canonical has not been
|
|
seen before, it will send the query to all clusters. The first response from a
|
|
cluster will designate that cluster as the best one for that canonical. Also,
|
|
when the first response is received, the other queries are cancelled. The
|
|
response is sent to the client once all clusters have responded to the query
|
|
or the cancel.
|
|
|
|
There is obviously overhead when a new canonical is seen. This means that
|
|
queries after a MaxScale start will be slightly slower than normal. The
|
|
execution time of a query depends on the database engine, and on the contents
|
|
of the tables being queried. As a result, MaxScale will periodically re-measure
|
|
queries.
|
|
|
|
The performance behavior of queries under dynamic conditions, and their effect
|
|
on different storage engines is being studied at MariaDB. As we learn more, we
|
|
will be able to better categorize queries and move that knowledge into
|
|
SmartRouter.
|
|
|
|
## Limitations
|
|
|
|
* `LOAD DATA LOCAL INFILE` is not supported.
|
|
* The performance data is not persisted. The measurements have to be performed
|
|
anew after each startup.
|
|
|
|
## Complete configuration example
|
|
```
|
|
[maxscale]
|
|
|
|
[row_server_1]
|
|
type = server
|
|
address = <ip>
|
|
port = <port>
|
|
protocol = MariaDBBackend
|
|
|
|
[row_server_2]
|
|
type = server
|
|
address = <ip>
|
|
port = <port>
|
|
protocol = MariaDBBackend
|
|
|
|
[Row-Monitor]
|
|
type = monitor
|
|
module = mariadbmon
|
|
servers = row_server_1, row_server_2
|
|
user = <user>
|
|
password = <password>
|
|
monitor_interval = 2000ms
|
|
|
|
[column_server_1]
|
|
type = server
|
|
address = <ip>
|
|
port = <port>
|
|
protocol = MariaDBBackend
|
|
|
|
[Column-Monitor]
|
|
type = monitor
|
|
module = csmon
|
|
servers = column_server_1
|
|
user = <user>
|
|
password = <password>
|
|
monitor_interval = 2000ms
|
|
|
|
# Row Read write split
|
|
[RWS-Row]
|
|
type = service
|
|
router = readwritesplit
|
|
servers = row_server_1, row_server_2
|
|
user = <user>
|
|
password = <password>
|
|
|
|
[RWS-Row-Listener]
|
|
type = listener
|
|
service = RWS-Row
|
|
protocol = mariadbclient
|
|
socket = /tmp/rws-row.sock
|
|
|
|
# Columnstore Read write split
|
|
[RWS-Column]
|
|
type = service
|
|
router = readwritesplit
|
|
servers = column_server_1
|
|
user = <user>
|
|
password = <password>
|
|
|
|
[RWS-Column-Listener]
|
|
type = listener
|
|
service = RWS-Column
|
|
protocol = mariadbclient
|
|
socket = /tmp/rws-col.sock
|
|
|
|
[RWS-Row-as-a-server]
|
|
type = server
|
|
socket = /tmp/rws-row.sock
|
|
protocol = MariaDBBackend
|
|
|
|
[RWS-Column-as-a-server]
|
|
type = server
|
|
socket = /tmp/rws-col.sock
|
|
protocol = MariaDBBackend
|
|
|
|
# Smart Query router
|
|
[SmartQuery]
|
|
type = service
|
|
router = smartrouter
|
|
servers = RWS-Row-as-a-server, RWS-Column-as-a-server
|
|
master = RWS-Row-as-a-server
|
|
user = <user>
|
|
password = <password>
|
|
|
|
[SmartQuery-Listener]
|
|
type = listener
|
|
service = SmartQuery
|
|
protocol = mariadbclient
|
|
port = <port>
|
|
```
|