Concurrency model for mysql data processing@rubyconf.tw 2012

  • 1,031 views
Uploaded on

Video: http://www.youtube.com/watch?v=3Oi_IjKDZtg

Video: http://www.youtube.com/watch?v=3Oi_IjKDZtg

More in: News & Politics
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
1,031
On Slideshare
0
From Embeds
0
Number of Embeds
2

Actions

Shares
Downloads
28
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Concurrency Model for huge MySQL data Mu-Fan Teng(@ryudoawaru) @ RubyConf Taiwan 201212年12月8⽇日星期六
  • 2. 緣起 Background12年12月8⽇日星期六
  • 3. 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⽇日星期六
  • 4. The purpose12年12月8⽇日星期六
  • 5. Transcoding to UTF812年12月8⽇日星期六
  • 6. Try12年12月8⽇日星期六
  • 7. Work Flow 1. 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⽇日星期六
  • 8. Failed!12年12月8⽇日星期六
  • 9. Cause • Too big size for most text editor. • Many mis-encoding text.12年12月8⽇日星期六
  • 10. Let’s reinvent the wheel!12年12月8⽇日星期六
  • 11. The new work flow • Connect DB • Transcode • Output db rows to SQL insert statement. • Write SQL file12年12月8⽇日星期六
  • 12. CORES_COUNT = 4 LIMIT = 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)) end class String def to_my_val "#{Mysql2::Client.escape self.force_encoding(‘Big5- UAO’).encode(UTF-8, :invalid => :replace, :undef => :replace, :replace => ??)}" end end 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 end end procs.each{|p| p.call(OUT)}12年12月8⽇日星期六
  • 13. Thanks for Ruby 1.9’s Awesome Encoding class which supports Big5-UAO.12年12月8⽇日星期六
  • 14. Reduces almost 80% of encoding problem.12年12月8⽇日星期六
  • 15. But the file size is too big to wait for transcoding!12年12月8⽇日星期六
  • 16. So I have to find the concurrency model to make it faster.12年12月8⽇日星期六
  • 17. Experiment Target • Test the difference of performance between thread and fork model.12年12月8⽇日星期六
  • 18. H&W Platform • 4 Cores Core2Quad CPU@2.5G • 8GB RAM • 1*SSD • MacOS 10.8 • MRI 1.9.3p19412年12月8⽇日星期六
  • 19. DBNAME = wwwfsc CORES_COUNT = 4 ForceEncoding = Big5-UAO LIMIT = ARGV[0].to_i || 10000 OUT = /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 => ??)}" end end 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 end end12年12月8⽇日星期六
  • 20. 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)}} end12年12月8⽇日星期六
  • 21. Result of 100k*4 rows12年12月8⽇日星期六
  • 22. Thread12年12月8⽇日星期六
  • 23. Fork12年12月8⽇日星期六
  • 24. Fork12年12月8⽇日星期六
  • 25. Circumstance • Thread ‣ CPU utilization rate between 105 and 125 percent. • Fork ‣ The rate changes frequently between processes.12年12月8⽇日星期六
  • 26. GVL still effects12年12月8⽇日星期六
  • 27. Try again12年12月8⽇日星期六
  • 28. Decompose the steps to find how to skip GVL.12年12月8⽇日星期六
  • 29. Experiment No.212年12月8⽇日星期六
  • 30. Minify the process to query DB only.12年12月8⽇日星期六
  • 31. DBNAME = wwwfsc CORES_COUNT = 4 limit = 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)) end procs = CORES_COUNT.times.map do |x| Proc.new do client = Mysql2::Client.new(database: DBNAME, reconnect: true) result = client.query(sqls[x]) end end 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)} end12年12月8⽇日星期六
  • 32. Result of 100k*4 rows12年12月8⽇日星期六
  • 33. It seems the Mysql2 Gem can skip GVL.12年12月8⽇日星期六
  • 34. Experiment NO.312年12月8⽇日星期六
  • 35. Limit the experiment to I/O operation only.12年12月8⽇日星期六
  • 36. 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 arr end 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 end end 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}} end12年12月8⽇日星期六
  • 37. 12年12月8⽇日星期六
  • 38. Result of 100k*4 rows12年12月8⽇日星期六
  • 39. Change I/O to different files.12年12月8⽇日星期六
  • 40. 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 end end12年12月8⽇日星期六
  • 41. Result reversed12年12月8⽇日星期六
  • 42. Implement the same change to the first experiment.12年12月8⽇日星期六
  • 43. 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 end end12年12月8⽇日星期六
  • 44. Dose not effect any12年12月8⽇日星期六
  • 45. Conclusion Thread fork normal MySQL2-read Fast Fast x Transcoding & Slow Very fast x iteration Write to the Very slow Slow Fast same I/O Write to the Fast Slow Fast different I/O12年12月8⽇日星期六
  • 46. There is no effective and 「all-around」 concurrency model.12年12月8⽇日星期六
  • 47. The small I/O can’t release GVL.12年12月8⽇日星期六
  • 48. Kosaki-san’s slide12年12月8⽇日星期六
  • 49. Matz is not a threading guy12年12月8⽇日星期六
  • 50. End12年12月8⽇日星期六