Introduction

Innodb has always been the most important concept in MySQL and Innodb_buffer_pool_size the most important variable to configure for ages now. Over the period of time  #mysql observability has improved a lot using information_schema and performance schema metrics and one of those use cases we have covered now. In this blog we are going to cover how innodb buffer pool gets allocated and what data gets allocated to it. Also we would cover about the meta data tables available to get some insights of the data stored in the Buffer pool Memory. The below readings we have collected are from a fairly busy environment with below configuration.

Environment Overview

  • RAM – 16GB
  • CPU – 8 Core
  • Database Size – 5 GB
  • MySQL Version – Community – 8.0.33

List of Buffer Pool Variables

Here is the list of variables we will be focussing on this blog.

+-------------------------------------+----------------+
| Variable_name                       | Value          |
+-------------------------------------+----------------+
| innodb_buffer_pool_chunk_size       | 134217728      |
| innodb_buffer_pool_instances        | 8              |
| innodb_buffer_pool_size             | 8589934592     |
+-------------------------------------+----------------+
11 rows in set (0.01 sec)

  • innodb_buffer_pool_size – The total size of the InnoDB buffer pool in bytes.
  • innodb_buffer_pool_instances – The number of instances into which the the buffer pool will be split if InnoDB buffer pool is greater than 1 GB.
  • innodb_buffer_pool_chunk_size – Defines the chunk size that InnoDB buffer pool would be further breaking down to.

We must always make sure that Buffer pool size must always be equal to or a multiple of innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances

Another important thing to keep check on is while configuring innodb_buffer_pool_chunk_size is there shouldn’t be than 1000 chunks for any size innodb_buffer_pool_size. You can use below query to validate.

mysql> select @@innodb_buffer_pool_size/@@innodb_buffer_pool_chunk_size 'should not exceed 1000';
+------------------------+
| should not exceed 1000 |
+------------------------+
|                64.0000 |
+------------------------+
1 row in set (0.00 sec)

How is the InnoDB buffer allocated ?

General practice is to allocate 50-70% memory to innodb_buffer_pool_size and configure number of innodb_buffer_pool_instances in the configuration and rest of the things are handled by innodb engine when the instance comes online. What we are going to see here is how the data gets distributed in the buffer pool at a very high level and how to find out what is stored from the system metadata available for us to examine. Components we are going to cover are

  • innodb_buffer_pool_instances
  • innodb_buffer_pool_chunk_size
  • blocks
  • Pages
  • space_id/file_id

What is innodb_buffer_pool_instances ?

Innodb_buffer_pool_instances is nothing but the number of instances the innodb buffer pool will get divided into. Dividing the buffer pool into separate instances can improve concurrency. Each page that is stored in or read from the buffer pool is assigned to one of the buffer pool instances randomly, using a hashing function. Each buffer pool has its own free lists, flush lists, LRUs, and all other data structures connected to a buffer pool, and is protected by its own buffer pool mutex.

You can try below query which would tell you how your InnoDB is configured in your environment.

select 
concat(round((@@innodb_buffer_pool_size)/(102410241024)),'G') as 'Total Innodb buffer pool', 
@@innodb_buffer_pool_instances as 'Number of Instances', round(@@innodb_buffer_pool_size/@@innodb_buffer_pool_instances/@@innodb_buffer_pool_chunk_size) as 'Approx. Chunks in 1 Instance', concat(round(round(@@innodb_buffer_pool_size/@@innodb_buffer_pool_instances/@@innodb_buffer_pool_chunk_size)@@innodb_buffer_pool_chunk_size/1024/1024/1024),'G') as '1 instance size', concat(round((@@)/(10241024)),'M') as '1 chunk size', concat(round((@@innodb_page_size)/(1024)),'K') as '1 Innodb Page Size', round((@@innodb_buffer_pool_chunk_size/@@innodb_page_size)) as 'Number of pages in 1 chunk', round(((@@innodb_buffer_pool_size/@@innodb_buffer_pool_instances/@@innodb_buffer_pool_chunk_size) * @@innodb_buffer_pool G

Sample Output

     Total Innodb buffer pool: 8G
          Number of Instances: 8
 Approx. Chunks in 1 Instance: 8
              1 instance size: 1G
                 1 chunk size: 128M
           1 Innodb Page Size: 16K
   Number of pages in 1 chunk: 8192
Number of pages in 1 Instance: 65536

What do we have in each Innodb Buffer pool Instance ?

Generally InnoDB buffer pool gets broken in terms of small memory chunks called innodb_buffer_pool_chunk_size˘ and it some how assigns ( innodb_buffer_pool_size / innodb_buffer_pool_chunk_size / innodb_buffer_pool_instances ) chunks to each innodb buffer pool instance which further get divided into 8192 Blocks

What is innodb_buffer_pool_chunk_size ?

innodb_buffer_pool_chunk_size defines the chunk size for InnoDB buffer pool resizing operations.

To avoid copying all buffer pool pages during resizing operations, the operation is performed in “chunks”. By default, innodb_buffer_pool_chunk_size is 128MB (134217728 bytes). The number of pages contained in a chunk depends on the value of innodb_page_size. innodb_buffer_pool_chunk_size can be increased or decreased in units of 1MB (1048576 bytes).

Chunk just seems to be a logical separation of 8192 blocks within an InnoDB Buffer Instance.

select POOL_ID, max(BLOCK_ID),min(BLOCK_ID) from INNODB_BUFFER_PAGE group by POOL_ID;
+---------+---------------+---------------+
| POOL_ID | max(BLOCK_ID) | min(BLOCK_ID) |
+---------+---------------+---------------+
|       0 |          8191 |             0 |
|       1 |          8191 |             0 |
|       2 |          8191 |             0 |
|       3 |          8191 |             0 |
|       4 |          8191 |             0 |
|       5 |          8191 |             0 |
|       6 |          8191 |             0 |
|       7 |          8191 |             0 |
+---------+---------------+---------------+
8 rows in set (3.63 sec)

NOTE : So we have 0 – 8191 Block_ids which is 8192 blocks in each buffer_pool_instances

Number of pages in each Innodb Buffer pool instance

Each buffer pool instance would contain 65536 pages if that instance size is 1 GB.

select POOL_ID,count(1) Page_Cnt from INFORMATION_SCHEMA.INNODB_BUFFER_PAGE group by POOL_ID;
+---------+----------+
| POOL_ID | Page_Cnt |
+---------+----------+
|       0 |    65531 |
|       1 |    65530 |
|       2 |    65532 |
|       3 |    65533 |
|       4 |    65529 |
|       5 |    65530 |
|       6 |    65529 |
|       7 |    65529 |
+---------+----------+
8 rows in set (2.95 sec)

What is a Block ? How is it further broken down ?

Can be found referred as buffer pool block ID in INFORMATION_SCHEMA.INNODB_BUFFER_PAGE table. As mentioned earlier in the diagram each block contains 8 pages.

mysql> select POOL_ID,BLOCK_ID,count(1) Page_Cnt from INFORMATION_SCHEMA.INNODB_BUFFER_PAGE where POOL_ID=0 group by POOL_ID,BLOCK_ID;
+---------+----------+----------+
| POOL_ID | BLOCK_ID | Page_Cnt |
+---------+----------+----------+
|       0 |        0 |        8 |
|       0 |        1 |        8 |
|       0 |        2 |        8 |
|       0 |        3 |        8 |
.................................
.................................
.................................
.................................
.................................
|       0 |     8187 |        8 |
|       0 |     8188 |        8 |
|       0 |     8189 |        8 |
|       0 |     8190 |        8 |
|       0 |     8191 |        3 |
+---------+----------+----------+
8192 rows in set (2.90 sec)

So InnoDB page call be called as the smallest element here. Now let’s try to find some useful information from our metadata tables and make sure you perform these admin tasks with almost precaution.

Page Distribution in Innodb Buffer Pool Instance

So we now know Innodb stores the relevant data or indexes in pages and it can be assigned to any instance , block etc. But what does it actually stores in it ? How is InnoDB page further classified. Let’s try to understand this using below query.

select POOL_ID,PAGE_TYPE,PAGE_STATE,count(PAGE_STATE) from INFORMATION_SCHEMA.INNODB_BUFFER_PAGE where POOL_ID=0 group by POOL_ID,PAGE_TYPE,PAGE_STATE;

This shows be the different types of pages that are being used and being allocated to buffer pool ID 0. The page types tells us the kind of information/data has been loaded from those pages into buffer pool memory.

+---------+-------------------+------------+-------------------+
| POOL_ID | PAGE_TYPE         | PAGE_STATE | count(PAGE_STATE) |
+---------+-------------------+------------+-------------------+
|       0 | UNDO_LOG          | FILE_PAGE  |               127 |
|       0 | SYSTEM            | FILE_PAGE  |                 4 |
|       0 | INDEX             | FILE_PAGE  |             17522 |
|       0 | INODE             | FILE_PAGE  |                 9 |
|       0 | LOB_FIRST         | FILE_PAGE  |              3820 |
|       0 | SDI_INDEX         | FILE_PAGE  |                30 |
|       0 | IBUF_BITMAP       | FILE_PAGE  |                10 |
|       0 | FILE_SPACE_HEADER | FILE_PAGE  |                 8 |
|       0 | EXTENT_DESCRIPTOR | FILE_PAGE  |                 1 |
|       0 | LOB_DATA          | FILE_PAGE  |              3253 |
|       0 | ALLOCATED         | FILE_PAGE  |                26 |
|       0 | IBUF_INDEX        | FILE_PAGE  |                 5 |
|       0 | UNKNOWN           | NOT_USED   |             40717 |
+---------+-------------------+------------+-------------------

Allocated Page Types

Descriptions of page type values and pool IDs across which the data is spread across.

Page TypePOOL_IDPage_CountDescription
ALLOCATED0,1,2,3,4,5,6,7255Freshly allocated page
EXTENT_DESCRIPTOR0,1,3,4,5,6,715Extent descriptor page
FILE_SPACE_HEADER0,1,2,3,4,5,6,759File space header
IBUF_BITMAP0,1,2,3,4,5,6,793Insert buffer bitmap
IBUF_FREE_LIST29Insert buffer free list
IBUF_INDEX0,1,2,385Insert buffer index
INDEX0,1,2,3,4,5,6,7112397B-tree node
INODE0,1,2,3,4,5,6,762Index node
LOB_DATA0,1,2,3,4,5,6,727426Uncompressed LOB data
LOB_FIRST0,1,2,3,4,5,6,733085First page of uncompressed LOB
LOB_INDEX1,4,55Uncompressed LOB index
RSEG_ARRAY1,42Rollback segment array
SDI_INDEX0,1,2,3,5,6,753SDI index
SYSTEM0,1,2,3,4,6,7387System page
TRX_SYSTEM31Transaction system data
UNDO_LOG0,1,2,3,4,6,71068Undo log page
UNKNOWN0,1,2,3,4,5,6,7349242Unknown

Unallocated Page Types

These are the other set of page types to store BLOB , Compressed or encrypted objects as listed below. Since in this use case we don’t have any objects fitting thes

e page types they are left unused.

Page TypeDescription
BLOBUncompressed BLOB page
COMPRESSED_BLOB2Subsequent comp BLOB page
COMPRESSED_BLOBFirst compressed BLOB page
ENCRYPTED_RTREEEncrypted R-tree
FIL_PAGE_TYPE_UNUSEDUnused
PAGE_IO_COMPRESSEDCompressed page
PAGE_IO_COMPRESSED_ENCRYPTEDCompressed and encrypted page
PAGE_IO_ENCRYPTEDEncrypted page
RTREE_INDEXR-tree index
SDI_BLOBUncompressed SDI BLOB [ Serialized Dictionary Information]
SDI_COMPRESSED_BLOBCompressed SDI BLOB
ZLOB_DATACompressed LOB data
ZLOB_FIRSTFirst page of compressed LOB [ Large Objects ]
ZLOB_FRAGCompressed LOB fragment
ZLOB_FRAG_ENTRYCompressed LOB fragment index
ZLOB_INDEXCompressed LOB index

Pages that contain system data

Below query tells us the approximate number of pages that contain system data and the total number of buffer pool pages, and approximate percentage of pages that contain system data.

SELECT
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE
WHERE TABLE_NAME IS NULL OR (INSTR(TABLE_NAME, '/') = 0 AND INSTR(TABLE_NAME, '.') = 0)) AS system_pages,
(SELECT COUNT(*)FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE) AS total_pages,
(SELECT ROUND((system_pages/total_pages) * 100)) AS system_page_percentage;

Sample Data:
+--------------+-------------+------------------------+
| system_pages | total_pages | system_page_percentage |
+--------------+-------------+------------------------+
|       437045 |      524244 |                     83 |
+--------------+-------------+------------------------+

Type of System Data loaded in Buffer Pool

The type of system data in the buffer pool can be determined by querying the PAGE_TYPE value.

SELECT PAGE_TYPE, count(1) Page_Count FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE
WHERE TABLE_NAME IS NULL OR (INSTR(TABLE_NAME, '/') = 0 AND INSTR(TABLE_NAME, '.') = 0) group by PAGE_TYPE;

Sample Data:
+-------------------+------------+
| PAGE_TYPE         | Page_Count |
+-------------------+------------+
| UNDO_LOG          |       1068 |
| SYSTEM            |        387 |
| INODE             |         66 |
| LOB_FIRST         |      33366 |
| SDI_INDEX         |         56 |
| IBUF_BITMAP       |         94 |
| FILE_SPACE_HEADER |         62 |
| INDEX             |      49730 |
| EXTENT_DESCRIPTOR |         15 |
| LOB_DATA          |      27422 |
| ALLOCATED         |        297 |
| IBUF_INDEX        |         85 |
| UNKNOWN           |     324380 |
| RSEG_ARRAY        |          2 |
| LOB_INDEX         |          5 |
| IBUF_FREE_LIST    |          9 |
| TRX_SYSTEM        |          1 |
+-------------------+------------+

Pages Containing User data

Below query tells us the approximate number of pages that contain user data, the total number of buffer pool pages, and an approximate percentage of pages that contain user data

SELECT
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE
WHERE TABLE_NAME IS NOT NULL AND (INSTR(TABLE_NAME, '/') > 0 OR INSTR(TABLE_NAME, '.') > 0)) AS user_pages,
(SELECT COUNT(*) FROM information_schema.INNODB_BUFFER_PAGE) AS total_pages,
(SELECT ROUND((user_pages/total_pages) * 100)) AS user_page_percentage;

Sample Data:
+------------+-------------+----------------------+
| user_pages | total_pages | user_page_percentage |
+------------+-------------+----------------------+
|      87199 |      524244 |                   17 |
+------------+-------------+----------------------+

Pages Containing Index Data

Below query tells us the query the INDEX_NAME column using the name of the index and the number of pages and total data size allocated to that index

 

SELECT INDEX_NAME, COUNT(*) AS Pages, ROUND(SUM(IF(COMPRESSED_SIZE = 0, @@GLOBAL.innodb_page_size, COMPRESSED_SIZE))/1024/1024) AS 'Total Data (MB)' FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE group by INDEX_NAME;

Sample Data:
+----------------------+--------+-----------------+
| INDEX_NAME           | Pages  | Total Data (MB) |
+----------------------+--------+-----------------+
| NULL                 | 437036 |            6829 |
| PRIMARY              |  70087 |            1095 |
| catalog_id           |      2 |               0 |
| default_collation_id |      2 |               0 |
| tablespace_id        |      7 |               0 |
| file_name            |      1 |               0 |
| name                 |     21 |               0 |
| table_id             |     60 |               1 |
| srs_id               |     18 |               0 |
| index_id             |     22 |               0 |
+----------------------+--------+-----------------+

Unused Buffer Pool Pages

Now this is my favourite one I used the below query to fetch count of unused buffer pool pages. This tells us an important metric of how much of the memory is over allocated to innodb buffer pool size which can be reclaimed or reused based on the use case. Generally when we suggest customer that memory is over allocated and we can reduce or downgrade the instance this metric would help to show how much of buffer pool is allowed but unused support our statement of over provisioning.

select POOL_ID,PAGE_TYPE,PAGE_STATE,count(PAGE_STATE) Page_Count from INFORMATION_SCHEMA.INNODB_BUFFER_PAGE where PAGE_STATE='NOT_USED' group by POOL_ID,PAGE_TYPE,PAGE_STATE;

Sample Data:
+---------+-----------+------------+------------+
| POOL_ID | PAGE_TYPE | PAGE_STATE | Page_Count |
+---------+-----------+------------+------------+
|       0 | UNKNOWN   | NOT_USED   |      44789 |
|       1 | UNKNOWN   | NOT_USED   |      44515 |
|       2 | UNKNOWN   | NOT_USED   |      44575 |
|       3 | UNKNOWN   | NOT_USED   |      45499 |
|       4 | UNKNOWN   | NOT_USED   |      44049 |
|       5 | UNKNOWN   | NOT_USED   |      44249 |
|       6 | UNKNOWN   | NOT_USED   |      42312 |
|       7 | UNKNOWN   | NOT_USED   |      44966 |
+---------+-----------+------------+------------+

Resizing Buffer Pool based on Unused Pages metrics

Like I said in this use case memory was over allocated so I was able to resize the buffer_pool from 8GB to 4GB and the unused page drastically dropped and memory got free back into this system. So the general formula is 50-70% of available RAM OR closer to the size of the database whichever is lesser.

+---------+-----------+------------+------------+
| POOL_ID | PAGE_TYPE | PAGE_STATE | Page_Count |
+---------+-----------+------------+------------+
|       0 | UNKNOWN   | NOT_USED   |      12022 |
|       1 | UNKNOWN   | NOT_USED   |      11749 |
|       2 | UNKNOWN   | NOT_USED   |      11808 |
|       3 | UNKNOWN   | NOT_USED   |      12732 |
|       4 | UNKNOWN   | NOT_USED   |      11285 |
|       5 | UNKNOWN   | NOT_USED   |      11485 |
|       6 | UNKNOWN   | NOT_USED   |       9547 |
|       7 | UNKNOWN   | NOT_USED   |      12202 |
+---------+-----------+------------+------------+

While I did this operation only thing you have to make sure is resizing shouldn’t effect your existing performance. Whereas in this case if you see the memory allocated to dictionary , database pages didn’t change only thing that change is free buffers which reduced significantly with 4 GB innodb buffer pool size.

8 GB InnoDB Buffer Pool Size4 GB InnoDB Buffer Pool Size
BUFFER POOL AND MEMORY
Total large memory allocated 0
Dictionary memory allocated 1473113
Buffer pool size 524244
Free buffers 349251
Database pages 174993
Old database pages 64433
Modified db pages 6355
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000
BUFFER POOL AND MEMORY
Total large memory allocated 0
Dictionary memory allocated 1473113
Buffer pool size 262123
Free buffers 87130
Database pages 174993
Old database pages 64433
Modified db pages 6467
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000

Conclusion

Innodb being the most critical portion of any mysql instance it is really important to understand how InnoDB Functions and how to find out the specifics related to innodb from the metadata tables and make use of them. Any operations on information_schema related to InnoDB can be performance impacting so please test them and use them carefully. Also I have not covered details about how Space_ID/File_ID are associated and what is its use etc ? Will be covering them shortly in the next blog.

Hope you find this information useful. Keep reading and follow us for more exclusive content for open source databases. Do not forget to reach out to our support is your would like to know about our services

Here are some of our blogs which you might find interesting.

%d bloggers like this: