Skip to content

Commit

Permalink
feat: add introduction docs
Browse files Browse the repository at this point in the history
  • Loading branch information
earayu committed Aug 17, 2023
1 parent bbd5a74 commit 07c32a3
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 0 deletions.
12 changes: 12 additions & 0 deletions doc/introduction/Dive into Read-Write-Splitting of WeSQL-Scale.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
# Introduction
The need for read-only nodes in MySQL arises from the increasing demand for high availability and scalability in modern applications. As the number of users and data volume grows, it becomes essential to distribute the workload across multiple nodes to ensure optimal performance. Read-write splitting is a technique that helps achieve this goal by directing read queries to read-only nodes and write queries to read-write nodes, thereby reducing the load on the primary node and improving overall performance.

WeSQL-Scale is a database proxy designed to be highly compatible with MySQL. It supports the MySQL wire protocol, read-write splitting without stale reads, connection pooling, and transparent failover. In this article, we will explain the design considerations and provide detailed techniques for read-write-splitting in WeSQL-Scale.
# Read-Write-Splitting
After adding a read-only node, users are often provided an endpoint for read-only operations. Users need to modify the application configuration or code to determine whether to utilize the original read-write endpoint or the newly acquired read-only endpoint. Some proxies offer users the ability to create read-write-splitting rules using regular expressions. However, this approach can be quite complex and hard to handle efficiently.

Our implementation of read-write splitting involves parsing SQL queries and using the Abstract Syntax Tree (AST) to determine where to route the queries. By analyzing the user's SQL, we can identify whether it is a read-only query statement, a Data Manipulation Language (DML) statement, or a special statement that needs to be executed on the primary node, such as SELECT LAST_INSERT_ID(). If it is a read-only query statement, we route it to the read-only node; otherwise, it is sent to the read-write node.

This approach eliminates the need for users to configure anything or make any application code changes.
# ReadAfterWrite Consistency
In a replicated MySQL cluster, the follower always takes some time to sync the data from the leader. Therefore, a read operation may be forwarded to a lagging follower and not receive the data that was just written by the same client. The Read-Write-Splitting feature has exacerbated this phenomenon, as it automatically sends read operations to followers.

The Picture below shows why the read operation is unable to get the latest data.
![](images/16922380094375.jpg)
Some proxies track replication lag and disable the use of read slaves if the lag becomes too high, but they are not intelligent enough to determine when it is safe for a specific query to be routed to the read-slave.
Our solution, WeSQL-Scale, guarantees ReadAfterWrite Data Consistency by tracking Global Transaction Identifiers (GTIDs) between primary and follower nodes.

When GTID is enabled, every transaction (including autocommit) will be assigned a GTID after it is successfully committed. The GTID serves as the watermark for the current operation. The follower MySQL instance will subsequently apply the transaction and mark the GTID executed. At this point, we can determine that it’s safe to read the data on the follower node.
## Technique Details
WeSQL-Scale facilitates the following steps to ensure the freshness of the data, while attempting to avoid introducing additional round-trip (RT) time to guarantee performance:

**Step 1: Get GTID after write operation without extra network round**
Starting from MySQL 5.7, the MySQL protocol implements a mechanism (SESSION_TRACK_GTIDS) to collect the GTIDs to be sent over the wire in the response packet. This feature assists us in acquiring GTIDs without introducing further network rounds after write operations.

**Step 2: Store the GTID in WeSQL-Scale sessions**
After parsing the response packet and getting the GTIDs, WeSQL-Scale will store them in memory.
Depends on the consistency level, the GTIDs may be stored in the client’s Session or a global memory data structure.
Later read operations will utilize GTIDs stored in WeSQL-Scale’s memory, to ensure retrieval of data that was previously written. See below steps for more details.

**Step 3: Select a follower for reading**
A memory data structure called CLUSTER_GTID_EXEUTED is also matained in WeSQL-Scale’s memory, it contains the @@global.gtid_executedvalues of every follower MySQL instance. The CLUSTER_GTID_EXEUTED is updated periodically by the health-check module, but it will inevitably be lagging.
During the routing phase of a read operation, WeSQL-Scale will use the GTIDSet (which we got from step2) to pick a MySQL instance based on CLUSTER_GTID_EXEUTED.
If there is a MySQL instance that has a more up-to-date GTIDSet than the GTIDSet requested by the read operation, we can send the read operation directly to the MySQL instance and ensure that it will read the data we just wrote.

**Step 4: Ensure write operations have been propagated to the follower MySQL**
All the follower MySQL instances may be lagging, or the CLUSTER_GTID_EXEUTED may be out-of-date for whatever reason. It is possible that no follower is available for a read operation in Step 3.
We can either send the read operation to the leader, or send the read operation to the follower with a WAIT_FOR_EXECUTED_GTID_SET prefix. WAIT_FOR_EXECUTED_GTID_SET function will keep waiting until a GTID is executed on the follower or until times out.

We can use multi-statements to save one network round:
```SQL
-- for example, if user's SQL is:
Expand All @@ -36,6 +46,7 @@ select * from t1;
select WAIT_FOR_EXECUTED_GTID_SET('ab73d556-cd43-11ed-9608-6967c6ac0b32:7', 3);select * from t1;
```
We need to handle the MySQL protocol carefully to use the multi-statement, otherwise the MySQL connection may be broken.

All the steps above will not introduce additional network rounds. The Picture below shows why the Read-After-Write feature ensures the read operation is able to get the latest data.
![](images/16922380711791.jpg)

Expand All @@ -46,6 +57,7 @@ WeSQL-Scale implements different Load Balance policies to give the users the pow
- **LEAST_RT**: Load balance queries across read nodes with the lowest response time.
- **LEAST_BEHIND_PRIMARY**: Load balance queries across read nodes with the least lag behind the primary node.
- **RANDOM**: Randomly load balance queries across available read nodes.

WeSQL-Scale will maintain health checks with all MySQL nodes. So, with the failover event of a node, WeSQL-Scale is capable of detecting the failed node automatically and routing requests to a healthy node. Once the node has recovered, WeSQL-Scale will mark it as available and sending requests to it. This entire process does not require any human intervention or application configuration changes. This feature ensures smooth operation and minimal downtime during failover situations.
# Conclusion
People usually add read-only nodes for high availability and scalability, but if the newly added read-only nodes come with an additional endpoint, it can bring a lot of additional complexity to developers and applications. For example, how applications can use two endpoints to achieve read-write split and handle the additional readAfterWrite Consistency and Load balance issues that arise after splitting read/write traffic.
Expand Down
11 changes: 11 additions & 0 deletions doc/introduction/Introduction To WeSQL-Scale.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
# Introduction
A database proxy is an essential tool for developers and database administrators to improve the scalability, performance, security, and resilience of their applications.

WeSQL-Scale is a database proxy designed to be highly compatible with MySQL. It supports the MySQL wire protocol, read-write splitting without stale reads, connection pooling, and transparent failover. In this article, we will introduce you to WeSQL-Scale, explaining its architecture, key features, and benefits to help you understand why you might want to use it.

# Architecture
WeSQL-Scale is a fork of the Vitess project, we removed support for sharding in exchange for better SQL compatibility, for example, better support for subqueries, Common Table Expressions (CTE) and expression evluation. The below graph displays the architecture of a WeSQL-Scale cluster.

**VTGate**: Client application usually connects to VTGate via standard MySQL wire protocol. VTGate is stateless, which means it can be easily and effectively scaled in terms of size and performance. It acts like a MySQL and is responsible for parsing SQL queries, as well as planning and routing queries to VTTables.

**VTTablet**: Typically, VTTablet is implemented as a sidecar for MySQL. If WeSQL-Scale is to be deployed in Kubernetes, then VTTablet should be in the same pod with MySQL. VTTablet accepts gRPC requests from VTGate and then sends those queries to be executed on MySQL. The VTTablet takes care of a few tasks such as permission checking and logging, but its most critical role is ensuring proper connection pooling.

**VTController**: The VTController component facilitates service discovery between VTGate and VTTablet, while also enabling them to store metadata about the cluster. If the role of MySQL changes, say from leaders to followers, the corresponding role of VTTablet should change accordingly. VTController checks the status of the MySQL cluster and sends commands to VTTablet to request that it changes roles.
![](images/16922377805654.jpg)
# Connection Pooling
Database connections consume memory, and to ensure that the buffer pool has enough memory, databases restrict the value of 'max_connections'. As applications are generally stateless and may need to rapidly scale-out, a large number of connections may be created, which can easily overload your database. So, these kinds of applications are not suitable for directly connecting to the MySQL server and creating a connection pool.

Applications can create as many connections as they require with WeSQL-Scale, without needing to be concerned about max_connection errors. Because WeSQL-Scale will take over the establishment of a connection pool to the database, allowing applications to share and reuse connections at MySQL server side. This reduces the memory and CPU overhead associated with opening and closing connections in the MySQL server-side, improving scalability and performance.
# Read Write Splitting
Using read-only nodes can significantly reduce the workload on the primary database node and improve resource utilization. However, managing multiple read-only nodes and deciding when to use each can be challenging for applications. WeSQL-Scale addresses this issue by offering three essential features: read-write-split, read-after-write consistency, and load-balancing. These features make it easier for applications to take advantage of read-only nodes effectively.

**Read-Write Split**: WeSQL-Scale simplifies application logic by automatically routing read queries to read-only nodes and write queries to the primary node. This is achieved by parsing and analyzing SQL statements, which improves load balancing and ensures efficient use of available resources.

**Read-After-Write Consistency**: This feature works in conjunction with read-write-split to maintain data consistency while still benefiting from performance improvements. When an application writes data to the primary node and subsequently reads it on a read-only node, WeSQL-Scale makes sure that the data that was just written to the primary node can be accessed and read from the read-only node.

**Load-Balancing**: WeSQL-Scale helps manage read-only nodes by routing queries to the appropriate node using various load balancing policies. This ensures that the workload is evenly distributed across all available nodes, optimizing performance and resource utilization.
# Transparent Failover
Failover is a feature designed to ensure that if the original database instance becomes unavailable, it is replaced with another instance and remains highly available. Various factors can trigger a failover event, including issues with the database instance or scheduled maintenance procedures like database upgrades.

Without WeSQL-Scale, a failover requires a short period of downtime. Existing connections to the database are disconnected and need to be reopened by your application. WeSQL-Scale is capable of automatically detecting failovers and buffering application SQL in its memory while keeping application connections intact, thus enhancing application resilience in the event of database failures.

There is no way to completely solve the application-side connection error problem. However, the proxy is stateless and can be deployed with multiple nodes for high availability. Moreover, restart/recovery is usually way faster than the database, as it does not need to recover from the undo-redo log.
# Future Works
Databases have emerged to accommodate the common needs of applications. However, the development of application technology has significantly outpaced the development of traditional database kernels. In addition to the benefits mentioned above, we aim to implement more features to make databases more user-friendly for applications. This includes proxy-side authentication and authorization, SQL throttling, Online DDL, and so on.
Expand All @@ -29,4 +39,5 @@ If you still have any questions after reading the content above, here is a simpl
3. You have applications that require rapid scaling, leading to a surge in database connections.
4. You have applications that need to use read-write separation and want to ensure consistency.
5. You have DB instances that require maintenance operations such as upgrades, and applications can not afford any downtime.

In conclusion, WeSQL-Scale is a powerful database proxy that can significantly enhance the scalability, performance, security, and resilience of applications. It helps address database connection issues and improve resource utilization through effective connection management, read-write separation and consistency, load balancing, and transparent failover features.

0 comments on commit 07c32a3

Please sign in to comment.