concurrency model for mysql data processing@rubyconf.tw 2012
Post on 11-May-2015
1.087 Views
Preview:
DESCRIPTION
TRANSCRIPT
Concurrency Model for huge MySQL data
Mu-Fan Teng(@ryudoawaru)@ RubyConf Taiwan 2012
12年12月8⽇日星期六
緣起
Background
12年12月8⽇日星期六
Legacy environment
• A Mysql Database with 2.3gb data with Big5 charset and ISO-8859-1 encoding.
• The biggest table in DB is 1.5gb.
12年12月8⽇日星期六
The purpose
12年12月8⽇日星期六
Transcoding to UTF8
12年12月8⽇日星期六
Try
12年12月8⽇日星期六
Work Flow1. mysqldump with -default-character-
set=latin1 parameter to generate SQL file.
2. Transcoding SQL file with tool like iconv/bsdconv.
3. Edit transcoded SQL file to avoid 「slash」 problem.
4. Restore SQL file to new DB.
12年12月8⽇日星期六
Failed!
12年12月8⽇日星期六
Cause
• Too big size for most text editor.
• Many mis-encoding text.
12年12月8⽇日星期六
Let’s reinvent the wheel!
12年12月8⽇日星期六
The new work flow
• Connect DB
• Transcode
• Output db rows to SQL insert statement.
• Write SQL file
12年12月8⽇日星期六
CORES_COUNT = 4LIMIT = ARGV[0].to_i || 10000sqls = CORES_COUNT.times.map do |x| sprintf("SELECT * FROM cdb_posts ORDER BY pid LIMIT %d OFFSET %d;", LIMIT, (x * LIMIT))end
class String def to_my_val "'#{Mysql2::Client.escape self.force_encoding(‘Big5-UAO’).encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => '??')}'" endend
procs = sqls.map do |sql| Proc.new do |out| Mysql2::Client.new(database: DBNAME, reconnect: true, encoding: 'latin1').query(sql).each(as: :array) do |row| out.print "INSERT INTO `cdb_posts` VALUES (#{row.map(&:to_my_val).join(',')});\n" end endendprocs.each{|p| p.call(OUT)}
12年12月8⽇日星期六
Thanks for Ruby 1.9’s Awesome Encoding class which supports
Big5-UAO.
12年12月8⽇日星期六
Reduces almost 80% of encoding problem.
12年12月8⽇日星期六
But the file size is too big to wait for transcoding!
12年12月8⽇日星期六
So I have to find the concurrency model to
make it faster.
12年12月8⽇日星期六
Experiment Target
• Test the difference of performance between thread and fork model.
12年12月8⽇日星期六
H&W Platform
• 4 Cores Core2Quad CPU@2.5G
• 8GB RAM
• 1*SSD
• MacOS 10.8
• MRI 1.9.3p194
12年12月8⽇日星期六
DBNAME = 'wwwfsc'CORES_COUNT = 4ForceEncoding = 'Big5-UAO'LIMIT = ARGV[0].to_i || 10000OUT = '/dev/null'
sqls = CORES_COUNT.times.map do |x| sprintf("SELECT * FROM cdb_posts ORDER BY pid LIMIT %d OFFSET %d;", LIMIT, (x * LIMIT))end
class String def to_my_val "'#{Mysql2::Client.escape self.force_encoding(ForceEncoding).encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => '??')}'" endend
procs = sqls.map do |sql| Proc.new do |out| open(out,'w') do |io| Mysql2::Client.new(database: DBNAME, reconnect: true, encoding: 'latin1').query(sql).each(as: :array) do |row| io.print "INSERT INTO `cdb_posts` VALUES (#{row.map(&:to_my_val).join(',')});\n" end end endend
12年12月8⽇日星期六
Benchmark.bm(15) do |x| x.report("Thread"){procs.map{|p| Thread.new{p.call(OUT)} }.each(&:join)} x.report("Fork"){procs.each{|p| fork{p.call(OUT)} }; Process.waitall} x.report("Normal"){procs.each{|p| p.call(OUT)}}end
12年12月8⽇日星期六
Result of 100k*4 rows
12年12月8⽇日星期六
Thread
12年12月8⽇日星期六
Fork
12年12月8⽇日星期六
Fork
12年12月8⽇日星期六
Circumstance
• Thread
‣ CPU utilization rate between 105 and 125 percent.
• Fork
‣ The rate changes frequently between processes.
12年12月8⽇日星期六
GVL still effects
12年12月8⽇日星期六
Try again
12年12月8⽇日星期六
Decompose the steps to find how to skip
GVL.
12年12月8⽇日星期六
Experiment No.2
12年12月8⽇日星期六
Minify the process to query DB only.
12年12月8⽇日星期六
DBNAME = 'wwwfsc'CORES_COUNT = 4limit = ARGV[0].to_i || 10000
sqls = CORES_COUNT.times.map do |x| sprintf("SELECT * FROM cdb_posts ORDER BY pid LIMIT %d OFFSET %d;", limit, (x * limit))endprocs = CORES_COUNT.times.map do |x| Proc.new do client = Mysql2::Client.new(database: DBNAME, reconnect: true) result = client.query(sqls[x]) endend
Benchmark.bmbm(15) do |x| x.report("Thread"){procs.map{|p| Thread.new{p.call} }.each(&:join)} x.report("Fork"){procs.each{|p| fork{p.call} }; Process.waitall} x.report("Normal"){procs.each(&:call)}end
12年12月8⽇日星期六
Result of 100k*4 rows
12年12月8⽇日星期六
It seems the Mysql2 Gem can skip GVL.
12年12月8⽇日星期六
Experiment NO.3
12年12月8⽇日星期六
Limit the experiment to I/O operation only.
12年12月8⽇日星期六
client = Mysql2::Client.new(database: DBNAME, reconnect: true, encoding: 'latin1')sql_raws = sqls.map do |sql| arr = [] client.query(sql).each(as: :array) do |row| arr << "(#{row.map(&:to_my_val).join(',')})" end arrend
procs = sql_raws.map do |arr| proc do io = open('/dev/null','w') io.write "INSERT INTO `cdb_posts` VALUES " io.write arr.join(',') io.write "\n" io.close endend
Benchmark.bm(15) do |x| x.report("Thread"){procs.map{|p| Thread.new{p.call} }.each(&:join)} x.report("Fork"){procs.each{|p| fork{p.call} }; Process.waitall} x.report("Normal"){procs.each{|p| p.call}}end
12年12月8⽇日星期六
12年12月8⽇日星期六
Result of 100k*4 rows
12年12月8⽇日星期六
Change I/O to different files.
12年12月8⽇日星期六
procs = sql_raws.map do |arr| proc do io = Tempfile.new(SecureRandom.uuid)#open('/dev/null','w') puts io.path io.write "INSERT INTO `cdb_posts` VALUES " io.write arr.join(',') io.write "\n" io.close endend
12年12月8⽇日星期六
Result reversed
12年12月8⽇日星期六
Implement the same change to the first
experiment.
12年12月8⽇日星期六
procs = sqls.map do |sql| Proc.new do io = Tempfile.new(SecureRandom.uuid) Mysql2::Client.new(database: DBNAME, reconnect: true, encoding: 'latin1').query(sql).each(as: :array) do |row| io.write "INSERT INTO `cdb_posts` VALUES (#{row.map(&:to_my_val).join(',')});\n" end io.close endend
12年12月8⽇日星期六
Dose not effect any
12年12月8⽇日星期六
ConclusionThread fork normal
MySQL2-read Fast Fast x
Transcoding & iteration Slow Very fast x
Write to the same I/O Very slow Slow Fast
Write to the different I/O Fast Slow Fast
12年12月8⽇日星期六
There is no effective and 「all-around」 concurrency model.
12年12月8⽇日星期六
The small I/O can’t release GVL.
12年12月8⽇日星期六
Kosaki-san’s slide
12年12月8⽇日星期六
Matz is not a threading guy
12年12月8⽇日星期六
End
12年12月8⽇日星期六
top related