Bug #51272 Major performance issue found running sysbench
Submitted: 18 Feb 2010 7:48 Modified: 22 Mar 2010 20:26
Reporter: Georgi Kodinov Email Updates:
Status: Not a Bug Impact on me:
None 
Category:MySQL Server Severity:S2 (Serious)
Version:mysql-next-mr OS:Any
Assigned to: Davi Arnaut CPU Architecture:Any

[18 Feb 2010 7:48] Georgi Kodinov
Description:
I found a very serious performance issue in the mysql-next-mr
tree. It's quite likely to also exist in the 5.1.44 release.

I managed to drill down to the following patch which fixes the
performance issue.

It improves the performance from 9500tps to 17700 tps for readonly
sysbench.

I am still puzzled about this, it's not the normal issue I am used
to finding.

I do however have a theory about what causes the issue.

The problem I think is changes like this one:

-  char buff[FN_REFLEN+1];
+  char buff[FN_REFLEN];

Declaring a buffer which isn't aligned on a word boundary means
that things will still work, but the memcpy and other instructions
will have to use much less optimised instructions which move 1 byte
at a time instead of words at a time.

Solution here if FN_REFLEN isn't enough is to use an word-aligned
size (I think should be even aligned at 8 byte boundary but at least
at 4 bytes, but 8 bytes would be great here. Even better if buffer
is declared at top of declarations to ensure it is aligned on 8byte
boundary.

The patch I attached here fixes mysql-next-mr.

I think it is necessary to look for all kinds of issues like this,
there might be more of them that haven't been discovered before.

I'll make another round of experiments tomorrow to boil down even
further which part of this patch which is responsible for the
performance issue.

How to repeat:
readonly sysbench

Suggested fix:
=== modified file 'client/mysql.cc'
--- client/mysql.cc	2010-02-06 10:28:06 +0000
+++ client/mysql.cc	2010-02-17 22:28:56 +0000
@@ -3612,8 +3612,7 @@ print_table_data_vertically(MYSQL_RES *r
    for (uint off=0; off < mysql_num_fields(result); off++)
    {
      field= mysql_fetch_field(result);
-      if (column_names)
-        tee_fprintf(PAGER, "%*s: ",(int) max_length,field->name);
+      tee_fprintf(PAGER, "%*s: ",(int) max_length,field->name);
      if (cur[off])
      {
        unsigned int i;

=== modified file 'client/mysqldump.c'
--- client/mysqldump.c	2010-02-06 10:28:06 +0000
+++ client/mysqldump.c	2010-02-17 22:28:56 +0000
@@ -840,7 +840,7 @@ get_one_option(int optid, const struct m
                                    &err_ptr, &err_len);
      if (err_len)
      {
-        strmake(buff, err_ptr, min(sizeof(buff) - 1, err_len));
+        strmake(buff, err_ptr, min(sizeof(buff), err_len));
        fprintf(stderr, "Invalid mode to --compatible: %s\n", buff);
        exit(1);
      }
@@ -4630,7 +4630,7 @@ static ulong find_set(TYPELIB *lib, cons

      for (; pos != end && *pos != ','; pos++) ;
      var_len= (uint) (pos - start);
-      strmake(buff, start, min(sizeof(buff) - 1, var_len));
+      strmake(buff, start, min(sizeof(buff), var_len));
      find= find_type(buff, lib, var_len);
      if (!find)
      {

=== modified file 'include/config-win.h'
--- include/config-win.h	2010-01-18 20:19:19 +0000
+++ include/config-win.h	2010-02-17 22:28:56 +0000
@@ -190,7 +190,7 @@ typedef SSIZE_T ssize_t;
#define isnan(X) _isnan(X)
#define finite(X) _finite(X)

-#ifndef MYSQL_CLIENT_NO_THREADS
+#ifndef UNDEF_THREAD_HACK
#define THREAD
#endif
#define VOID_SIGHANDLER

=== modified file 'libmysql/libmysql.c'
--- libmysql/libmysql.c	2010-01-18 20:19:19 +0000
+++ libmysql/libmysql.c	2010-02-17 22:28:56 +0000
@@ -409,10 +409,7 @@ my_bool	STDCALL mysql_change_user(MYSQL 
  if (!passwd)
    passwd="";

-  /*
-    Store user into the buffer.
-    Advance position as strmake returns a pointer to the closing NUL.
-  */
+  /* Store user into the buffer */
  end= strmake(end, user, USERNAME_LENGTH) + 1;

  /* write scrambled password according to server capabilities */
@@ -900,7 +897,7 @@ mysql_list_fields(MYSQL *mysql, const ch
{
  MYSQL_RES   *result;
  MYSQL_FIELD *fields;
-  char	     buff[258],*end;
+  char	     buff[257],*end;
  DBUG_ENTER("mysql_list_fields");
  DBUG_PRINT("enter",("table: '%s'  wild: '%s'",table,wild ? wild : ""));

@@ -1906,7 +1903,7 @@ mysql_stmt_param_metadata(MYSQL_STMT *st

/* Store type of parameter in network buffer. */

-static void store_param_type(unsigned char **pos, MYSQL_BIND *param)
+static void store_param_type(char **pos, MYSQL_BIND *param)
{
  uint typecode= param->buffer_type | (param->is_unsigned ? 32768 : 0);
  int2store(*pos, typecode);
@@ -2185,7 +2182,7 @@ int cli_stmt_execute(MYSQL_STMT *stmt)
	that is sent to the server.
      */
      for (param= stmt->params;	param < param_end ; param++)
-        store_param_type(&net->write_pos, param);
+        store_param_type((char**) &net->write_pos, param);
    }

    for (param= stmt->params; param < param_end; param++)

=== modified file 'mysys/default.c'
--- mysys/default.c	2009-12-24 07:56:13 +0000
+++ mysys/default.c	2010-02-17 22:28:56 +0000
@@ -683,7 +683,7 @@ static int search_default_file_with_ext(
                                        int recursion_level)
{
  char name[FN_REFLEN + 10], buff[4096], curr_gr[4096], *ptr, *end, **tmp_ext;
-  char *value, option[4096+2], tmp[FN_REFLEN];
+  char *value, option[4096], tmp[FN_REFLEN];
  static const char includedir_keyword[]= "includedir";
  static const char include_keyword[]= "include";
  const int max_recursion_level= 10;

=== modified file 'mysys/mf_pack.c'
--- mysys/mf_pack.c	2009-12-18 18:44:24 +0000
+++ mysys/mf_pack.c	2010-02-17 22:28:56 +0000
@@ -245,7 +245,7 @@ my_bool my_use_symdir=0;	/* Set this if 
#ifdef USE_SYMDIR
void symdirget(char *dir)
{
-  char buff[FN_REFLEN+1];
+  char buff[FN_REFLEN];
  char *pos=strend(dir);
  if (dir[0] && pos[-1] != FN_DEVCHAR && my_access(dir, F_OK))
  {
@@ -257,7 +257,7 @@ void symdirget(char *dir)
    *pos++=temp; *pos=0;	  /* Restore old filename */
    if (file >= 0)
    {
-      if ((length= my_read(file, buff, sizeof(buff) - 1, MYF(0))) > 0)
+      if ((length= my_read(file, buff, sizeof(buff), MYF(0))) > 0)
      {
	for (pos= buff + length ;
	     pos > buff && (iscntrl(pos[-1]) || isspace(pos[-1])) ;

=== modified file 'sql/log.cc'
--- sql/log.cc	2010-02-15 11:16:49 +0000
+++ sql/log.cc	2010-02-17 22:28:56 +0000
@@ -2638,7 +2638,7 @@ const char *MYSQL_LOG::generate_name(con
  {
    char *p= fn_ext(log_name);
    uint length= (uint) (p - log_name);
-    strmake(buff, log_name, min(length, FN_REFLEN-1));
+    strmake(buff, log_name, min(length, FN_REFLEN));
    return (const char*)buff;
  }
  return log_name;
@@ -3901,7 +3901,7 @@ int MYSQL_BIN_LOG::purge_logs_before_dat
      if (stat_area.st_mtime < purge_time) 
        strmake(to_log, 
                log_info.log_file_name, 
-                sizeof(log_info.log_file_name) - 1);
+                sizeof(log_info.log_file_name));
      else
        break;
    }
@@ -5370,11 +5370,11 @@ bool flush_error_log()
  if (opt_error_log)
  {
    char err_renamed[FN_REFLEN], *end;
-    end= strmake(err_renamed,log_error_file,FN_REFLEN-5);
+    end= strmake(err_renamed,log_error_file,FN_REFLEN-4);
    strmov(end, "-old");
    mysql_mutex_lock(&LOCK_error_log);
#ifdef __WIN__
-    char err_temp[FN_REFLEN+5];
+    char err_temp[FN_REFLEN+4];
    /*
     On Windows is necessary a temporary file for to rename
     the current error file.

=== modified file 'sql/sql_acl.cc'
--- sql/sql_acl.cc	2010-02-15 11:16:49 +0000
+++ sql/sql_acl.cc	2010-02-17 22:28:56 +0000
@@ -1044,7 +1044,7 @@ int acl_getroot(THD *thd, USER_RESOURCES
    *mqh= acl_user->user_resource;

    if (acl_user->host.hostname)
-      strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME - 1);
+      strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME);
    else
      *sctx->priv_host= 0;
  }
@@ -1145,7 +1145,7 @@ bool acl_getroot_no_password(Security_co
    sctx->priv_user= acl_user->user ? user : (char *) "";

    if (acl_user->host.hostname)
-      strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME - 1);
+      strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME);
    else
      *sctx->priv_host= 0;
  }

=== modified file 'sql/sql_connect.cc'
--- sql/sql_connect.cc	2010-01-25 16:48:45 +0000
+++ sql/sql_connect.cc	2010-02-17 22:28:56 +0000
@@ -727,7 +727,7 @@ static int check_connection(THD *thd)
  ulong server_capabilites;
  {
    /* buff[] needs to big enough to hold the server_version variable */
-    char buff[SERVER_VERSION_LENGTH + 1 + SCRAMBLE_LENGTH + 1 + 64];
+    char buff[SERVER_VERSION_LENGTH + SCRAMBLE_LENGTH + 64];
    server_capabilites= CLIENT_BASIC_FLAGS;

    if (opt_using_transactions)

=== modified file 'sql/sql_table.cc'
--- sql/sql_table.cc	2010-02-15 11:16:49 +0000
+++ sql/sql_table.cc	2010-02-17 22:28:56 +0000
@@ -2715,7 +2715,7 @@ mysql_prepare_create_table(THD *thd, HA_
	!(sql_field->charset= get_charset_by_csname(sql_field->charset->csname,
						    MY_CS_BINSORT,MYF(0))))
    {
-      char tmp[65];
+      char tmp[64];
      strmake(strmake(tmp, save_cs->csname, sizeof(tmp)-4),
              STRING_WITH_LEN("_bin"));
      my_error(ER_UNKNOWN_COLLATION, MYF(0), tmp);
[18 Feb 2010 7:53] Giuseppe Maxia
probably related to Bug#51249
[18 Feb 2010 8:11] Mattias Jonsson
The proposed patch uses strmake with too short length!

see bug#48983 which can be the cause of the performance degradation (but avoids buffer overflow by one byte...)
[18 Feb 2010 15:30] Mikael Ronström
A smaller patch that fixes the issue

Attachment: fix2.patch (text/x-diff), 4.08 KiB.

[18 Feb 2010 15:44] Mikael Ronström
Yet smaller patch that fixes the issue

Attachment: fix3.patch (text/x-diff), 2.84 KiB.

[18 Feb 2010 17:06] Mikael Ronström
And even smaller

Attachment: fix4.patch (text/x-diff), 1.88 KiB.

[18 Feb 2010 17:51] Mikael Ronström
Minimal patch that fixes issue

Attachment: fix5.patch (text/x-diff), 1.38 KiB.

[19 Mar 2010 9:34] Mikael Ronström
Reverified on latest mysql-trunk also with normal compilation
-O2.
[19 Mar 2010 14:51] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/103857

3122 Mikael Ronstrom	2010-03-19
      BUG#51272, revert of previous bug fix which causes performance regression
[22 Mar 2010 20:26] Davi Arnaut
After further analysis, it turns out that the higher value is bogus. Due to the problem described in Bug#48284 (issue involving the C API for prepared statements), sysbench would send to the server queries with a impossible where clause, but wouldn't detect it as the queries results were simple being discarded.