Skip to content

Commit

Permalink
Test against MySQL 8.0 (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
coding-chimp authored Sep 20, 2023
1 parent f6070c1 commit cb3a882
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 27 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ jobs:
include:
- ruby-version: "3.0"
activerecord-version: "6.0"
mysql_version: "5.7"
- ruby-version: "3.1"
activerecord-version: "6.1"
mysql_version: "5.7"
- ruby-version: "3.2"
activerecord-version: "7.0"
mysql_version: "8.0"
- ruby-version: "3.2"
activerecord-version: "7.1.0.beta1"
mysql_version: "8.0"
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
Expand All @@ -35,7 +39,7 @@ jobs:
- name: Install Ubuntu packages
run: sudo apt-get update && sudo apt-get install numactl libaio-dev libmysqlclient-dev
- name: Setup MySQL and ProxySQL (docker-compose)
run: docker-compose up -d # Might have to change to docker compose up -d (i.e. Compose V2) when the Ubuntu image changes the docker-compose version
run: docker-compose -f docker-compose-mysql-${{ matrix.mysql_version }}.yml up -d # Might have to change to docker compose up -d (i.e. Compose V2) when the Ubuntu image changes the docker-compose version
- name: Wait until DBs are alive
run: ./scripts/helpers/wait-for-dbs.sh
timeout-minutes: 2
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Unreleased
* Test against MySQL 8.0.

# 4.0.0 (Sep, 2023)
* Deprecate `SlaveLag` throttler class name. Use `ReplicaLag` instead (https://github.com/Shopify/lhm/pull/144)
Expand Down
3 changes: 3 additions & 0 deletions dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ commands:
run: podman-compose logs -f
clear:
run: podman-compose down -v && podman-compose up -d && ./scripts/helpers/wait-for-dbs.sh
subcommands:
mysql-5.7: podman-compose down -v && podman-compose -f docker-compose-mysql-5.7.yml up -d && ./scripts/helpers/wait-for-dbs.sh
mysql-8.0: podman-compose down -v && podman-compose -f docker-compose-mysql-8.0.yml up -d && ./scripts/helpers/wait-for-dbs.sh
pre-publish:
# Ensures all Gemfile.lock are sync with the new version in `lhm/version.rb` and runs appraisals
run: bundle install && bundle exec appraisal install && bundle exec appraisal rake specs
1 change: 1 addition & 0 deletions docker-compose-mysql-5.7.yml
63 changes: 63 additions & 0 deletions docker-compose-mysql-8.0.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
services:
# Writer
mysql-1:
container_name: mysql-1
image: percona:8.0
platform: linux/amd64
command:
--server-id=1
--log-bin
--log-slave-updates=ON
--gtid-mode=ON
--enforce-gtid-consistency=ON
--read-only=OFF
--max-connections=1000
--default-authentication-plugin=mysql_native_password
hostname: 'mysql-1'
volumes:
- ./scripts/mysql/writer:/docker-entrypoint-initdb.d
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_HOST: mysql-1
ports:
- "33006:3306"
# Reader
mysql-2:
container_name: mysql-2
image: percona:8.0
platform: linux/amd64
command:
--server-id=2
--log-bin
--log-slave-updates=ON
--gtid-mode=ON
--enforce-gtid-consistency=ON
--read-only=ON
--max-connections=1000
--default-authentication-plugin=mysql_native_password
hostname: 'mysql-2'
volumes:
- ./scripts/mysql/reader:/docker-entrypoint-initdb.d
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_HOST: mysql-2
ports:
- "33007:3306"
# Proxysql
proxysql:
container_name: proxysql
image: proxysql/proxysql:2.0.11
platform: linux/amd64
volumes:
- ./scripts/proxysql/proxysql.cnf:/etc/proxysql.cnf
command: "proxysql -c /etc/proxysql.cnf -f --idle-threads"
ports:
- "33005:3306"
- "6032:6032"
toxiproxy:
container_name: toxiproxy
image: "ghcr.io/shopify/toxiproxy"
ports:
- "8474:8474"
- "22220:22220"
- "22222:22222"
2 changes: 1 addition & 1 deletion scripts/mysql/writer/create_users.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ CREATE USER IF NOT EXISTS 'writer'@'%' IDENTIFIED BY 'password';
CREATE USER IF NOT EXISTS 'reader'@'%' IDENTIFIED BY 'password';

CREATE USER IF NOT EXISTS 'replication'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO' replication'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO' replication'@'%';
25 changes: 20 additions & 5 deletions spec/integration/chunker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ def log_messages
Lhm::Chunker.new(migration, connection, {raise_on_warnings: true, throttler: throttler, printer: printer} ).run
end

assert_match "Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'", exception.message
error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")
assert_match "Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'", exception.message
end

it 'should copy and warn on unexpected warnings by default' do
Expand All @@ -87,8 +88,10 @@ def log_messages

Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run

error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")

assert_equal 2, log_messages.length
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'"), log_messages
end

it 'should log two times for two unexpected warnings' do
Expand All @@ -103,9 +106,11 @@ def log_messages

Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run

error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")

assert_equal 3, log_messages.length
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
assert log_messages[2].include?("Unexpected warning found for inserted row: Duplicate entry '1002' for key 'index_custom_primary_key_on_id'"), log_messages
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'"), log_messages
assert log_messages[2].include?("Unexpected warning found for inserted row: Duplicate entry '1002' for key '#{error_key}'"), log_messages
end

it 'should copy and warn on unexpected warnings' do
Expand All @@ -118,8 +123,10 @@ def log_messages

Lhm::Chunker.new(migration, connection, {raise_on_warnings: false, throttler: throttler, printer: printer} ).run

error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")

assert_equal 2, log_messages.length
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'"), log_messages
end

it 'should create the modified destination, even if the source is empty' do
Expand Down Expand Up @@ -261,4 +268,12 @@ def throttler.replica_connection(replica)
end
end
end

def index_key(table_name, index_name)
if mysql_version.start_with?("8")
"#{table_name}.#{index_name}"
else
index_name
end
end
end
8 changes: 8 additions & 0 deletions spec/integration/integration_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ def connection
@connection
end

def mysql_version
@mysql_version ||= begin
# This SQL returns a value of shape: X.Y.ZZ-AA-log
result = connection.query("SELECT VERSION()")
result.dig(0, 0).split("-", 2)[0]
end
end

def connect_proxysql!
connect!(
'127.0.0.1',
Expand Down
42 changes: 29 additions & 13 deletions spec/integration/lhm_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

before(:each) { connect_master!; Lhm.cleanup(true) }

let(:collation) do
mysql_version.start_with?("8.0") ? "utf8mb3_general_ci" : "utf8_general_ci"
end

describe 'id column requirement' do
it 'should migrate the table when id is pk' do
table_create(:users)
Expand All @@ -17,9 +21,11 @@
t.add_column(:logins, "int(12) default '0'")
end

expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"

replica do
value(table_read(:users).columns['logins']).must_equal({
:type => 'int(12)',
:type => expected_type,
:is_nullable => 'YES',
:column_default => '0',
:comment => '',
Expand All @@ -35,9 +41,11 @@
t.add_column(:logins, "int(12) default '0'")
end

expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"

replica do
value(table_read(:custom_primary_key).columns['logins']).must_equal({
:type => 'int(12)',
:type => expected_type,
:is_nullable => 'YES',
:column_default => '0',
:comment => '',
Expand All @@ -53,9 +61,11 @@
t.add_column(:logins, "int(12) default '0'")
end

expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"

replica do
value(table_read(:composite_primary_key).columns['logins']).must_equal({
:type => 'int(12)',
:type => expected_type,
:is_nullable => 'YES',
:column_default => '0',
:comment => '',
Expand Down Expand Up @@ -132,9 +142,11 @@
t.add_column(:logins, "INT(12) DEFAULT '0'")
end

expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"

replica do
value(table_read(:users).columns['logins']).must_equal({
:type => 'int(12)',
:type => expected_type,
:is_nullable => 'YES',
:column_default => '0',
:comment => '',
Expand Down Expand Up @@ -272,7 +284,7 @@
:is_nullable => 'NO',
:column_default => 'none',
:comment => '',
:collate => 'utf8_general_ci',
:collate => collation,
})
end
end
Expand All @@ -284,9 +296,11 @@
t.change_column(:id, 'int(5)')
end

expected_type = mysql_version.start_with?("8.0") ? "int" : "int(5)"

replica do
value(table_read(:small_table).columns['id']).must_equal({
:type => 'int(5)',
:type => expected_type,
:is_nullable => 'NO',
:column_default => nil,
:comment => '',
Expand All @@ -311,7 +325,7 @@
:is_nullable => 'YES',
:column_default => nil,
:comment => '',
:collate => 'utf8_general_ci',
:collate => collation,
})

result = select_one('SELECT login from users')
Expand All @@ -336,7 +350,7 @@
:is_nullable => 'YES',
:column_default => 'Superfriends',
:comment => '',
:collate => 'utf8_general_ci',
:collate => collation,
})

result = select_one('SELECT `fnord` from users')
Expand Down Expand Up @@ -383,11 +397,13 @@
t.rename_column(:reference, :ref)
end

expected_type = mysql_version.start_with?("8.0") ? "int" : "int(11)"

replica do
table_data = table_read(:users)
assert_nil table_data.columns['reference']
value(table_read(:users).columns['ref']).must_equal({
:type => 'int(11)',
:type => expected_type,
:is_nullable => 'YES',
:column_default => nil,
:comment => 'RefComment',
Expand Down Expand Up @@ -418,7 +434,7 @@
:is_nullable => 'YES',
:column_default => nil,
:comment => '',
:collate => 'utf8_general_ci',
:collate => collation,
})

result = select_one('SELECT `fnord` from users')
Expand All @@ -443,7 +459,7 @@
:is_nullable => 'YES',
:column_default => nil,
:comment => '',
:collate => 'utf8_general_ci',
:collate => collation,
})

result = select_one('SELECT `user_name` from users')
Expand All @@ -470,7 +486,7 @@
:is_nullable => 'NO',
:column_default => nil,
:comment => '',
:collate => 'utf8_general_ci',
:collate => collation,
})

result = select_one('SELECT `user_name` from users')
Expand Down Expand Up @@ -517,7 +533,7 @@
:is_nullable => 'YES',
:column_default => 'Superfriends',
:comment => '',
:collate => 'utf8_general_ci',
:collate => collation,
})
end
end
Expand Down
20 changes: 14 additions & 6 deletions spec/integration/sql_retry/lock_wait_timeout_test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@ def initialize(lock_duration:, innodb_lock_wait_timeout:)

@lock_duration = lock_duration

# While implementing this, I discovered that MySQL seems to have an off-by-one
# bug with the innodb_lock_wait_timeout. If you ask it to wait 2 seconds, it will wait 3.
# In order to avoid surprisingly the user, let's account for that here, but also
# guard against a case where we go below 1, the minimum value.
raise ArgumentError, "innodb_lock_wait_timeout must be greater than or equal to 2" unless innodb_lock_wait_timeout >= 2
raise ArgumentError, "innodb_lock_wait_timeout must be an integer" if innodb_lock_wait_timeout.class != Integer
@innodb_lock_wait_timeout = innodb_lock_wait_timeout - 1

result = @main_conn.query("SELECT VERSION()")
mysql_version = result.to_a.dig(0, "VERSION()").split("-", 2)[0]

if mysql_version.start_with?("8")
@innodb_lock_wait_timeout = innodb_lock_wait_timeout
else
# While implementing this, I discovered that MySQL seems to have an off-by-one
# bug with the innodb_lock_wait_timeout. If you ask it to wait 2 seconds, it will wait 3.
# In order to avoid surprisingly the user, let's account for that here, but also
# guard against a case where we go below 1, the minimum value.
raise ArgumentError, "innodb_lock_wait_timeout must be greater than or equal to 2" unless innodb_lock_wait_timeout >= 2
@innodb_lock_wait_timeout = innodb_lock_wait_timeout - 1
end

@threads = []
@queue = Queue.new
Expand Down
2 changes: 1 addition & 1 deletion spec/integration/table_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
end

it 'should parse columns' do
value(@table.columns['id'][:type]).must_match(/(bigint|int)\(\d+\)/)
value(@table.columns['id'][:type]).must_match(/(bigint|int)(\(\d+\))?/)
end

it 'should return true for method that should be renamed' do
Expand Down

0 comments on commit cb3a882

Please sign in to comment.