1

I have a migration in rails that does the following:

class AddMissingIndexes < ActiveRecord::Migration[5.1]
  def change
    # Applications
    add_index :applications, :evid, length: { evid: 255 }
  end
end

This seems to run smoothly in my test environment

However when I run the migration in my production environment I get this error:

Mysql2::Error: Specified key was too long; max key length is 767 bytes: CREATE  INDEX `index_applications_on_evid`  ON `applications` (`evid`(255)) 

I'm trying to fix this issue using the second answer from this question

class AddMissingIndexes < ActiveRecord::Migration[5.1]
  def change
    # Applications
    add_index "applications", ["evid"], :name => :evid, :length => { :evid => 255 }
  end
end

However I want to make sure this works before making any further changes to the schema. So I need to be able to reproduce this error in my test environment.

Test environment:

+-------------------------+------------------+
| Variable_name           | Value            |
+-------------------------+------------------+
| innodb_version          | 5.5.62           |
| protocol_version        | 10               |
| slave_type_conversions  |                  |
| version                 | 5.5.62-0+deb8u1  |
| version_comment         | (Debian)         |
| version_compile_machine | x86_64           |
| version_compile_os      | debian-linux-gnu |
+-------------------------+------------------+

Production environment

+-------------------------+---------------------+
| Variable_name           | Value               |
+-------------------------+---------------------+
| innodb_version          | 5.6.40              |
| protocol_version        | 10                  |
| slave_type_conversions  |                     |
| version                 | 5.6.40-log          |
| version_comment         | Source distribution |
| version_compile_machine | x86_64              |
| version_compile_os      | Linux               |
+-------------------------+---------------------+

Using SHOW GLOBAL VARIABLES LIKE 'innodb_%'; I'm able to see my test environment database and this is what I found that my local db environment has the following variables like this:

Test Environment

innodb_file_format=Barracuda;
innodb_large_prefix=1;
innodb_file_per_table=1;
innodb_file_format_max=Barracuda;
innodb_strict_mode=1;
character_set_server='utf8mb4';

Production Environment

innodb_file_format=Antelope;
innodb_large_prefix=OFF;
innodb_file_per_table=ON;
innodb_file_format_max=Antelope;
innodb_strict_mode=OFF;
character_set_server='utf8mb4';

I tried to reproduce my production environment setting the variables going 1 at a time. But to no avail.

Alvaro Alday
  • 343
  • 3
  • 19

1 Answers1

1

Run SHOW CREATE TABLE applications\G in both your test and production environment. I predict the difference will be:

Test:

CREATE TABLE `applications` (
  ...
  `evid` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Production:

CREATE TABLE `applications` (
  ...
  `evid` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

An index must be able to fit in 767 bytes, as the error says. The utf8 character set counts 3 bytes per character toward this limit, so 3*255 = 765, which fits.

Whereas utf8mb4 counts 4 bytes per character. 4*255 = 1020, which is too long.

You can index VARCHAR(191) when you use utf8mb4, to stay within 767 bytes.

Alternatively, you can use newer InnoDB row format to support index size up to 3072 bytes. See mysql change innodb_large_prefix

If you want to avoid these sorts of surprises, it's important to run the same version of MySQL in your testing and production, and make sure you make the MySQL config options as close as possible, and make sure the table definitions are identical.


Re your updated question with innodb config variables.

I see your production environment has settings that mean it is unable to define tables with the Barracuda file format, which means no DYNAMIC row format, which means no innodb_large_prefix.

You need to make the settings match your test environment, and then you probably need to rebuild your table so it's really in Barracuda format with DYNAMIC row format.

I also recommend (again) that you upgrade your test server to the same MySQL version you run in production.

Also compare other config settings, to see if there are other differences (besides those that are appropriate to be different from production, like innodb_buffer_pool_size).

You should also make sure you use the same version of other parts of your tech stack like Linux version, Ruby version, etc. It's a well-known source of project instability and schedule delays if you are surprised by version incompatibilities, if you fail to make your dev and test environments match your production environment.

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • I ran that command in my test environment and got the following: `'evid' varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL ... ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC` – Alvaro Alday Feb 26 '19 at 19:22
  • How about `SELECT @@innodb_large_prefix;`? And do the same investigation in production to compare. – Bill Karwin Feb 26 '19 at 19:55
  • Adding executing that last command in test gives me a plain old 0 this is what production looks when I run SHOW CREATE TABLE applications\G in production `'evid' varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC |` – Alvaro Alday Feb 26 '19 at 21:02
  • `innodb_large_prefix` must be 1 (or ON) to allow large index sizes. Read: https://dev.mysql.com/doc/refman/5.6/en/innodb-parameters.html#sysvar_innodb_large_prefix But I don't know why it works in the test environment and not the other. – Bill Karwin Feb 26 '19 at 21:48
  • Is there a way to show how that is currently setup in my test environment? something like `SHOW GLOBAL innodb_file_format` – Alvaro Alday Feb 26 '19 at 22:28
  • There's no option to set the global default innodb_file_format until MySQL 5.7. You can query `SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES`. The large index size depends on `ROW_FORMAT=DYNAMIC`, and that depends on `FILE_FORMAT=Barracuda`. Also Barracuda depends on `innodb_file_per_table=true`. – Bill Karwin Feb 26 '19 at 22:46
  • My test environment `information_schema` includes: INNODB_BUFFER_PAGE INNODB_TRX INNODB_BUFFER_POOL_STATS INNODB_LOCK_WAITS INNODB_CMPMEM INNODB_CMP INNODB_LOCKS INNODB_CMPMEM_RESET INNODB_CMP_RESET INNODB_BUFFER_PAGE_LRU – Alvaro Alday Feb 26 '19 at 23:20
  • Oh, right, the [INNODB_SYS tables](https://dev.mysql.com/doc/refman/5.6/en/innodb-i_s-tables.html) were new in 5.6, so you won't be able to query them in 5.5. You really need to use the same version in test that you use in production! – Bill Karwin Feb 26 '19 at 23:26
  • I found a way to look at them using this command: `SHOW GLOBAL VARIABLES LIKE 'innodb_%';` I'll update my question with my findings. – Alvaro Alday Feb 27 '19 at 00:11