Gh-OST 的设计和实现

  • A+
所属分类:技术教程

GitHub’s online schema migration tool for MySQL

gh-ost has been developed at GitHub in recent months to answer a problem we faced with ongoing, continuous production changes requiring modifications to MySQL tables. gh-ost changes the existing online table migration paradigm by providing a low impact, controllable, auditable, operations friendly solution.

gh-ost

gh-ost : stands for GitHub’s Online Schema

– Triggerless

– Lightweight

– Pauseable

– Dynamically controllable

– Auditable

– Testable

– Trustable

– Triggerless

Gh-OST 的设计和实现

Transmogrifier/Transfigurator/Transformer/Thingy

使用方式

  1. # 执行命令
  2. ./gh-ost --help
  3. #输出
  4. -allow-master-master
  5.     explicitly allow running in a master-master setup
  6. -allow-nullable-unique-key
  7.         allow gh-ost to migrate based on a unique key with nullable columns. As long as no NULL values exist, this should be OK. If NULL values exist in chosen key, data may be corrupted. Use at your own risk!
  8. -allow-on-master
  9.         allow this migration to run directly on master. Preferably it would run on a replica
  10. -alter string
  11.         alter statement (mandatory)
  12. -approve-renamed-columns ALTER
  13.         in case your ALTER statement renames columns, gh-ost will note that and offer its interpretation of the rename. By default gh-ost does not proceed to execute. This flag approves that gh-ost's interpretation si correct
  14. -chunk-size int
  15.         amount of rows to handle in each iteration (allowed range: 100-100,000) (default 1000)
  16. -conf string
  17.         Config file
  18. -critical-load --max-load
  19.         Comma delimited status-name=threshold, same format as --max-load. When status exceeds threshold, app panics and quits
  20. -cut-over string
  21.         choose cut-over type (default|atomic, two-step) (default "atomic")
  22. -cut-over-lock-timeout-seconds int
  23.         Max number of seconds to hold locks on tables while attempting to cut-over (retry attempted when lock exceeds timeout) (default 3)
  24. -database string
  25.         database name (mandatory)
  26. -debug
  27.         debug mode (very verbose)
  28. -default-retries int
  29.         Default number of retries for various operations before panicking (default 60)
  30. -exact-rowcount
  31.         actually count table rows as opposed to estimate them (results in more accurate progress estimation)
  32. -execute
  33.         actually execute the alter & migrate the table. Default is noop: do some tests and exit
  34. -help
  35.         Display usage
  36. -host string
  37.         MySQL hostname (preferably a replica, not the master) (default "127.0.0.1")
  38. -initially-drop-ghost-table
  39.         Drop a possibly existing Ghost table (remains from a previous run?) before beginning operation. Default is to panic and abort if such table exists
  40. -initially-drop-old-table
  41.         Drop a possibly existing OLD table (remains from a previous run?) before beginning operation. Default is to panic and abort if such table exists
  42. -initially-drop-socket-file
  43.         Should gh-ost forcibly delete an existing socket file. Be careful: this might drop the socket file of a running migration!
  44. -max-lag-millis int
  45.         replication lag at which to throttle operation (default 1500)
  46. -max-load string
  47.         Comma delimited status-name=threshold. e.g: 'Threads_running=100,Threads_connected=500'. When status exceeds threshold, app throttles writes
  48. -migrate-on-replica
  49.         Have the migration run on a replica, not on the master. This will do the full migration on the replica including cut-over (as opposed to --test-on-replica)
  50. -nice-ratio float
  51.         force being 'nice', imply sleep time per chunk time; range: [0.0..100.0]. Example values: 0 is aggressive. 1.5for every ms spend in a rowcopy chunk, spend 1.5ms sleeping immediately after
  52. -ok-to-drop-table
  53.         Shall the tool drop the old table at end of operation. DROPping tables can be a long locking operation, which is why I'm not doing it by default. I'm an online tool, yes?
  54. -panic-flag-file string
  55.         when this file is created, gh-ost will immediately terminate, without cleanup
  56. -password string
  57.         MySQL password
  58. -port int
  59.         MySQL port (preferably a replica, not the master) (default 3306)
  60. -postpone-cut-over-flag-file string
  61.         while this file exists, migration will postpone the final stage of swapping tables, and will keep on syncing the ghost table. Cut-over/swapping would be ready to perform the moment the file is deleted.
  62. -quiet
  63.         quiet
  64. -replication-lag-query string
  65.         Query that detects replication lag in seconds. Result can be a floating point (by default gh-ost issues SHOW SLAVE STATUS and reads Seconds_behind_master). If you're using pt-heartbeat, query would be something like: SELECT ROUND(UNIX_TIMESTAMP() - MAX(UNIX_TIMESTAMP(ts))) AS delay FROM my_schema.heartbeat
  66. -serve-socket-file string
  67.         Unix socket file to serve on. Default: auto-determined and advertised upon startup
  68. -serve-tcp-port int
  69.         TCP port to serve on. Default: disabled
  70. -skip-renamed-columns ALTER
  71.         in case your ALTER statement renames columns, gh-ost will note that and offer its interpretation of the rename. By default gh-ost does not proceed to execute. This flag tells gh-ost to skip the renamed columns, i.e. to treat what gh-ost thinks are renamed columns as unrelated columns. NOTE: you may lose column data
  72. -stack
  73.         add stack trace upon error
  74. -switch-to-rbr
  75.         let this tool automatically switch binary log format to 'ROW' on the replica, if needed. The format will NOT be switched back. I'm too scared to do that, and wish to protect you if you happen to execute another migration while this one is running
  76. -table string
  77.         table name (mandatory)
  78. -test-on-replica
  79.         Have the migration run on a replica, not on the master. At the end of migration replication is stopped, and tables are swapped and immediately swap-revert. Replication remains stopped and you can compare the two tables for building trust
  80. -throttle-additional-flag-file string
  81.         operation pauses when this file exists; hint: keep default, use for throttling multiple gh-ost operations (default "/tmp/gh-ost.throttle")
  82. -throttle-control-replicas string
  83.         List of replicas on which to check for lag; comma delimited. Example: myhost1.com:3306,myhost2.com,myhost3.com:3307
  84. -throttle-flag-file string
  85.         operation pauses when this file exists; hint: use a file that is specific to the table being altered
  86. -throttle-query string
  87.         when given, issued (every second) to check if operation should throttle. Expecting to return zero for no-throttle, >0 for throttle. Query is issued on the migrated server. Make sure this query is lightweight
  88. -user string
  89.         MySQL user
  90. -verbose
  91.     verbose
  92. -version
  93.     Print version & exit

example

  1. --max-load=Threads_running=25 \
  2. --critical-load=Threads_running=1000 \
  3. --chunk-size=1000 \
  4. --throttle-control-replicas="replip" \
  5. --max-lag-millis=1500 \
  6. --user="xxxxx" \
  7. --password="xxxxxx" \
  8. --host="xxx.xxx..xxx.13" \
  9. --port=3306 \
  10. --database="test" \
  11. --table="big_data_check_hdfs" \
  12. --verbose \
  13. --alter="change file_path file_path varchar(128) " \
  14. --switch-to-rbr \
  15. --allow-master-master \
  16. --cut-over=default \
  17. --exact-rowcount \
  18. --default-retries=120 \
  19. --postpone-cut-over-flag-file=/data/dbbak/gh-ost/ghost.postpone.flag \
  20. --execute

原理:

gh-ost最核心的两个模块: 行数据拷贝 ; 日志解析和应用 . 两个操作是同时并发执行的, 只要有日志就会立即在影子表执行.

行数据拷贝(row copy)

Row Copy的逻辑如下:

  • 计算Chunk
  • 对每个 Chunk 数据进行copy, copy的过程中 , 对原表进行加锁, 保证copy过程中没有对正在copy的数据进行修改操作.(如果是给表添加unique key,重复的数据会被ignore, 正常现象)
  1. insert /* gh-ost %s.%s */ ignore into %s.%s (%s)
  2.  (select %s from %s.%s force index (%s)
  3. where (%s and %s) %s  lock in share mode
  • 将所有的 chunck都copy完成,整个copy过程完成

日志解析和应用

通过伪装为slave的方式从master获取binglog日志, 并解析拼装成为sql ,在目标库或者源库执行(这取决于在哪个库做操作, 大部分是在主库)

可行性分析

由于RowCopy的过程中是加锁的, 所以copy过程中是不存在对这部分数据的任何修改操作的, 即数据在copy过程中不可能有对应的binlog产生. 所以我们对不同类型的操作只需要分析 log before copy (b-log) 和 log after copy(a-log) , 我们假设copy过程为t0区间(可以看做一个时间点)

代码生成模板

  • INSTER
  1. result = fmt.Sprintf(`
  2. replace /* gh-ost %s.%s */ into
  3. %s.%s
  4. (%s)
  5. values
  6. (%s)
  7.  `, databaseName, tableName,
  8. databaseName, tableName,
  9. strings.Join(mappedSharedColumnNames, ", "),
  10. strings.Join(preparedValues, ", "),

日志提前到达, 直接insert进来, rowcopy的时候会被忽略. 日志延迟到

#如下逻辑
if b-log > t0: 
    日志延迟到达, 没有关系直接replace
else:  
    日志提前到达, 直接insert进来, rowcopy的时候会被忽略
  • UPDATE
  1. fmt.Sprintf(`
  2.         update /* gh-ost %s.%s */
  3.                 %s.%s
  4.             set
  5.                 %s
  6.             where
  7.                 %s
  8.     `, databaseName, tableName,
  9.     databaseName, tableName,
  10.     setClause,
  11.     equalsComparison
  • #如下逻辑
    if b-log > t0:

    直接update目标表数据
    

    else:

    空update, rowcopy的时候会把new 值带过来
    

    if a-log > t0:

    空update
    

    else:

    直接update, 相当于update了两次
    
  • DELETE
  1. result = fmt.Sprintf(`
  2.         delete /* gh-ost %s.%s */
  3.             from
  4.                 %s.%s
  5.             where
  6.                 %s
  7.     `, databaseName, tableName,
  8.     databaseName, tableName,
  9.     equalsComparison,
  10. )
  • #如下逻辑
    if b-log > t0:

    空删除
    

    else:

    空删除
    

总结

gh-ost以一个新的思路来完成 online ddl 等操作, 避免了传统使用trigger方式来操作的很多弊端. 不过目前gh-ost尚未流行,需要进行充分的测试才可以上生产环境. 总之 , gh-ost是一个好工具。

图片引用自网络