Jun
28
2018
--

What To Do When MySQL Runs Out of Memory: Troubleshooting Guide

MySQL memory troubleshooting

MySQL memory troubleshootingTroubleshooting crashes is never a fun task, especially if MySQL does not report the cause of the crash. For example, when MySQL runs out of memory. Peter Zaitsev wrote a blog post in 2012: Troubleshooting MySQL Memory Usage with a lots of useful tips. With the new versions of MySQL (5.7+) and performance_schema we have the ability to troubleshoot MySQL memory allocation much more easily.

In this blog post I will show you how to use it.

First of all, there are 3 major cases when MySQL will crash due to running out of memory:

  1. MySQL tries to allocate more memory than available because we specifically told it to do so. For example: you did not set innodb_buffer_pool_size correctly. This is very easy to fix
  2. There is some other process(es) on the server that allocates RAM. It can be the application (java, python, php), web server or even the backup (i.e. mysqldump). When the source of the problem is identified, it is straightforward to fix.
  3. Memory leaks in MySQL. This is a worst case scenario, and we need to troubleshoot.

Where to start troubleshooting MySQL memory leaks

Here is what we can start with (assuming it is a Linux server):

Part 1: Linux OS and config check
  1. Identify the crash by checking mysql error log and Linux log file (i.e. /var/log/messages or /var/log/syslog). You may see an entry saying that OOM Killer killed MySQL. Whenever MySQL has been killed by OOM “dmesg” also shows details about the circumstances surrounding it.
  2. Check the available RAM:
    • free -g
    • cat /proc/meminfo
  3. Check what applications are using RAM: “top” or “htop” (see the resident vs virtual memory)
  4. Check mysql configuration: check /etc/my.cnf or in general /etc/my* (including /etc/mysql/* and other files). MySQL may be running with the different my.cnf (run
    ps  ax| grep mysql

     )

  5. Run
    vmstat 5 5

     to see if the system is reading/writing via virtual memory and if it is swapping

  6. For non-production environments we can use other tools (like Valgrind, gdb, etc) to examine MySQL usage
Part 2:  Checks inside MySQL

Now we can check things inside MySQL to look for potential MySQL memory leaks.

MySQL allocates memory in tons of places. Especially:

  • Table cache
  • Performance_schema (run:
    show engine performance_schema status

      and look at the last line). That may be the cause for the systems with small amount of RAM, i.e. 1G or less

  • InnoDB (run
    show engine innodb status

      and check the buffer pool section, memory allocated for buffer_pool and related caches)

  • Temporary tables in RAM (find all in-memory tables by running:
    select * from information_schema.tables where engine='MEMORY'

     )

  • Prepared statements, when it is not deallocated (check the number of prepared commands via deallocate command by running show global status like ‘
    Com_prepare_sql';show global status like 'Com_dealloc_sql'

      )

The good news is: starting with MySQL 5.7 we have memory allocation in performance_schema. Here is how we can use it

  1. First, we need to enable collecting memory metrics. Run:
    UPDATE setup_instruments SET ENABLED = 'YES'
    WHERE NAME LIKE 'memory/%';
  2. Run the report from sys schema:
    select event_name, current_alloc, high_alloc
    from sys.memory_global_by_current_bytes
    where current_count > 0;
  3. Usually this will give you the place in code when memory is allocated. It is usually self-explanatory. In some cases we can search for bugs or we might need to check the MySQL source code.

For example, for the bug where memory was over-allocated in triggers (https://bugs.mysql.com/bug.php?id=86821) the select shows:

mysql> select event_name, current_alloc, high_alloc from memory_global_by_current_bytes where current_count > 0;
+--------------------------------------------------------------------------------+---------------+-------------+
| event_name                                                                     | current_alloc | high_alloc  |
+--------------------------------------------------------------------------------+---------------+-------------+
| memory/innodb/buf_buf_pool                                                     | 7.29 GiB      | 7.29 GiB    |
| memory/sql/sp_head::main_mem_root                                              | 3.21 GiB      | 3.62 GiB    |
...

The largest chunk of RAM is usually the buffer pool but ~3G in stored procedures seems to be too high.

According to the MySQL source code documentation, sp_head represents one instance of a stored program which might be of any type (stored procedure, function, trigger, event). In the above case we have a potential memory leak.

In addition we can get a total report for each higher level event if we want to see from the birds eye what is eating memory:

mysql> select  substring_index(
    ->     substring_index(event_name, '/', 2),
    ->     '/',
    ->     -1
    ->   )  as event_type,
    ->   round(sum(CURRENT_NUMBER_OF_BYTES_USED)/1024/1024, 2) as MB_CURRENTLY_USED
    -> from performance_schema.memory_summary_global_by_event_name
    -> group by event_type
    -> having MB_CURRENTLY_USED>0;
+--------------------+-------------------+
| event_type         | MB_CURRENTLY_USED |
+--------------------+-------------------+
| innodb             |              0.61 |
| memory             |              0.21 |
| performance_schema |            106.26 |
| sql                |              0.79 |
+--------------------+-------------------+
4 rows in set (0.00 sec)

I hope those simple steps can help troubleshoot MySQL crashes due to running out of memory.

Links to more resources that might be of interest

The post What To Do When MySQL Runs Out of Memory: Troubleshooting Guide appeared first on Percona Database Performance Blog.

Jan
24
2014
--

MySQL server memory usage troubleshooting tips

There are many blog posts already written on topics related to “MySQL server memory usage,” but nevertheless there are some who still get confused when troubleshooting issues associated with memory usage for MySQL. As a Percona support engineer, I’m seeing many issues regularly related to heavy server loads – OR OOM killer got invoked and killed MySQL server due to high Memory usage… OR with a question like: “I don’t know why mysql is taking so much memory. How do I find where exactly memory is allocated? please help!”

There are many ways to check memory consumption of MySQL. So, I’m just trying here to explain it by combining all details that I know of in this post.

  • Check memory related Global/Session variables.

If you are using MyISAM then you need to check for Key_buffer_size, while using InnoDB, you can check innodb_buffer_pool_size,  innodb_additional_memory_pool_size, innodb_log_buffer_size,  innodb_sort_buffer_size (used only for sorting data while creating index in innodb, introduced from 5.6). max_connections, query_cache_size and table_cache are also important variables to check

We know that whenever a thread is connected to MySQL, it will need it’s own buffers when they are doing some complex operations like FTS,  sorting, creating temp tables etc. So we also need to check the size of read_buffer_size, sort_buffer_size, read_rnd_buffer_size and tmp_table_size.

There is a very good quote from High Performance MySQL, 3rd Edition: “ You can think of MySQL’s memory consumption as falling into two categories: the memory you can control, and the memory you can’t. You can’t control how much memory MySQL uses merely to run the server, parse queries, and manage its internals, but you have a lot of control over how much memory it uses for specific purposes.” So it seems we have to understand the purpose for configuring any variable… either it is Global or Session level. I would like to explain more about that here.

For the Global variables like key_buffer_size, query_cache_size etc,  MySQL always allocates and initializes the specified amount of memory all at once when the server starts. But it’s not happened for those who are global default but can be set as per-session variables, i.e  For read_buffer_size, sort_buffer_size, join_buffer_size, MySQL doesn’t allocate any memory for these buffers until query needs. But when a query needs, it immediately allocates the entire chunk of memory specified. So if there are even small sorts, full buffer size will be allocated which is just waste of memory. Even some buffers can be used multiple times. For example on queries that join several tables join_buffer can be allocated once per joined table. also some complicated queries including sub-queries can use multiple sort_buffers at the same time which can lead to high memory consumption. In some scenario, query didn’t even use sort_buffer whatever size is, as it select by primary key which will not allocate it. So it depends on the nature of your environment but I would say it’s always better to start with a safe variable value that can be larger than default if needed but not as large as it can consume all of the server’s memory.

One more thing,  not all per thread memory allocation is configured by variables.  Some of memory allocation per thread is done by MySQL itself for running complex processes/queries like “stored procedures” and it can take unlimited amount of memory while running. And sometimes, optimizer  can also take a lot of memory working with highly complex queries which generally we can’t control by any configuration parameter.

Even innodb_buffer_pool_size is not a hard limit, usually innodb uses 10% more memory than the one specified. Many people do not recommend using both storage engine MyISAM and InnoDB at the same time on production server. Because both have individual buffers which can eat all server memory.

For detailed information related to this topic, I would suggest reading this post from Peter Zaitsev titled “MySQL Server Memory Usage.”

  • Check “SHOW ENGINE INNODB STATUS” for section “BUFFER POOL AND MEMORY
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 137363456; in additional pool allocated 0
Dictionary memory allocated 76056
Buffer pool size 8191
Free buffers 7804
Database pages 387
Old database pages 0
Modified db pages 0

Above one is from Native MySQL but if you’ll check the same with Percona Server you’ll get some more information.

----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 137756672; in additional pool allocated 0
Total memory allocated by read views 88
Internal hash tables (constant factor + variable factor)
Adaptive hash index 2217584 (2213368 + 4216)
Page hash 139112 (buffer pool 0 only)
Dictionary cache 597885 (554768 + 43117)
File system 83536 (82672 + 864)
Lock system 333248 (332872 + 376)
Recovery system 0 (0 + 0)
Dictionary memory allocated 43117
Buffer pool size 8191
Buffer pool size, bytes 134201344
Free buffers 7760
Database pages 431
Old database pages 0
Modified db pages 0

This will give you information regarding how much memory is allocated by InnoDB. You can see here “Total Memory Allocated”, “Internal Hash Tables”, “Dictionary Memory Allocated”, “Buffer Pool Size” etc.

  • Profiling MySQL Memory usage with Valgrind Massif

Recently, I used this tool and surprisingly I got very good statistics about memory usage. Here the only problem is you have to shutdown the mysql, start it with valgrind massif and after collecting statistics, you again have to shutdown and normal start.

$ /etc/init.d/mysql stop
$ valgrind --tool=massif --massif-out-file=/tmp/massif.out /usr/sbin/mysqld
$ /etc/init.d/mysql restart

After getting massif.out file, you have to read it with ms_print command. You will see pretty nice graph and then statistics. i.e

[root@percona1 ~]# ms_print /tmp/massif.out
--------------------------------------------------------------------------------
Command:            /usr/sbin/mysqld
Massif arguments:   --massif-out-file=/tmp/massif.out
ms_print arguments: /tmp/massif.out
--------------------------------------------------------------------------------
    MB
50.22^         ##
     |         #
     |         #
     |         #
     |         #
     |         #
     |    :    #
     |    ::   #                      ::::::@:::::::::::::@:::@::::@:::@::::
     |    : @::# :::::@@::::::::::::::::::: @::: ::: :::::@:::@::: @:::@::::
     |    : @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::
     |    : @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::
     |    : @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::
     |    : @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@
     |    : @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@
     |   :: @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@
     |  ::: @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@
     | :::: @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@:
     | :::: @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@:
     | :::: @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@:
     | :::: @::# :  ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@:
   0 +----------------------------------------------------------------------->Mi
     0                                                                   575.9
Number of snapshots: 96
 Detailed snapshots: [1, 7, 11 (peak), 16, 35, 48, 58, 68, 78, 88]
--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
  0              0                0                0             0            0
  1      6,090,089          195,648          194,590         1,058            0
99.46% (194,590B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->48.79% (95,458B) 0x7A1D20: my_malloc (my_malloc.c:38)
| ->25.08% (49,060B) 0x6594F1: read_texts(char const*, char const*, char const***, unsigned int) (derror.cc:160)
| | ->25.08% (49,060B) 0x6597C2: init_errmessage() (derror.cc:69)
| |   ->25.08% (49,060B) 0x506232: init_common_variables() (mysqld.cc:3414)
| |     ->25.08% (49,060B) 0x508CBB: mysqld_main(int, char**) (mysqld.cc:4461)
| |       ->25.08% (49,060B) 0x5B2CD1B: (below main) (in /lib64/libc-2.12.so)
| |
| ->09.36% (18,317B) 0x789571: my_read_charset_file (charset.c:364)
| | ->09.36% (18,317B) 0x789DEC: init_available_charsets (charset.c:458)
| |   ->09.36% (18,317B) 0x4E35D31: pthread_once (in /lib64/libpthread-2.12.so)
| |     ->09.36% (18,317B) 0x789C80: get_charset_by_csname (charset.c:644)
| |       ->09.36% (18,317B) 0x5062E9: init_common_variables() (mysqld.cc:3439)
| |         ->09.36% (18,317B) 0x508CBB: mysqld_main(int, char**) (mysqld.cc:4461)
| |           ->09.36% (18,317B) 0x5B2CD1B: (below main) (in /lib64/libc-2.12.so)
| |
| ->08.37% (16,384B) 0x79DEEF: my_set_max_open_files (my_file.c:105)
| | ->08.37% (16,384B) 0x506169: init_common_variables() (mysqld.cc:3373)
| |   ->08.37% (16,384B) 0x508CBB: mysqld_main(int, char**) (mysqld.cc:4461)
| |     ->08.37% (16,384B) 0x5B2CD1B: (below main) (in /lib64/libc-2.12.so)
| |
| ->04.36% (8,536B) 0x788DB4: init_dynamic_array2 (array.c:70)
| | ->02.45% (4,800B) 0x5CD51A: add_status_vars(st_mysql_show_var*) (sql_show.cc:2062)
| | | ->02.45% (4,800B) 0x505E68: init_common_variables() (mysqld.cc:3245)
| | |   ->02.45% (4,800B) 0x508CBB: mysqld_main(int, char**) (mysqld.cc:4461)
| | |     ->02.45% (4,800B) 0x5B2CD1B: (below main) (in /lib64/libc-2.12.so)
| | |

You can see from the output that where memory is allocated, to which function etc. You can use this tool to find memory leaks. You can get more information here for how to install and use it. Here’s another related post by Roel Van de Paar titled: “Profiling MySQL Memory Usage With Valgrind Massif.”

If possible Valgrind massif should not be used on busy production server as it can degrade the performance. Generally it’s used to find memory leak by creating mirror environment on test/stage server and run on it. It needs debug binary to run so it decreases performance a lot. So it can be used for investigating some cases but not for regular use.

  • Check Plot memory usage by monitoring ps output. 

This also useful when you want to check how much virtual(VSZ) and real memory (RSS) is used by mysqld. You can either simply run some bash script for monitoring it like

while true
do
  date >> ps.log
  ps aux | grep mysqld >> ps.log
  sleep 60
done


Or you can also check when needed from shell prompt with “ps aux | grep mysqld” command. 

  • Memory tables in MySQL 5.7

With MySQL 5.7, some very interesting memory statistics tables are introduced to check memory usage in performance_schema.  There is no any detailed documentation available yet but you can check some details here.  http://dev.mysql.com/doc/refman/5.7/en/memory-summary-tables.html

In P_S, there are five memory summary tables.

mysql> show tables like '%memory%';
+-----------------------------------------+
| Tables_in_performance_schema (%memory%) |
+-----------------------------------------+
| memory_summary_by_account_by_event_name |
| memory_summary_by_host_by_event_name |
| memory_summary_by_thread_by_event_name |
| memory_summary_by_user_by_event_name |
| memory_summary_global_by_event_name |
+-----------------------------------------+
5 rows in set (0.00 sec)

So with every event you can get summarized memory consumption for a particular account, host, thread and user. While checking more, I found that there are around 209 different events to check. I have just tried to check one event related to join buffer size.

mysql> select * from memory_summary_by_account_by_event_name where SUM_NUMBER_OF_BYTES_ALLOC <> 0 and user = 'msandbox' and event_name = 'memory/sql/JOIN_CACHE' \G
*************************** 1. row ***************************
USER: msandbox
HOST: localhost
EVENT_NAME: memory/sql/JOIN_CACHE
COUNT_ALLOC: 2
COUNT_FREE: 2
SUM_NUMBER_OF_BYTES_ALLOC: 524288
SUM_NUMBER_OF_BYTES_FREE: 524288
LOW_COUNT_USED: 0
CURRENT_COUNT_USED: 0
HIGH_COUNT_USED: 1
LOW_NUMBER_OF_BYTES_USED: 0
CURRENT_NUMBER_OF_BYTES_USED: 0
HIGH_NUMBER_OF_BYTES_USED: 262144
1 row in set (0.00 sec)
mysql> show global variables like 'join%';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| join_buffer_size | 262144 |
+------------------+--------+
1 row in set (0.00 sec)
mysql>

Here, COUNT_ALLOC, COUNT_FREE are aggregate the number of calls to malloc-like and free-like functions. SUM_NUMBER_OF_BYTES_ALLOC and SUM_NUMBER_OF_BYTES_FREE are indicate the aggregate size of allocated and freed memory blocks. CURRENT_COUNT_USED is the aggregate number of currently allocated blocks that have not been freed yet. CURRENT_NUMBER_OF_BYTES_USED is the aggregate size of currently allocated memory blocks that have not been freed yet. LOW_ and HIGH_ are low and high water marks corresponding to the columns. 

If you are aware about these scripts then, these are giving very good summary about overall server memory consumption as well as related to MySQL.

Like in output of pt-summary,

# Memory #####################################################
Total | 11.8G
Free | 143.7M
Used | physical = 11.6G, swap allocated = 4.0G, swap used = 0.0, virtual = 11.6G
Buffers | 224.9M
Caches | 6.2G
Dirty | 164 kB
UsedRSS | 4.8G

In output of pt-mysql-summary.

# Query cache ################################################
query_cache_type | OFF
Size | 0.0
Usage | 0%
HitToInsertRatio | 0%
# InnoDB #####################################################
Version | 5.5.30-rel30.2
Buffer Pool Size | 4.0G
Buffer Pool Fill | 35%
Buffer Pool Dirty | 1%
# MyISAM #####################################################
Key Cache | 32.0M
Pct Used | 20%
Unflushed | 0%

Conclusion: 

It is really important for us to know where MySQL allocates memory and how it affects the overall load on the MySQL server and performance. I have just tried here to describe a few ways but I still think that we should have some sort of script or something that can combine all of these results and gives us some truthful output of memory usage in MySQL.

The post MySQL server memory usage troubleshooting tips appeared first on MySQL Performance Blog.

Powered by WordPress | Theme: Aeros 2.0 by TheBuckmaker.com