Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using utf8mb4 Specified key was too long; max key length is 767 bytes #651

Closed
gczh opened this issue Nov 21, 2015 · 18 comments
Closed

Using utf8mb4 Specified key was too long; max key length is 767 bytes #651

gczh opened this issue Nov 21, 2015 · 18 comments

Comments

@gczh
Copy link

gczh commented Nov 21, 2015

I'm using utf8mb4 for my database to support emoji. Not sure if anyone might encounter the same problem illustrated below. This might be a potential issue that should be highlighted in the docs. Perhaps the migrations file should always enforce utf8 for the CHARSET option.

Using:
JRuby 1.7.19 (1.9.3p551)
Rails 4.2.5
Activerecord (4.2.5)
paper_trail (4.0.0)

Error:

ActiveRecord::JDBCError: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Specified key was too long; max key length is 767 bytes: CREATE  INDEX `index_versions_on_item_type_and_item_id` ON `versions` (`item_type`(255), `item_id`)arjdbc/jdbc/RubyJdbcConnection.java:587:in `execute'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-jdbc-adapter-1.3.19/lib/arjdbc/jdbc/adapter.rb:595:in `_execute'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-jdbc-adapter-1.3.19/lib/arjdbc/jdbc/adapter.rb:571:in `execute'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/connection_adapters/abstract_adapter.rb:472:in `log'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activesupport-4.2.5/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/connection_adapters/abstract_adapter.rb:466:in `log'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-jdbc-adapter-1.3.19/lib/arjdbc/jdbc/adapter.rb:571:in `execute'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/connection_adapters/abstract/schema_statements.rb:557:in `add_index'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:665:in `method_missing'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:634:in `say_with_time'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:634:in `say_with_time'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:654:in `method_missing'
/Users/SOHAPPY/Downloads/roy-samples/CabernetSauvignon/db/migrate/20151121144702_create_versions.rb:18:in `change'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:608:in `exec_migration'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:592:in `migrate'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:591:in `migrate'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/connection_adapters/abstract/connection_pool.rb:292:in `with_connection'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:590:in `migrate'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:768:in `migrate'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:998:in `execute_migration_in_transaction'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:1046:in `ddl_transaction'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:997:in `execute_migration_in_transaction'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:959:in `migrate'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:955:in `migrate'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:823:in `up'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/migration.rb:801:in `migrate'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/tasks/database_tasks.rb:137:in `migrate'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/gems/activerecord-4.2.5/lib/active_record/railties/databases.rake:44:in `(root)'
/Users/SOHAPPY/.rvm/gems/jruby-1.7.19/bin/jruby_executable_hooks:15:in `(root)'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)

Fix:

class CreateVersions < ActiveRecord::Migration

  # The largest text column available in all supported RDBMS is
  #1024^3 - 1 bytes, roughly one gibibyte.  We specify a size
  # so that MySQL will use `longtext` instead of `text`.  Otherwise,
  # when serializing very large objects, `text` might not be big enough.
  TEXT_BYTES = 1_073_741_823

  def change
    create_table :versions, options: 'ROW_FORMAT=DYNAMIC ENGINE=InnoDB DEFAULT CHARSET=utf8' do |t|
      t.string   :item_type, :null => false
      t.integer  :item_id,   :null => false
      t.string   :event,     :null => false
      t.string   :whodunnit
      t.text     :object,    :limit => TEXT_BYTES
      t.datetime :created_at
    end
    add_index :versions, [:item_type, :item_id]
  end
end

Note:
Force CHARSET=utf8 on your versions migration created by rails g paper_trail:install

@batter
Copy link
Collaborator

batter commented Nov 22, 2015

Interesting.. I believe UTF-8 is the default for ActiveRecord / rails so I'm guessing this is probably rarely encountered. It seems that 767 bytes is the standard limit for a column using InnoDB. My question is whether the option string you are applying here will only be executed against databases with that setup or whether this could potentially cause errors if attempted to execute against other databases if we added it to the standard migration?

@ecopony
Copy link

ecopony commented Dec 1, 2015

FWIW, my team ran into related issues. We had a database incorrectly configured with a default charset of latin1 and so the versions table got set to latin1 and we started seeing exceptions in production with some Japanese characters. I'm not sure what the preferred solution should be from the paper_trail standpoint, but at the least, it might be worth bringing up the issue of charsets in the README and perhaps provide some options for passing through your desired charset.

@jaredbeck
Copy link
Member

Default database character sets

Solution

Fix: ... Force CHARSET=utf8 on your versions migration

Makes sense to me, but Ben makes a good point:

[will] the option string you are applying here will only be executed against databases with that setup ..?

Unfortunately, no. We can't simply copy the given solution into our migration template (paper_trail/lib/generators/paper_trail/templates/create_versions.rb) because the CHARSET option don't apply to, e.g. postgres.

create table derp () DEFAULT CHARSET=utf8;
-- ERROR:  syntax error at or near "DEFAULT"

So, we'll have to have some conditional logic. Maybe something like:

def options
  if ActiveRecord::Base.connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
    'ROW_FORMAT=DYNAMIC ENGINE=InnoDB DEFAULT CHARSET=utf8'
  else
    ''
  end
end

I've never had to write something like the above, so I have no idea if it's the best implementation.

@jaredbeck
Copy link
Member

Fixed in #681. Thanks @gczh, Ben @batter, and Ed @ecopony. Gotta love encodings, huh 😬

@iamcarrico
Copy link

I am still seeing this bug (using 5.0.1), wasn't using utf8mb4 part of the problem in the first place?

@jaredbeck
Copy link
Member

I am still seeing this bug (using 5.0.1),

Hi Ian. You're seeing the "Specified key was too long; max key length is 767 bytes" in 5.0.1? When do you get the error, when you're running the migration that creates the versions table?

wasn't using utf8mb4 part of the problem in the first place?

Possibly? But we must use utf8mb4 because Ruby strings are UTF-8 by default, and utf8mb4 is the only compatible CHARSET in MySQL, right?

@jaredbeck jaredbeck reopened this May 20, 2016
@jaredbeck
Copy link
Member

According to this SO answer:

Mysql assumes worst case for the number of bytes per character in the string. For .. 'utf8' .. that's 3 bytes per character .. For .. 'utf8mb4' .. it's 4 bytes per character ..

It goes on to say that the maximum length of a utf8mb4 varchar key is 191 chars.

as 191 * 4 = 764 < 767

So, assuming we can reproduce this issue, one solution may be to limit item_type to 191 chars.

@iamcarrico
Copy link

Yes— specifically the whole output

-- create_table(:versions, {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"})
   -> 0.0641s
-- add_index(:versions, [:item_type, :item_id])
rake aborted!
StandardError: An error has occurred, all later migrations canceled:

Mysql2::Error: Specified key was too long; max key length is 767 bytes: CREATE  INDEX `index_versions_on_item_type_and_item_id`  ON `versions` (`item_type`, `item_id`)

As for the column type— utf8 might be enough— utf8mb4 is a superset of UTF-8, which allows for new characters like emojis. Unless you are expecting those in here (I haven't dug deep into this codebase, just working on a small design)

@jaredbeck
Copy link
Member

Ian, it all works for me in a new app with rails 5.0.0.rc1, mysql 5.7.12, ruby 2.3.1. What are you using?

@jaredbeck
Copy link
Member

Ian, I haven't heard back from you, so I'm going to close this issue again. If anyone knows how to reproduce Ian's issue, please let us know, thanks!

@zuf
Copy link

zuf commented May 26, 2016

I am still have this bug with mariadb 10.1
mysql Ver 15.1 Distrib 10.1.14-MariaDB, for Linux (x86_64) using readline 5.1

@iamcarrico
Copy link

Oh hey! Sorry, missed the notification.

Rails 4.2.1
MySQL 5.6.23 (InnoDB / utf8 for all other tables)
Ruby 2.2.2

@jaredbeck
Copy link
Member

jaredbeck commented May 26, 2016

Well, it wasn't easy, but I was able to reproduce this on a VM with ubuntu 16.04, rails 4.2.6, mysql 5.6.16, and ruby 2.3.0. Re-opening.

.. one solution may be to limit item_type to 191 chars.

I'm going to try this.

@jaredbeck jaredbeck reopened this May 26, 2016
@jaredbeck
Copy link
Member

.. one solution may be to limit item_type to 191 chars.

I'm going to try this.

Seems to work.

@zuf
Copy link

zuf commented May 26, 2016

Fix worked for me. Thanks!

@iamcarrico
Copy link

Again, why are we forcing utf8mb4 when utf8 should also correct this? Unless item_type or item_id need emoji support— I cannot think of a reason to use utf8mb4 here.

@jaredbeck
Copy link
Member

why are we forcing utf8mb4 when utf8 should also correct this? Unless item_type or item_id need emoji support— I cannot think of a reason to use utf8mb4 here.

Yes, we want PT to have support for emoji (and other 4-byte chars). utf8mb4 is the only encoding in MySQL that matches ruby's default encoding. If your app needs a different encoding, you can edit your migration.

@BrendonW
Copy link

We've run into this and unless you change tables that store user input to utf8mb4, you get exceptions and/or data loss. Phones have a lot of 4byte characters easily accessible on the default keyboards, so users add smiley faces, etc. Since they are a valid part of UTF-8, they are not easy to remove before sticking data into the database.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants