Starting with SQL Server 2012, AlwaysOn Availability Groups provided group level high availability for any number of databases for multiple secondaries known as ‘replicas’. The secondary replicas allow direct read-only querying or can enforce connections that specify ‘ReadOnly’ as their Application Intent using the new feature called Read-Only routing which can be leveraged to scale out reporting workloads. However, in SQL Server 2012 and 2014 versions this redirection is only concerned with the first secondary replica defined in the priority list and all the read-only connections are routed only to that one replica by design. This restricts the other secondary replicas from participating in the load distribution process and thereby reducing the load balancing capability. This article provides you with the configuration and testing of read-only routing along with configuring a custom SQL Agent job in an attempt to create an improved load balancing effect.
Read-only routing refers to the ability of SQL Server to route incoming read-intent connection requests, which are directed to an availability group listener, to an available readable secondary replica. One of the pre-requisites to support read-only routing is that the availability replicas must be enabled for read access.
Tip: Use the below script to check if Read-Only Routing is already configured in your server
SELECT ag.name as "Availability Group", ar.replica_server_name as "When Primary Replica Is", rl.routing_priority as "Routing Priority", ar2.replica_server_name as "RO Routed To", ar.secondary_role_allow_connections_desc, ar2.read_only_routing_url FROM sys.availability_read_only_routing_lists rl INNER JOIN sys.availability_replicas ar on rl.replica_id = ar.replica_id INNER JOIN sys.availability_replicas ar2 on rl.read_only_replica_id = ar2.replica_id INNER JOIN sys.availability_groups ag on ar.group_id = ag.group_id ORDER BY ag.name, ar.replica_server_name, rl.routing_priority
To make it easy to understand, in this demo, we will use the below terminology:
- Availability group named AG
- Listener named AGLISTEN
- Replicas SQL01A (primary) and SQL01B(secondary)
NOTE: Read-only routing can support ALLOW_CONNECTIONS property set to READ_ONLY or ALL (Graphically shown below)
Once the secondaries are set to readable (Read-Intent only/Yes), the below three steps are required to configure Read-Only Routing –
- Define a read-only routing URL
- Define a read-only routing List
- Update the client’s connection string to specify Application Intent connection property as ‘read-only’
Let’s take a look at the above steps in details.
1. Configure Read-Only routing URL
A read_only_routing_url is the entry point of an application to connect to a readable secondary. It contains the system address or the port number that identifies the replica when acting as a readable secondary. This is similar to the endpoint URL we specify when configuring database mirroring. For each readable secondary replica that is to support read-only routing, you need to specify this routing URL
For example, define a URL SQL01B, so that when SQL01B is in the secondary role, it can accept read-only connections.
ALTER AVAILABILITY GROUP AG MODIFY REPLICA ON N'SQL01A' WITH (SECONDARY_ROLE (READ_ONLY_ROUTING_URL = N'TCP://SQL01A:1433')) ALTER AVAILABILITY GROUP AG MODIFY REPLICA ON N'SQL01B' WITH (SECONDARY_ROLE (READ_ONLY_ROUTING_URL = N'TCP://SQL01B:1433'))
Tip: Use THIS code to generate routing URLs for each available secondary replicas to use in the above script
2. Configure Read-Only routing List
For each replica that will act as primary, we need to define the corresponding secondary replicas that will act as the routing target for read-only workloads. This means that if the replica is acting as a primary, all read-only workloads will be redirected to the replicas in the read-only routing list. For example, when SQL01A is in the primary role, define our routing list to consist of SQL01B which is where read-only connection requests will be routed first and if it is not available or not synchronizing (Only in SQL Server 2012) connections will go to the next server in the list.
ALTER AVAILABILITY GROUP AG MODIFY REPLICA ON N'SQL01A' WITH (PRIMARY_ROLE (READ_ONLY_ROUTING_LIST= ('SQL01B', ‘SQL01A’))); GO ALTER AVAILABILITY GROUP AG MODIFY REPLICA ON N'SQL01B' WITH (PRIMARY_ROLE (READ_ONLY_ROUTING_LIST= ('SQL01A', ‘SQL01B’)));
Tip: Alternatively to automate the above process you can use THIS script to dynamically generate the scripts required mentioned in the above tasks
Unfortunately there is no graphical user interface to perform these tasks in SSMS. The read-only routing URL and the routing list can be performed only through Transact-SQL or PowerShell
NOTE: As a best practice it is always recommended to assign the primary replica name at the end of the routing list separated by comma, in the rare event if all of the available secondary replicas are not available.
3. Update client connection string
Read-only clients must direct their connection requests to this listener, and the client’s connection strings must specify the application intent as “read-only.” That is, they must be read-intent connection requests. This can be seen in the connection string, an example is shown below:
Before making client side changes you can confirm this newly configured read-only routing using SQL CMD by specifying application intent option (-K) as shown below
Sqlcmd –S AGLISTEN –E –d AGDB1 –K readonly
Load Balancing using Read-Only Routing List
The read-only routing introduced in SQL Server 2012 is used for redirection and offloading the read queries to the secondary replicas instead of primary replica. However, this redirection is only concerned with the first secondary replica defined in the priority list that we define. Since the primary replica strictly traverses the list and looks for the first replica that can serve the connection request. Once found, all subsequent read-only connections are routed to it. For example, in a multiple secondary architecture, all the read intent queries only hit the first secondary replica in the list while other secondaries do not participate in distributing this load. This limits the load balancing capability among other secondary replicas.
To overcome this situation here a workaround that will modify the Read-Only Routing list periodically to let read intent queries to use all the replicas at certain intervals (every 30 seconds in this case), so as to create a load balancing effect. This is only applicable to SQL Servers running 2012 and 2014 versions since starting with SQL Server 2016, Microsoft changed the game by introducing native load-balancing capabilities which we will look into later in the paper
WHILE 1=1 Begin If ( SELECT ARS.role_desc FROM SYS.availability_REPLICAs AR join sys.dm_hadr_availability_REPLICA_states ARS ON AR.REPLICA_id = ARS.REPLICA_id WHERE AR.REPLICA_server_name = (select @@SERVERNAME) ) = 'PRIMARY' and (select count(*) from sys.availability_read_ONly_routing_lists) > 1 Begin ALTER AVAILABILITY GROUP [AGDB1] MODIFY REPLICA ON N'SQL01A' WITH (Primary_Role (READ_ONLY_ROUTING_LIST =('SQL01B','SQLNODE3'))) ALTER AVAILABILITY GROUP [AGDB1] MODIFY REPLICA ON N'SQL01B' WITH (Primary_Role (READ_ONLY_ROUTING_LIST =('SQL01A','SQLNODE3'))) ALTER AVAILABILITY GROUP [AGDB1] MODIFY REPLICA ON N'SQLNODE3' WITH (Primary_Role (READ_ONLY_ROUTING_LIST =('SQL01B', 'SQL01A'))) --print 'changing ROR URL in 30 seconds...' WAITFOR DELAY '00:00:30' --print 'Changing ROR URL' --Run every 30 secONds ALTER AVAILABILITY GROUP [AGDB1] MODIFY REPLICA ON N'SQL01A' WITH (Primary_Role (READ_ONLY_ROUTING_LIST =('SQLNODE3','SQL01B'))) ALTER AVAILABILITY GROUP [AGDB1] MODIFY REPLICA ON N'SQL01B' WITH (Primary_Role (READ_ONLY_ROUTING_LIST =('SQLNODE3','SQL01A'))) ALTER AVAILABILITY GROUP [AGDB1] MODIFY REPLICA ON N'SQLNODE3' WITH (Primary_Role (READ_ONLY_ROUTING_LIST =('SQL01A', 'SQL01B'))) END WAITFOR DELAY '00:00:30' End
Note: You can add additional replica details based on the number of secondary replicas configured in your Read-Only Routing
To complete the procedure run the code from above in a new query window in SSMS for testing purposes and once verified you can then use the code to create a SQL agent job in all the replicas. This job needs to be run continuously on each replica. The code will only run from the instance that is in the PRIMARY role.
The verify the Read-Only Routing is rotating correctly run the below script
SELECT ag.name as "Availability Group", ar.replica_server_name as "When Primary Replica Is", rl.routing_priority as "Routing Priority", ar2.replica_server_name as "RO Routed To" FROM sys.availability_read_only_routing_lists rl INNER JOIN sys.availability_replicas ar on rl.replica_id = ar.replica_id INNER JOIN sys.availability_replicas ar2 on rl.read_only_replica_id = ar2.replica_id INNER JOIN sys.availability_groups ag on ar.group_id = ag.group_id ORDER BY ag.name, ar.replica_server_name, rl.routing_priority
After 30 seconds, notice that the “RO Routed To” column alternates among the available secondary replicas.
As evident from above result, this code modifies Read-Only routing list bringing in a new secondary replica into play periodically to cater for the read intent connections essentially creating a load balancing effect. Load balancing using this technique provides a way to get even more use from server hardware that host secondary’s databases and provide reporting applications with better performance and throughput especially for long and resource intensive queries. Please note that this algorithm is limited but it serves the purpose quite effectively. A similar but much robust algorithm has been built into the native SQL engine starting with SQL Server 2016.