Details
-
New Feature
-
Status: Open (View Workflow)
-
Major
-
Resolution: Unresolved
-
None
-
None
-
None
-
None
Description
TL;DR
● Add a listener state that closes the listening socket (new connections get a TCP reset) while leaving existing sessions and the listener
config intact, so it can be rebound with start listener. Neither stop listener (socket stays bound, clients hang) nor destroy listener
(config lost) covers this, and the maintenance/drain backend workaround causes transaction-replay failures and post-restart auth-block
outages. Must not survive a restart, must not propagate via config_sync_cluster, and a failed rebind must be an explicit error state.
Extends MXS-2806; mirrors MXS-1417 for servers.
Problem
There is no supported way to make a running MaxScale stop accepting new client
connections while existing sessions finish. This is the basic operation needed to
restart or upgrade one instance of an HA pair. Every current option has a
disqualifying side effect:
- maxctrl stop listener keeps the listening socket bound. New connections
complete the TCP handshake and then receive nothing, so clients hang until their
connect timeout expires instead of being refused. - maxctrl destroy listener closes the socket but deletes the listener config,
which must then be recreated. Its persistence across restart is not defined. - Setting backends to maintenance or drain is not a substitute and is
actively harmful (see below). drain is also correctly refused on a Master
perMXS-3532.
Operators are pushed outside the product, to iptables or an external load balancer,
for an operation MaxScale is best placed to perform.
Why accept-and-hang is worse than refusing
- It is indistinguishable from a hung MaxScale.
- It defeats TCP health checks. A load balancer sees the port open and keeps the
instance in rotation. - It prevents connector failover. Connector/J and friends fail over on a refused
connection, not a slow one, so a client waits out connectTimeout (default 30s)
before trying the second MaxScale.
Evidence (see linked case)
Two MaxScale instances, jdbc:mariadb:loadbalance://mx1:3307,mx2:3307/db split
50/50, in front of a 3 node Galera cluster. Nine listeners on 3306-3314, one service.
Using stop listener before a restart dropped service capacity to 50% instead of
failing over, because clients kept connecting to the drained instance and hanging.
Working around it with server maintenance produced a 4 minute outage, of which the
restart itself was 24 seconds:
11:48:33-11:48:56 all three Galera nodes into maintenance
|
11:49:26+ Could not find valid server for target type TARGET_SLAVE ... closing connection
|
18x Transaction replay time limit of 30 seconds exceeded, closi
|
11:51:09 MaxScale shuts down
|
11:51:33 MaxScale 25.10.2 starts, 9 listeners come up
|
11:51:33 No valid servers from which to query MariaDB user accounts found
|
11:51:33-11:52:35 all clients receive Access denied; hosts blocked for too many a
|
11:52:13-11:52:19 servers come out of maintenance
|
Transaction replay failed 18 times because every candidate was in maintenance, so
workaround disabled the feature that exists to prevent this. After restart, with
backends still in maintenance, MaxScale could not query the user account table, so
clients got Access denied ... (using password: YES) and hosts were blocked for
repeated auth failures. Those blocks outlive the maintenance window and read to
application teams as a credentials problem.
The customer implemented the correct behaviour themselves, in a script that parses the
listener ports from maxscale.cnf and applies:
iptables -I INPUT 1 -p tcp -m conntrack --ctstate NEW --dport "$PORT" -j REJECT --reject-with tcp-reset
|
It works, and it is what Support currently recommends. It should not be necessary.
Proposal
Add a listener state that stops accepting and closes the listening socket, leaving
listener configuration intact so it can be restarted:
maxctrl drain listener <name> # close the socket, keep the config
|
maxctrl start listener <name> # rebind and resume
|
Alternative shape: maxctrl stop listener <name> --close-socket. Either should
exposed via the REST API, and ideally applicable at service level so all listeners on
a service drain in one call (nine listeners on one service in this case).
Critical requirement: this is runtime state, not configuration, and must NOT
propagate through config_sync_cluster. Draining one instance of an HA pair is the
primary use case, so propagation would drain both and take down the entire tier.
Design notes
- The socket is presumably held today to avoid a bind race and to stop another pro
taking the port. If it is closed, a rebind on start listener can fail. That must
be reported explicitly and the listener left in a clearly failed state, not sile
down. The config object surviving means the operator can retry. - The drained state must not survive a MaxScale restart. A restarted MaxScale must
back serving on all configured listeners. This ambiguity is exactly what makes
destroy listener unsafe to recommend today. - Connections already in the accept queue when the socket closes will be reset. That is
the desired outcome. - Existing sessions are untouched and run to completion, as with stop listener today.
Acceptance criteria
- Draining a listener closes the socket. A new connection to the port receives a TCP reset.
- Existing sessions on that listener continue and complete normally.
- start listener rebinds and resumes. A failed rebind is an error and is reflected in state.
- maxctrl list listeners distinguishes stopped-with-socket-bound from drained-
- The drained state is not persisted across a MaxScale restart.
- The drained state is not propagated by config_sync_cluster.
Secondary: naming and documentation
stop listener does not stop the listener. It stops forwarding accepted connect
to the service while the socket stays bound and connections continue to be accepted.
The name implies the socket closes, which is what every operator in this case assu
including Support.
Not proposing a rename, since that breaks existing scripts. But:
- The MaxCtrl reference should state that stop listener leaves the socket bound,
that new connections are accepted and not serviced, and that clients hang rather
being refused. It currently documents this for destroy listener ("closes the
listening socket, opening it up for immediate reuse") and says nothing about soc
behaviour for stop listener. There is no way to discover it short of testing. - Consider deprecating the verb in favour of pause listener, which describes t
actual behaviour, reserving stop for the socket-closing behaviour proposed above.
Relationship to existing issues
Extends MXS-2806, which added stop/start listener. This is not a duplicate: MXS-2806
delivered administrative stop/start, this asks for socket closure so the drained
instance is externally observable as unavailable. MXS-1417 makes the same argument for
servers, citing HAProxy.