Search code examples
mysqlamazon-rdspartitioningdatabase-performance

Improving MySQL performance on RDS by partitioning


I am trying to improve a performance of some large tables (can be millions of records) in a MySQL 8.0.20 DB on RDS.

Scaling up DB instance and IOPS is not the way to go, as it is very expensive (the DB is live 24/7). Proper indexes (including composite ones) do already exist to improve the query performance. The DB is mostly read-heavy, with occasional massive writes - when these writes happen, reads can be just as massive at the same time.

I thought about doing partitioning. Since MySQL doesn't support vertical partitioning, I considered doing horizontal partitioning - which should work very well for these large tables, as they contain activity records from dozens/hundreds of accounts, and storing each account's records in a separate partition makes a lot of sense to me. But these tables do contain some constraints with foreign keys, which rules out using MySQL's horizontal partitioning : Restrictions and Limitations on Partitioning

Foreign keys not supported for partitioned InnoDB tables. Partitioned tables using the InnoDB storage engine do not support foreign keys. More specifically, this means that the following two statements are true:

  1. No definition of an InnoDB table employing user-defined partitioning may contain foreign key references; no InnoDB table whose definition contains foreign key references may be partitioned.

  2. No InnoDB table definition may contain a foreign key reference to a user-partitioned table; no InnoDB table with user-defined partitioning may contain columns referenced by foreign keys.

What are my options, other than doing "sharding" by using separate tables to store activity records on a per account basis? That would require a big code change to accommodate such tables. Hopefully there is a better way, that would only require changes in MySQL, and not the application code. If the code needs to be changed - the less the better :)


Solution

  • storing each account's records in a separate partition makes a lot of sense to me

    Instead, have the PRIMARY KEY start with acct_id. This provides performance at least as good as PARTITION BY acct_id, saves disk space, and "clusters" an account's data together for "locality of reference".

    The DB is mostly read-heavy

    Replicas allows 'infinite' scaling of reads. But if you are not overloading the single machine now, there may be no need for this.

    with occasional massive writes

    Let's discuss techniques to help with that. Please explain what those writes entail -- hourly/daily/sporadic? replace random rows / whole table / etc? keyed off what? Etc.

    Proper indexes (including composite ones) do already exist to improve the query performance.

    Use the slowlog (with long_query_time = 1 or lower) to verify. Use pt-query-digest to find the top one or two queries. Show them to us -- we can help you "think out of the box".

    read-heavy

    Is the working set size less than innodb_buffer_pool_size? That is, are you CPU-bound and not I/O-bound?

    More on PARTITION

    PRIMARY KEY(acct_id, ..some other columns..) orders the data primarily on acct_id and makes this efficient: WHERE acct_id=123 AND ....

    PARTITION BY .. (acct_id) -- A PARTITION is implemented as a separate "table". "Partition pruning" is the act of deciding which partition(s) are needed for the query. So WHERE acct_id=123 AND ... will first do that pruning, then look for the row(s) in that "table" to handle the AND .... Hopefully, there is a good index (perhaps the PRIMARY KEY) to handle that part of the filtering.

    The pruning is sort of takes the place of one level of BTree. It is hard to predict which will be slower or faster.

    Note that when partitioning by, say, acct_id, there is usually not efficient to start the index with that column. (However, it would need to be later in the PK.)

    Big Deletes

    There are several ways to do a "big delete" while minimizing the impact on the system. Partitioning by date is optimal but does not sound viable for your type of data. Check out the others listed here: http://mysql.rjweb.org/doc.php/deletebig

    Since you say that the deletion is usually less than 15%, the "copy over what needs to be kept" technique is not applicable either.