octopus MySQL read only
最近在线上遇到一个问题,使用 rails db:migrate
时,会去写只读库。
经过排查发现是 octopus 这个 gem 造成的。
查看相关文档,并没有什么收获,倒是 Issues 里有一堆反映问题的。
既然 octopus
改变了 rails db:migrate
的原始行为,那么在源码中通过 migrate
之类的关键字肯定能找到。
很快,就在 lib/octopus/migration.rb
中找到了。
# https://github.com/thiagopradi/octopus/blob/master/lib/octopus/migration.rb#L70
alias_method :migrate_without_octopus, :migrate
alias_method :migrate, :migrate_with_octopus
可以看到它使用 alias_method
这种别名,替换了 migrate
函数。
这里有一种解决方案是使用 alias_method
,反向替换回去,但是并不一定好,毕竟我们还要继续使用 octopus
。
先看看 migrate_with_octopus
到底做了什么?
# https://github.com/thiagopradi/octopus/blob/master/lib/octopus/migration.rb#L157
def migrate_with_octopus(migrations_paths, target_version = nil, &block)
return migrate_without_octopus(migrations_paths, target_version, &block) unless connection.is_a?(Octopus::Proxy)
connection.send_queries_to_multiple_shards(connection.shard_names) do
migrate_without_octopus(migrations_paths, target_version, &block)
end
end
在 migrate_with_octopus
中,循环执行 migrate_without_octopus
,而它就是 migrate
的别名,即migrate_with_octopus
中,在循环执行 migrate
。
那么我们把 connection.shard_names
打出来看看,你想的没错,它是一个数组
p connection.shard_names
# ["slave", "master"]
找到了关键点,那么解决方案就很简单了,在 rails
启动时去修改它,比如写一个 monkey patch
# config/initializers/octopus.rb
return unless Octopus.enabled?
module Octopus
class ProxyConfig
# cover shard_names method
def shard_names
# support db those commands
if ARGV[0].match(/^db:/).present?
["master"]
else
# default
shards.keys
end
end
end
end
为什么本地开发环境不会遇到这个问题?
使用 show global variables like "%read_only%";
查看
mysql> show global variables like '%read_only%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_read_only | OFF |
| read_only | ON |
| super_read_only | OFF |
+------------------+-------+
read_only
的确是 ON
啊,为什么不生效?
一同显示的还有 innodb_read_only
和 super_read_only
它们是什么?
查看官方文档
When the read_only system variable is enabled, the server permits no client updates except from users who have the CONNECTION_ADMIN or SUPER privilege. This variable is disabled by default.
The server also supports a super_read_only system variable (disabled by default), which has these effects:
If super_read_only is enabled, the server prohibits client updates, even from users who have the SUPER privilege.
Setting super_read_only to ON implicitly forces read_only to ON.
Setting read_only to OFF implicitly forces super_read_only to OFF.
对,我们找到了原因,本地开发环境一般使用 root
账号,它拥有 SUPER privilege
,read_only
自然管不住它。
在本地开发环境复现问题很简单,使用一个没有 SUPER privilege
权限的账号,或者使用 set global super_read_only=ON;
将 SUPER
也限制为只读。