Hot row performance optimization

Updated at:
Copy as MD

Hot rows are database rows that are frequently modified. In high-concurrency scenarios, updating hot rows causes severe row lock contention and long wait times, degrading system performance. To address this issue, PolarDB implements optimizations at the database kernel level to significantly improve system performance.

Background

Hot rows present the following challenges:

  • When a transaction updates a data row, it acquires a lock on the target row and does not release the lock until the transaction is committed or rolled back. During this time, only one transaction can update the row, and other transactions must wait. This means that update requests for a single hot row are executed serially. Traditional database and table sharding strategies provide limited performance improvements in this scenario.

  • In these scenarios, a large number of update requests for hot rows can reach the backend database system in a short period. This leads to severe row lock contention and long wait times, which degrades system performance. A long wait for an update request can significantly impact business operations.

Simply scaling up hardware cannot meet these low-latency requirements. To address this, PolarDB provides innovative optimizations at the database kernel level. The system automatically identifies hot row update requests and groups updates to the same data row that occur within a specific time interval. Different groups are then processed in parallel using a pipeline. These optimizations greatly improve system performance.

Technical solution

  • From sequential to pipeline processing

    Parallel processing is the most direct way to improve database performance, but it is difficult to fully parallelize update operations on the same hot row. PolarDB uses an innovative pipeline processing approach to maximize the parallelism of hot row update operations.

    image

    SQL statements used for hot row update operations are marked with autocommit or COMMIT_ON_SUCCESS. The optimized MySQL kernel layer automatically identifies update operations with these marks. Within a certain time interval, it performs a Hash on the collected update operations based on their primary key or unique key. For update operations that are mapped to the same bucket by the Hash, the kernel groups and commits them in batches according to their arrival order.

    To process these update operations in a pipeline, two execution units are used to group them. When the first group is collected and ready to commit, the second group immediately starts collecting update operations. When the second group is collected and ready to commit, the first group has already been committed and begins collecting a new batch of update operations. The two groups alternate and execute in parallel.

    This pipeline processing model fully utilizes hardware resources, increases CPU utilization, and improves the system's parallel processing capability, maximizing its throughput.

  • Eliminate waiting when requesting row locks

    To ensure logical data consistency, an update first acquires a lock on the target data row. If the lock request cannot be granted immediately, the request enters a waiting state. This not only increases processing latency but also triggers deadlock detection, which adds extra overhead.

    As mentioned earlier, update operations on the same data row are grouped in chronological order. The first update operation in a group is the Leader. It reads the target data row and locks it. Subsequent update operations are Followers. When a Follower requests a lock on the target data row and finds that the Leader already holds the row lock, it can acquire the lock immediately without waiting.

    This optimization reduces the number of row lock acquisitions and the associated time overhead. As a result, overall system performance improves significantly.

  • Reduce B-tree index traversals

    MySQL manages data by using B-tree indexes. Each query must traverse the index to locate the target data row. The larger the data table and the deeper the index, the longer the traversal takes.

    In the grouping mechanism described earlier, only the Leader of each group traverses the index to locate the data row, and then caches the updated row in memory (Row Cache). After a Follower in the same group successfully acquires the lock, it reads the target data row directly from memory without traversing the index again.

    This reduces the total number of index traversals and their associated overhead.

Prerequisites

  • Your PolarDB cluster must run one of the following versions:

    • PolarDB for MySQL 5.6, with a minor engine version of 20200601 or later.

    • PolarDB for MySQL 5.7, with a minor engine version of 5.7.1.0.17 or later.

    • PolarDB for MySQL 8.0, with a minor engine version of 8.0.1.1.10 or later.

  • binlog is enabled.

  • The cluster parameter rds_ic_reduce_hint_enable must be disabled.

    • For PolarDB for MySQL 5.6 and PolarDB for MySQL 8.0, this parameter is disabled by default.

    • For PolarDB for MySQL 5.7, this parameter is enabled by default. Before you enable hot row optimization, you must change the parameter value to OFF.

    Note

    To ensure compatibility with MySQL configuration files, the PolarDB console prefixes all cluster parameters with loose_. To modify the rds_ic_reduce_hint_enable parameter in the PolarDB console, you must select the parameter with the loose_ prefix, which is loose_rds_ic_reduce_hint_enable.

Limitations

Hot row optimization does not apply in the following scenarios:

  • The table that contains the hot row is a partitioned table.

  • A trigger is defined on the table that contains the hot row.

  • The hot row uses the Statement Queue mechanism.

  • If global binlog is enabled but session-level binlog is disabled, hot row optimization is not applied to UPDATE statements.

  • After you enable hot row optimization, columns that rely solely on the ON UPDATE CURRENT_TIMESTAMP attribute for automatic updates will no longer be automatically updated. You must explicitly assign values to these columns in your UPDATE statements by using SET column_name = CURRENT_TIMESTAMP(n).

Usage

  1. Enable hot row optimization.

    In the PolarDB console, you can modify the following parameter to enable or disable hot row optimization.

    Parameter

    Description

    hotspot

    Specifies whether to enable the hot row optimization feature. Valid values:

    1. ON: enabled.

    2. OFF (default): disabled.

    Note

    To ensure compatibility with MySQL configuration files, the PolarDB console prefixes all cluster parameters with loose_. To modify the hotspot parameter in the PolarDB console, select the parameter with the loose_ prefix, which is loose_hotspot.

  2. Use hint syntax to apply hot row optimization.

    Hint

    Required

    Description

    COMMIT_ON_SUCCESS

    Required

    Commits the transaction if the update is successful.

    ROLLBACK_ON_FAIL

    Optional

    Rolls back the transaction if the update fails.

    TARGET_AFFECT_ROW(1)

    Optional

    Specifies that the request is expected to update only one row. If this condition is not met, the update fails.

    Note

    Because this hint automatically commits the transaction, you must include it in the last SQL statement of the transaction.

    Example: Update the value of the c column in the sbtest table.

    UPDATE /*+ COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ sbtest SET c = c + 1 WHERE id = 1;

Related operations

Custom parameters

You cannot modify the following parameters in the PolarDB console. To modify them, go to Quota Center, find the quota ID polardb_mysql_hotspot, and then click Apply in the Actions column.

Parameter

Description

hotspot_for_autocommit

Specifies whether to enable hot row optimization for UPDATE statements in autocommit mode. Valid values:

  • ON: enabled.

  • OFF (default): disabled.

hotspot_update_max_wait_time

The maximum time a Leader waits for Followers to join a group during a Group Update.

  • Unit: microseconds (us).

  • Default value: 100.

hotspot_lock_type

Specifies whether to enable a new row lock type for Group Update. Valid values:

  • ON: enabled.

  • OFF (default): disabled.

Note
  • If this parameter is enabled, when an update operation requests a row lock for the same hot row, it can acquire the lock immediately without waiting. This improves performance.

  • The new lock type is a lock that can be acquired immediately without waiting.

Parameter settings

Run the following command to view the parameter settings for hot row optimization.

SHOW variables LIKE "hotspot%";

Sample result:

+------------------------------+-------+
|Variable_name                 | Value |
+------------------------------+-------+
|hotspot                       | OFF   |
|hotspot_for_autocommit        | OFF   |
|hotspot_lock_type             | OFF   |
|hotspot_update_max_wait_time  | 100   |
+------------------------------+-------+

Usage statistics

Run the following command to view usage statistics for hot row optimization.

SHOW GLOBAL status LIKE 'Group_update%';

Performance test

Test tool

Sysbench is an open-source, cross-platform performance testing tool. It is primarily used for database benchmarks, such as for MySQL, and for system performance tests on components like the CPU, memory, I/O, and threads. It supports multi-threaded testing and uses Lua scripts to flexibly control test logic. It is suitable for scenarios such as database performance evaluation and stress testing.

Test table and statement

  • Table definition

    CREATE TABLE sbtest (id INT UNSIGNED NOT NULL, c BIGINT UNSIGNED NOT NULL, PRIMARY KEY (id));
  • Test statement

    UPDATE /*+ COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ sbtest SET c = c + 1 WHERE id = 1;

Test results

PolarDB for MySQL 5.6

Test scenario

A single hot row on an 8-core CPU.

Test results

In this test scenario, enabling hot row optimization improves update performance on inventory-related hot rows by nearly 50 times under high concurrency.

Test data (QPS)

image.png

Concurrency

1

8

16

32

64

128

256

512

1024

Hot row optimization disabled

1365.31

1863.94

1866.6

1862.64

1867.32

1832.51

1838.31

1819.52

1833.2

Hot row optimization enabled

1114.79

7000.19

12717.32

22029.48

43096.06

61349.7

83098.69

90860.94

87689

PolarDB for MySQL 5.7

Test scenario

A single hot row on an 8-core CPU.

Test results

In this test scenario, enabling hot row optimization improves update performance on inventory-related hot rows by nearly 35 times under high concurrency.

Test data

QPS

image.png

Concurrency

1

8

16

32

64

128

256

512

1024

Hot row optimization disabled

1348.49

1892.29

1889.77

1895.86

1875.2

1850.26

1843.62

1849.92

1835.68

Hot row optimization enabled

1104.9

6886.89

12485.17

16003.23

16460.31

16548.86

27920.89

47893.96

66500.92

95th percentile latency

image

Concurrency

1

8

16

32

64

128

256

512

1024

Hot row optimization disabled

0.9

5.47

9.91

18.95

36.89

73.13

164.45

297.92

590.56

Hot row optimization enabled

1.08

1.44

1.58

3.25

5.28

9.56

12.08

13.22

18.28

PolarDB for MySQL 8.0

Test scenario

A single hot row on an 8-core CPU.

Test results

In this test scenario, enabling hot row optimization improves update performance on inventory-related hot rows by nearly 26 times under high concurrency.

Test data

QPS

image

Concurrency

1

8

16

32

64

128

256

512

1024

Hot row optimization disabled

1559.14

2103.82

2116.4

2082.1

2079.74

2031.64

1993.09

1977.6

1983.61

Hot row optimization enabled

1237.28

7443.04

12244.19

15529.52

23041.15

33931.18

53924.24

54598.6

50988.22

95th percentile latency

image

Concurrency

1

8

16

32

64

128

256

512

1024

Hot row optimization disabled

0.8

5

8.9

17.32

33.12

66.84

153.02

287.38

549.52

Hot row optimization enabled

0.97

1.34

1.89

3.19

4.82

5.88

7.17

13.46

28.16

Performance test steps

  1. Prepare an ECS instance and install Sysbench.

  2. Place the following oltp_inventory.lua file in the src/lua directory of the Sysbench source code.

    #!/usr/bin/env sysbench
    -- it is to test inventory_hotspot performance
    
    sysbench.cmdline.options= {
        inventory_hotspot = {"enable ali inventory hotspot", 'off'},
        tables = {"table number", 1},
        table_size = {"table size", 1},
        oltp_skip_trx = {'skip trx', true},
        hotspot_rows = {'hotspot row number', 1}
    }
    
    
    function cleanup()
        drv = sysbench.sql.driver()
        con = drv:connect()
        for i = 1, sysbench.opt.tables do
            print(string.format("drop table sbtest%d ...", i))
            drop_table(drv, con, i)
        end
    end
    
    function drop_table(drv, con, table_id)
        local query
        query = string.format("drop table if exists sbtest%d ", table_id)
        con:query(query)
    end
    
    function create_table(drv, con, table_id)
        local query
        query = string.format("CREATE TABLE sbtest%d (id INT UNSIGNED NOT NULL, c BIGINT UNSIGNED NOT NULL, PRIMARY KEY (id))", table_id)
        con:query(query)
        for i=1, sysbench.opt.table_size do
            con:query("INSERT INTO sbtest" .. table_id .. "(id, c) values (" ..i.. ", 1)")
        end
    end
    
    function prepare()
        drv = sysbench.sql.driver()
        con = drv:connect()
        for i = 1, sysbench.opt.tables do
            print(string.format("Creating table sbtest%d ...", i))
            create_table(drv, con, i)
        end
    end
    
    function thread_init()
        drv = sysbench.sql.driver()
        con = drv:connect()
        begin_query = 'BEGIN'
        commit_query = 'COMMIT'
    end
    
    function event()
        local table_name
        table_name = "sbtest" .. sysbench.rand.uniform(1, sysbench.opt.tables)
        local min_line = math.min(sysbench.opt.table_size, sysbench.opt.hotspot_rows)
        local row_id = sysbench.rand.uniform(1, min_line)
    
        if not sysbench.opt.oltp_skip_trx then
            con:query(begin_query)
        end
    
        if (sysbench.opt.inventory_hotspot == "on") then
            con:query("UPDATE /*+ COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ " .. table_name .. " SET c=c+1 WHERE id =" .. row_id)
        else
            con:query("UPDATE " .. table_name .. " SET c=c+1 WHERE id = " .. row_id)
        end
    
        if not sysbench.opt.oltp_skip_trx then
            if (sysbench.opt.inventory_hotspot == "on") then
                con:query(commit_query)
            end
        end
    end
    
    function thread_done()
       con:disconnect()
    end
  3. Connect to the cluster by using the command line.

  4. Run the Sysbench test.

    1. Prepare the data.

      sysbench --hotspot_rows=1 --histogram=on --mysql-user=<user> --inventory_hotspot=on --mysql-host=<host> --threads=1 --report-interval=1 --mysql-password=<password> --tables=1 --table-size=1 --oltp_skip_trx=true --db-driver=mysql --percentile=95 --time=300 --mysql-port=<port> --events=0 --mysql-db=<database> oltp_inventory prepare
    2. Run the test.

      sysbench --db-driver=mysql --mysql-host=<host> --mysql-port=<port> --mysql-user=<user> --mysql-password=<password> --mysql-db=<database> --range-selects=0 --table_size=25000 --tables=250 --events=0 --time=600 --rand-type=uniform --threads=<threads> oltp_inventory run

    Input parameters

    Parameter

    Description

    mysql-host

    The cluster endpoint.

    mysql-port

    The port of the cluster endpoint.

    mysql-user

    The username for the database account.

    mysql-password

    The password for the database account.

    mysql-db

    The database name.

    Output parameters

    Parameter

    Metric

    Description

    tables

    Number of tables

    The total number of tables used in the test.

    table_size

    Number of rows per table

    The number of records in each table.

    Data size

    The data size of the table, measured in units such as MB or GB.

    threads

    Number of concurrent threads

    The number of configured threads.

    Thread status

    The real-time status of the running threads.