Friday, May 3, 2013

How to tune the MySQL Server

This blog post is part of blog series
    To continue now with the server itself:

    Well besides just saying do what Peter says, lets review examples of how they can solve this themselves.

    For starters, you can compare you my.cnf file against the Q&A version that is available for free via Percona.  It this an ideal solution? No, but it will allow you to take a fresh look at your configuration file after you answers all of their questions, via their configuration wizard.


    innodb_buffer_pool_size –
    select @@innodb_buffer_pool_size;

    Setting the innodb_buffer_pool_size is by far one of the most important settings for a MySQL InnoDB database.Some good reading on this topic is below:
    You need to keep in mind that there is not a "one size fits all query" for setting the innodb_buffer_pool_size.  We do have some common guidelines and suggestions that have surfaced over the years (seen below) and we also have several different theories about the best options.

    SELECT CEILING(Total_InnoDB_Bytes*1.6/POWER(1024,3)) RIBPS
    FROM (SELECT SUM(data_length+index_length) Total_InnoDB_Bytes
    FROM information_schema.tables WHERE engine='InnoDB') A;


    SELECT CONCAT(ROUND(KBS/POWER(1024,
    IF(PowerOf1024<0,0,IF(PowerOf1024>3,0,PowerOf1024)))+0.49999),
    SUBSTR(' KMG',IF(PowerOf1024<0,0,
    IF(PowerOf1024>3,0,PowerOf1024))+1,1)) recommended_innodb_buffer_pool_size
    FROM (SELECT SUM(data_length+index_length) KBS FROM information_schema.tables
    WHERE engine='InnoDB') A,
    (SELECT 3 PowerOf1024) B;

    It is safe to say that if you have the default 8M set as your innodb_buffer_pool_size  variable then this should be one of the first settings you adjust.

    Basically, allow this to have all of the available memory you can afford to give it. This of course takes into account several factors, like what else needs to allocate memory on the server ?  Keep in mind that Disk I/O is important to performance and the more memory you allow this variable to have the less disk I/O  should be required for table access.

    Let us also consider this....
    Since a general recommendation is to set the innodb_log_file_size at  25 % of the buffer pool size we should evaluate what we have for the innodb_log_file_size per Baron's concepts then look to see how the relates to the innodb_buffer_pool_size.

    For example, following the logic from Baron's post:
    > show engine innodb status\G select sleep(60); show engine innodb status\G
    Log sequence number 3982683217
    1 row in set (0.01 sec)

    1 row in set (59.99 sec)

    Log sequence number 3991367755
    1 row in set (0.01 sec)

    > SET @sequence1=3982683217;
    Query OK, 0 rows affected (0.00 sec)
    > SET @sequence2=3991367755;
    Query OK, 0 rows affected (0.00 sec)
    > select (@sequence2 - @sequence1) / 1024 / 1024 as MB_per_min;
    +------------+
    | MB_per_min |
    +------------+
    | 8.28222084 |
    +------------+
    1 row in set (0.00 sec)
    > select ( (@sequence2 - @sequence1) / 1024 / 1024 )* 60 as MB_per_hour ;
    +--------------+
    | MB_per_hour  |
    +--------------+
    | 496.93325043 |
    +--------------+
    1 row in set (0.00 sec)
    > select ( ( (@sequence2 - @sequence1) / 1024 / 1024 )* 60 ) * 4 as estimated_buffer_pool ;
    +-----------------------+
    | estimated_buffer_pool |
    +-----------------------+
    |         1987.73300171 |
    +-----------------------+
    1 row in set (0.00 sec)
    > select ( ( ( (@sequence2 - @sequence1) / 1024 / 1024 )* 60 ) * 4 ) / 1024 as estimated_buffer_pool_GB ;
    +--------------------------+
    | estimated_buffer_pool_GB |
    +--------------------------+
    |           1.941145509481 |
    +--------------------------+
    1 row in set (0.00 sec)

    Now when you compare these results to the "guidelines"
    SELECT CONCAT(ROUND(KBS/POWER(1024,
        -> IF(PowerOf1024<0,0,IF(PowerOf1024>3,0,PowerOf1024)))+0.49999),
        -> SUBSTR(' KMG',IF(PowerOf1024<0,0,
        -> IF(PowerOf1024>3,0,PowerOf1024))+1,1)) recommended_innodb_buffer_pool_size
        -> FROM (SELECT SUM(data_length+index_length) KBS FROM information_schema.tables
        -> WHERE engine='InnoDB') A,
        -> (SELECT 3 PowerOf1024) B;
    +-------------------------------------+
    | recommended_innodb_buffer_pool_size |
    +-------------------------------------+
    | 1G                                  |
    +-------------------------------------+

    You get a different result.   While Baron's check is dependent on the time frame it is executed within (which is why you should check this during peak traffic)  it can give you a more realistic view on YOUR traffic and use.  I would look into setting the  innodb_buffer_pool_size to 2G not 1G in this simple example.

    SET @sequence1=3982683217;
    SET @sequence2=3991367755;
    select (@sequence2 - @sequence1) / 1024 / 1024 as MB_per_min;
    select ( (@sequence2 - @sequence1) / 1024 / 1024 )* 60 as MB_per_hour ;
    select ( ( (@sequence2 - @sequence1) / 1024 / 1024 )* 60 ) * 4 as estimated_buffer_pool ;
    select ( ( ( (@sequence2 - @sequence1) / 1024 / 1024 )* 60 ) * 4 ) / 1024 as estimated_buffer_pool_GB ;



    innodb_log_file_size–
    select @@innodb_log_file_size;

    I have found that I enjoy Baron's concepts on how to set the innodb_log_file_size.  Remember to execute his suggestions during peak times to get true readings. The larger the size of these files the better performance with large data sets but nothing comes for free, you will increase the recovery times. Increased recovery times might not sound like a major concern to some, until it is a revenue dependent database that you are waiting and watching process forever.  A general recommendation is to set it at  25 % of the buffer pool size.

    select ( @@innodb_buffer_pool_size * .25 )


    innodb_log_buffer_size –
    select @@innodb_log_buffer_size; 

    I recall I once had this set on a server to innodb_log_buffer_size=128M.  Was it a good choice? No it means I wanted memory when I likely did not have to. It was a very write heavy database but this setting was very likely to high. Focus on the defaults first and then double it ( 8MB - 16MB ) max.


    innodb_additional_mem_pool_size–
    select @@innodb_additional_mem_pool_size;

    Peter addresses this setting in his blog post. You can address it and evaluate options after other settings have been addressed in my opinion.


    innodb_flush_log_at_trx_commit–
    select @@innodb_flush_log_at_trx_commit;

    > set GLOBAL innodb_flush_log_at_trx_commit =2; 
    Peter addresses this setting in his blog post as well. This is a commonly overlooked variable and worth testing performance improvement by adjusting from 0 - 2. Keep in mind the risks.


    thread_cache –
    select @@thread_cache_size;
    +---------------------+
    | @@thread_cache_size |
    +---------------------+
    |                  50 |
    +---------------------+
    >show status like 'threads_created';
    +-----------------+-------+
    | Variable_name   | Value |
    +-----------------+-------+
    | Threads_created | 4     |
    +-----------------+-------+
    1 row in set (0.00 sec)

    > show status like 'connections';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | Connections   | 962   |
    +---------------+-------+
    1 row in set (0.00 sec)

    > SELECT 100 - ((4 / 962) * 100);
    +-------------------------+
    | 100 - ((4 / 962) * 100) |
    +-------------------------+
    |                 99.5842 |
    +-------------------------+

    Keep an eye on this status : show status like 'threads_created';
    If it climbs then you have it set too low and it needs to be raised.


    query_cache_size  –
    select ((@@query_cache_size / 1024) / 1024);

    The permissible values are multiples of 1024.
    Pay attention to this setting the more read dependent you application becomes.  I used to run with it set to key_buffer=16M and I could and likely should have doubled that.


    key_buffer_size –
    select @@key_buffer_size;

    With MySQL switching to InnoDB as the default storage engine it is likely some we could see fewer and fewer tables created as MyISAM. That is not a fact just an opinion. The key_buffer_size will very important to you if you do utilize MyISAM tables.

    Review Shlomi Noach's post to help you find which easily which tables which engine type.
    "See per table size (almost exactly as presented in INFORMATION_SCHEMA)" -- Shlomi Noach.

    > SHOW VARIABLES LIKE 'key_buffer_size';
    +-----------------+----------+
    | Variable_name   | Value    |
    +-----------------+----------+
    | key_buffer_size | 15728640 |
    +-----------------+----------+


    table_cache –
    show variables like '%table%cache%';
    +----------------------------+-------+
    | Variable_name              | Value |
    +----------------------------+-------+
    | table_definition_cache     | 4096  |
    | table_open_cache           | 4096  |
    | table_open_cache_instances | 1     |
    +----------------------------+-------+

    According to the manual this is a good formula to help determine the table_definition_cache size.
    SELECT 400 + (@@table_open_cache / 2);

    > SHOW status like '%Opened_tables%';

    table_open_cache_instances
    A value of 8 or 16 is recommended on systems that routinely use 16 or more cores, the default is 1.


    Hopefully this exposes the variables and settings that need to be reviewed to help you get the best from your MySQL database.

    Benchmarking:: MySQL, CPU, File I/O, Memory :: Sysbench & Apache Benchmark


    Decide which tool you would like to use:
    I will use Sysbench for this example:
    First download sysbench
    Install :
    SysBench manual
    Review your options :
    # sysbench  --test=oltp help
     
    Compiled-in tests:
      fileio - File I/O test
      cpu - CPU performance test
      memory - Memory functions speed test
      threads - Threads subsystem performance test
      mutex - Mutex performance test
      oltp - OLTP test

    Commands: prepare run cleanup help version

    Some examples below:

    fileio - File I/O test
    # sysbench --test=fileio help
    fileio options:
      --file-num=N                  number of files to create [128]
      --file-block-size=N           block size to use in all IO operations [16384]
      --file-total-size=SIZE        total size of files to create [2G]
      --file-test-mode=STRING       test mode {seqwr, seqrewr, seqrd, rndrd, rndwr, rndrw}
      --file-io-mode=STRING         file operations mode {sync,async,fastmmap,slowmmap} [sync]
      --file-extra-flags=STRING     additional flags to use on opening files {sync,dsync,direct} []
      --file-fsync-freq=N           do fsync() after this number of requests (0 - don't use fsync()) [100]
      --file-fsync-all=[on|off]     do fsync() after each write operation [off]
      --file-fsync-end=[on|off]     do fsync() at the end of test [on]
      --file-fsync-mode=STRING      which method to use for synchronization {fsync, fdatasync} [fsync]
      --file-merged-requests=N      merge at most this number of IO requests if possible (0 - don't merge) [0]
      --file-rw-ratio=N             reads/writes ratio for combined test [1.5]

    • # sysbench --test=fileio prepare
    • # sysbench --test=fileio --file-test-mode=rndwr run


    cpu - CPU performance test
    # sysbench --test=cpu help
    cpu options:
      --cpu-max-prime=N      upper limit for primes generator [10000]


    • # sysbench --test=cpu --num-threads=25  run


    memory - Memory functions speed test
    # sysbench --test=memory help 
    memory options:
      --memory-block-size=SIZE    size of memory block for test [1K]
      --memory-total-size=SIZE    total size of data to transfer [100G]
      --memory-scope=STRING       memory access scope {global,local} [global]
      --memory-hugetlb=[on|off]   allocate memory from HugeTLB pool [off]
      --memory-oper=STRING        type of memory operations {read, write, none} [write]
      --memory-access-mode=STRING memory access mode {seq,rnd} [seq]


    • # sysbench --test=memory <memory options> run

    threads - Threads subsystem performance test
    # sysbench --test=threads help  
    threads options:
      --thread-yields=N      number of yields to do per request [1000]
      --thread-locks=N       number of locks per thread [8]


    • # # sysbench --test=threads --num-threads=64 --test=threads --thread-yields=100 --thread-locks=6 run


    mutex - Mutex performance test
    # sysbench --test=mutex help   
    mutex options:
      --mutex-num=N        total size of mutex array [4096]
      --mutex-locks=N      number of mutex locks to do per thread [50000]
      --mutex-loops=N      number of empty loops to do inside mutex lock [10000]

    • # sysbench --test=mutex  --num-threads=64  run




    oltp - OLTP test
    # sysbench --test=oltp help
    oltp options:
      --oltp-test-mode=STRING         test type to use {simple,complex,nontrx,sp} [complex]
      --oltp-reconnect-mode=STRING    reconnect mode {session,transaction,query,random} [session]
      --oltp-sp-name=STRING           name of store procedure to call in SP test mode []
      --oltp-read-only=[on|off]       generate only 'read' queries (do not modify database) [off]
      --oltp-skip-trx=[on|off]        skip BEGIN/COMMIT statements [off]
      --oltp-range-size=N             range size for range queries [100]
      --oltp-point-selects=N          number of point selects [10]
      --oltp-simple-ranges=N          number of simple ranges [1]
      --oltp-sum-ranges=N             number of sum ranges [1]
      --oltp-order-ranges=N           number of ordered ranges [1]
      --oltp-distinct-ranges=N        number of distinct ranges [1]
      --oltp-index-updates=N          number of index update [1]
      --oltp-non-index-updates=N      number of non-index updates [1]
      --oltp-nontrx-mode=STRING       mode for non-transactional test {select, update_key, update_nokey, insert, delete} [select]
      --oltp-auto-inc=[on|off]        whether AUTO_INCREMENT (or equivalent) should be used on id column [on]
      --oltp-connect-delay=N          time in microseconds to sleep after connection to database [10000]
      --oltp-user-delay-min=N         minimum time in microseconds to sleep after each request [0]
      --oltp-user-delay-max=N         maximum time in microseconds to sleep after each request [0]
      --oltp-table-name=STRING        name of test table [sbtest]
      --oltp-table-size=N             number of records in test table [10000]
      --oltp-dist-type=STRING         random numbers distribution {uniform,gaussian,special} [special]
      --oltp-dist-iter=N              number of iterations used for numbers generation [12]
      --oltp-dist-pct=N               percentage of values to be treated as 'special' (for special distribution) [1]
      --oltp-dist-res=N               percentage of 'special' values to use (for special distribution) [75]

    General database options:

      --db-driver=STRING  specifies database driver to use ('help' to get list of available drivers)
      --db-ps-mode=STRING prepared statements usage mode {auto, disable} [auto]


    Compiled-in database drivers:
      mysql - MySQL driver

    mysql options:
      --mysql-host=[LIST,...]       MySQL server host [localhost]
      --mysql-port=N                MySQL server port [3306]
      --mysql-socket=STRING         MySQL socket
      --mysql-user=STRING           MySQL user [sbtest]
      --mysql-password=STRING       MySQL password []
      --mysql-db=STRING             MySQL database name [sbtest]
      --mysql-table-engine=STRING   storage engine to use for the test table {myisam,innodb,bdb,heap,ndbcluster,federated} [innodb]
      --mysql-engine-trx=STRING     whether storage engine used is transactional or not {yes,no,auto} [auto]
      --mysql-ssl=[on|off]          use SSL connections, if available in the client library [off]
      --myisam-max-rows=N           max-rows parameter for MyISAM tables [1000000]
      --mysql-create-options=STRING additional options passed to CREATE TABLE []

    sysbench expects to find the sbtest database so make sure and create that first or define what database you prefer.
    • sysbench --test=oltp help | grep sbtest
        --oltp-table-name=STRING        name of test table [sbtest]
        --mysql-user=STRING           MySQL user [sbtest]
        --mysql-db=STRING             MySQL database name [sbtest]

    # sysbench  --test=oltp  --mysql-host=localhost --mysql-user=root --mysql-password=<Password here> --mysql-table-engine=innodb prepare

    # sysbench  --test=oltp  --mysql-host=localhost --mysql-user=root --mysql-password=<Password here> --mysql-table-engine=innodb --num-threads=25  run

    Then keep an eye on your results as well as database as the tests run.



    Apache Benchmark  &||
    WWW::Mechanize::Firefox

     The key take away here is to use these tools to benchmark your application and process not just a single factor of the architecture.

    For example, if you expect to have 20,000 users an hour filling out your web form then you should benchmark your application to handle that.  You can use the tools above to mimic the posts, for just one example by passing tokens, and then test the code that is in place on your application.  A test like this then will test the code base and with the proper logging in place (which you could also turn on by a token) you can test how well the developers, dba and system is built.

    Very simple example of the concept :

    If you can open the url with elinks you can benchmark it.

    elinks http://www.google.com/search?ie=ISO-8859-1\&hl=en\&source=hp\&q=mysql

    # ab -n 10 -c 2  http://www.google.com/search?ie=ISO-8859-1\&hl=en\&source=hp\&q=mysql

    Of course you would execute this against your system at a much higher level to test how your application responds.

    MySQL Server Tuning Question

    I ran across this job posting the other day on elance.com or odesk.com.

    The person/company was looking for a "mySQL server tuning expert." Not a major concern but by seeing MySQL spelled mySQL, it gives a good idea that they are serious and do need help.

    The post continued to say how they want help beyond just table or index changes to speed up their queries. It currently takes 5 -10 minutes to return anything on their "8-core 8GB RAM dedicated server with a 27m record database."

    I will give them credit though, they did mention a good resource:
    "need someone to just make a few changes, things like this:
    http://www.mysqlperformanceblog.com/2006/09/29/what-to-tune-in-mysql-server-after-installation/"

    It sounds to me like the company does not put enough value on their data. They are looking for a cheap solution to help to solve, what sounds like a serious performance concern with their database. If they found the mysqlperformanceblog.com website could they not have found:
    Regardless it is true that Peter's post is a very valuable resource, and shows that the concern around MySQL performance is not a new one.
    First of all though, someone should look into the database logs and determine the true problem with this server, configuration, query optimization or both?

    "...takes 5 -10 minutes to return anything..." Really? Every query takes that long, or more realistically,  just the few important queries that might be written poorly? I would suggest another blog post to review: Baron's post.

    Have you checked out the MySQL Tuner as a starting point for the server performance?
    Additional tools/information are available:
    What version are they on? Big performance improvements have happened over the recent years.

    So what should they do next?

    Well this inspired me to create this current blog as well as a couple more to make small blog post series:

    How to Identify MySQL Slow Queries and related concerns

    This blog post is part of blog series
    What does the slow query log say? Does it include key queries of your application?

    How long has the MySQL server  executed queries in this state?
    Has it always been slow or these queries ran fine until a few weeks ago?
    What changed if anything?

    You start by also following a few guidelines by Shlomi Noach.
    He has a few helpful queries listed on this blog post.
      • "The following query returns the total size per engine per database." -- Shlomi Noach.
      • "See if some index is a prefix of another (in which case it is redundant)" -- Shlomi Noach.
    I would also suggest understanding the concepts and tools shown in Ronald's post

    So lets dig a little more into the slow query problem.

    This is a simple example so your results will vary.
     
    First gather some numbers:

    % Slow Queries
         Just demo data so your results will vary. 
    show status like 'Slow_queries';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | Slow_queries  | 7     |
    +---------------+-------+

    show status like 'Questions';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | Questions     | 84    |
    +---------------+-------+
    1 row in set (0.01 sec)

    SELECT (7 / 84) * 100 as "% Slow Queries";
    +----------------+
    | % Slow Queries |
    +----------------+
    |         8.3333 |
    +----------------+

    slow_query_log
    Gather the location, which the full time DBA should already know but just in case:
    show variables like '%slow_query%';
    +---------------------+-------------------------------+
    | Variable_name       | Value                         |
    +---------------------+-------------------------------+
    | slow_query_log      | ON                            |
    | slow_query_log_file | /var/lib/mysql/mysql-slow.log |
    +---------------------+-------------------------------+
    2 rows in set (0.00 sec)

    Start looking over these queries and running explains to see what might be the problem.
    If you want you can review some tools to help.
    “Different roads sometimes lead to the same castle.”
    ― George R.R. Martin, A Game of Thrones  

    “Somebody who only reads newspapers and at best books of contemporary authors looks to me like an extremely near-sighted person who scorns eyeglasses. He is completely dependent on the prejudices and fashions of his times, since he never gets to see or hear anything else.”
    ― Albert Einstein 


    For the best results, use more than one of these tools and ensure you get the big picture and understand what is being presented to you.
    Once you find the queries you are after, you need to evaluate them with  EXPLAIN. You will then have the required information to help you optimize the query and related indexes, if needed.
    More information on explain can be found below:

    Some additional concerns to help query performance.

     Query Cache Efficiency
        Just demo data so your results will vary. 

    > SELECT @@have_query_cache;
    +--------------------+
    | @@have_query_cache |
    +--------------------+
    | YES                |
    +--------------------+


    >show status like '%Qcache_hits%';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | Qcache_hits   | 32    |
    +---------------+-------+
    1 row in set (0.00 sec)

    > show status like '%Com_select%';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | Com_select    | 16    |
    +---------------+-------+
    1 row in set (0.00 sec)

    > SELECT ( 32 / (16 + 32)  ) * 100  AS "Query Cache Efficiency";
    +------------------------+
    | Query Cache Efficiency |
    +------------------------+
    |                66.6667 |
    +------------------------+
    1 row in set (0.00 sec)

    I do not want to rewrite what Peter has already written so please refer to his blog posts.
    Evaluate how efficient your query cache is. How deterministic are your queries ? 

     Joins that need an INDEX
            Just demo data so your results will vary.   
    > show status like '%Select_range_check%';
    +--------------------+-------+
    | Variable_name      | Value |
    +--------------------+-------+
    | Select_range_check | 0     |
    +--------------------+-------+

    > show status like '%Select_full_join%';
    +------------------+-------+
    | Variable_name    | Value |
    +------------------+-------+
    | Select_full_join | 1     |
    +------------------+-------+

    > SELECT (0 + 1) AS "# of Joins that need an index";

    #
    This is used below as the numerator in
    "# of Joins that need an index today"
    +-------------------------------+
    | # of Joins that need an index |
    +-------------------------------+
    |                             1 |
    +-------------------------------+  

    > show status like 'Uptime';
    +---------------+--------+
    | Variable_name | Value  |
    +---------------+--------+
    | Uptime        | 335243 |
    +---------------+--------+

    > SELECT (1/ (335243/86400 )) as " # of Joins that need an index today" ;
    +-------------------------------------+
    | # of Joins that need an index today |
    +-------------------------------------+
    |                              0.2577 |
    +-------------------------------------+

    Hopefully you can evaluate the slow queries found with the log as well as review your Query Cache Efficiency as well as find the Joins that need an index all from the  "select @@slow_query_log_file;"

     Hopefully this has gotten you started on solving a very old concern within MySQL.

    Wednesday, May 1, 2013

    How to find a MySQL DBA for your company.


    A company should look for someone with valid technical skills and a valid interest in the community you should then truly be able to find a great MySQL DBA. What you will then have to realize is that a MySQL DBA will not always be available in your region. Open yourself up to remote DBA work. You can find remote DBAs or remote DBA services either one can help you advance and stabilize your database. Don't just settle for what is available locally. 

    You also need to know the right places to look...


    Please find the following blog post helpful in your search for qualified DBAs.