fcoury-db2s3 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +37 -0
- data/Rakefile +4 -0
- data/db2s3.gemspec +46 -0
- data/init.rb +1 -0
- data/lib/db2s3.rb +109 -0
- data/lib/db2s3/tasks.rb +1 -0
- data/rails/init.rb +1 -0
- data/spec/db2s3_spec.rb +48 -0
- data/spec/mysql_drop_schema.sql +1 -0
- data/spec/mysql_schema.sql +4 -0
- data/spec/s3_config.example.rb +7 -0
- data/spec/spec_helper.rb +14 -0
- data/tasks/tasks.rake +39 -0
- metadata +78 -0
data/README
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
DB2S3 - A rails plugin to backup Mysql to Amazon S3
|
2
|
+
---------------------------------------------------
|
3
|
+
You're looking at a monthly spend of four cents
|
4
|
+
So pony up you cheap bastard, and store your backups on S3
|
5
|
+
|
6
|
+
Usage:
|
7
|
+
# In config/environment.rb
|
8
|
+
config.gem "xaviershay-db2s3", :lib => "db2s3", :source => "http://gems.github.com"
|
9
|
+
|
10
|
+
# In Rakefile
|
11
|
+
require 'db2s3/tasks'
|
12
|
+
|
13
|
+
# In config/initializers/db2s3.rb
|
14
|
+
DB2S3::Config.instance_eval do
|
15
|
+
S3 = {
|
16
|
+
:access_key_id => 'yourkey',
|
17
|
+
:secret_access_key => 'yoursecretkey',
|
18
|
+
:bucket => 'yourapp-db-backup'
|
19
|
+
}
|
20
|
+
end
|
21
|
+
# DB credentials are read from your rails environment
|
22
|
+
|
23
|
+
rake gems:install
|
24
|
+
|
25
|
+
# Add to your crontab or whatever
|
26
|
+
rake db2s3:backup:full
|
27
|
+
rake db2s3:backup:incremental # Unimplemented
|
28
|
+
|
29
|
+
# Handy tasks
|
30
|
+
rake db2s3:metrics # Estimated costs
|
31
|
+
rake db2s3:backup:restore # You should be testing this regularly
|
32
|
+
|
33
|
+
Caveats:
|
34
|
+
Currently only stores the latest backup
|
35
|
+
|
36
|
+
Kudos:
|
37
|
+
http://github.com/pauldowman/blog_code_examples/tree/master/mysql_s3_backup
|
data/Rakefile
ADDED
data/db2s3.gemspec
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{db2s3}
|
5
|
+
s.version = "0.2.2"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Felipe Coury"]
|
9
|
+
s.date = %q{2009-03-08}
|
10
|
+
s.description = %q{db2s3 provides rake tasks for backing up and restoring your DB to S3}
|
11
|
+
s.email = %q{contact@felipecoury.com}
|
12
|
+
s.files = %w(
|
13
|
+
README
|
14
|
+
Rakefile
|
15
|
+
db2s3.gemspec
|
16
|
+
init.rb
|
17
|
+
lib
|
18
|
+
lib/db2s3.rb
|
19
|
+
lib/db2s3/tasks.rb
|
20
|
+
rails
|
21
|
+
rails/init.rb
|
22
|
+
spec
|
23
|
+
spec/db2s3_spec.rb
|
24
|
+
spec/mysql_drop_schema.sql
|
25
|
+
spec/mysql_schema.sql
|
26
|
+
spec/s3_config.example.rb
|
27
|
+
spec/spec_helper.rb
|
28
|
+
tasks
|
29
|
+
tasks/tasks.rake
|
30
|
+
)
|
31
|
+
s.has_rdoc = false
|
32
|
+
s.homepage = %q{http://github.com/fcoury/db2s3}
|
33
|
+
#s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
#s.rubyforge_project = %q{grit}
|
36
|
+
s.rubygems_version = %q{1.3.1}
|
37
|
+
s.summary = %q{db2s3 provides rake tasks for backing up and restoring your DB to S3}
|
38
|
+
|
39
|
+
# TODO: WTF does this do
|
40
|
+
if s.respond_to? :specification_version then
|
41
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
42
|
+
s.specification_version = 2
|
43
|
+
end
|
44
|
+
|
45
|
+
s.add_dependency(%q<aws-s3>, [">= 0.5.1"])
|
46
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/rails/init"
|
data/lib/db2s3.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'aws/s3'
|
2
|
+
|
3
|
+
class DB2S3
|
4
|
+
class Config
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
end
|
9
|
+
|
10
|
+
def full_backup
|
11
|
+
store.store("dump-#{db_credentials[:database]}.sql.gz", open(dump_db.path))
|
12
|
+
end
|
13
|
+
|
14
|
+
def restore
|
15
|
+
file = store.fetch("dump-#{db_credentials[:database]}.sql.gz")
|
16
|
+
run "gunzip -c #{file.path} | mysql #{mysql_options}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def metrics
|
20
|
+
dump_file = dump_db
|
21
|
+
|
22
|
+
storage_dollars_per_byte_per_month = 0.15 / 1024.0 / 1024.0 / 1024.0
|
23
|
+
transfer_dollars_per_byte_per_month = 0.10 / 1024.0 / 1024.0 / 1024.0
|
24
|
+
full_dumps_per_month = 30
|
25
|
+
|
26
|
+
storage_cost = (dump_file.size * storage_dollars_per_byte_per_month * 100).ceil / 100.0
|
27
|
+
transfer_cost = (dump_file.size * full_dumps_per_month * transfer_dollars_per_byte_per_month * 100).ceil / 100.0
|
28
|
+
requests_cost = 0.02 # TODO: Actually calculate this, with incremental backups could be more
|
29
|
+
|
30
|
+
{
|
31
|
+
:db_size => dump_file.size,
|
32
|
+
:storage_cost => storage_cost,
|
33
|
+
:transfer_cost => transfer_cost,
|
34
|
+
:total_cost => storage_cost + transfer_cost + requests_cost,
|
35
|
+
:requests_cost => requests_cost,
|
36
|
+
:full_backups_per_month => full_dumps_per_month
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def dump_db
|
43
|
+
dump_file = Tempfile.new("dump")
|
44
|
+
|
45
|
+
#cmd = "mysqldump --quick --single-transaction --create-options -u#{db_credentials[:user]} --flush-logs --master-data=2 --delete-master-logs"
|
46
|
+
cmd = "mysqldump --quick --single-transaction --create-options #{mysql_options}"
|
47
|
+
cmd += " | gzip > #{dump_file.path}"
|
48
|
+
run(cmd)
|
49
|
+
|
50
|
+
dump_file
|
51
|
+
end
|
52
|
+
|
53
|
+
def mysql_options
|
54
|
+
cmd = " -u #{db_credentials[:username]} "
|
55
|
+
cmd += " -p'#{db_credentials[:password]}'" unless db_credentials[:password].nil?
|
56
|
+
cmd += " #{db_credentials[:database]}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def store
|
60
|
+
@store ||= S3Store.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def run(command)
|
64
|
+
result = system(command)
|
65
|
+
raise("error, process exited with status #{$?.exitstatus}") unless result
|
66
|
+
end
|
67
|
+
|
68
|
+
def db_credentials
|
69
|
+
ActiveRecord::Base.connection.instance_eval { @config } # Dodgy!
|
70
|
+
end
|
71
|
+
|
72
|
+
class S3Store
|
73
|
+
def initialize
|
74
|
+
@connected = false
|
75
|
+
end
|
76
|
+
|
77
|
+
def ensure_connected
|
78
|
+
return if @connected
|
79
|
+
AWS::S3::Base.establish_connection!(DB2S3::Config::S3.slice(:access_key_id, :secret_access_key).merge(:use_ssl => true))
|
80
|
+
AWS::S3::Bucket.create(bucket)
|
81
|
+
@connected = true
|
82
|
+
end
|
83
|
+
|
84
|
+
def store(file_name, file)
|
85
|
+
ensure_connected
|
86
|
+
AWS::S3::S3Object.store(file_name, file, bucket)
|
87
|
+
end
|
88
|
+
|
89
|
+
def fetch(file_name)
|
90
|
+
ensure_connected
|
91
|
+
AWS::S3::S3Object.find(file_name, bucket)
|
92
|
+
|
93
|
+
file = Tempfile.new("dump")
|
94
|
+
open(file.path, 'w') do |f|
|
95
|
+
AWS::S3::S3Object.stream(file_name, bucket) do |chunk|
|
96
|
+
f.write chunk
|
97
|
+
end
|
98
|
+
end
|
99
|
+
file
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def bucket
|
105
|
+
DB2S3::Config::S3[:bucket]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
data/lib/db2s3/tasks.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir["#{File.dirname(__FILE__)}/../../tasks/*.rake"].each { |ext| load ext }
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/db2s3')
|
data/spec/db2s3_spec.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe 'db2s3' do
|
4
|
+
def load_schema
|
5
|
+
`cat '#{File.dirname(__FILE__) + '/mysql_schema.sql'}' | mysql -u #{DBConfig[:user]} #{DBConfig[:database]}`
|
6
|
+
end
|
7
|
+
|
8
|
+
def drop_schema
|
9
|
+
`cat '#{File.dirname(__FILE__) + '/mysql_drop_schema.sql'}' | mysql -u #{DBConfig[:user]} #{DBConfig[:database]}`
|
10
|
+
end
|
11
|
+
|
12
|
+
class Person < ActiveRecord::Base
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'can save and restore a backup to S3' do
|
16
|
+
db2s3 = DB2S3.new
|
17
|
+
load_schema
|
18
|
+
Person.create!(:name => "Baxter")
|
19
|
+
db2s3.full_backup
|
20
|
+
drop_schema
|
21
|
+
db2s3.restore
|
22
|
+
Person.find_by_name("Baxter").should_not be_nil
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'provides estimated metrics' do
|
26
|
+
db2s3 = DB2S3.new
|
27
|
+
# 1 GB DB
|
28
|
+
db2s3.stub!(:dump_db).and_return(stub("dump file", :size => 1024 * 1024 * 1024))
|
29
|
+
metrics = db2s3.metrics
|
30
|
+
metrics.should == {
|
31
|
+
:storage_cost => 0.15, # 15c/GB-Month rounded up to nearest cent, we're only storing one backup
|
32
|
+
:transfer_cost => 3.0, # 10c/GB-Month * 30 backups
|
33
|
+
:db_size => 1024 * 1024 * 1024, # 1 GB
|
34
|
+
:total_cost => 3.17,
|
35
|
+
:requests_cost => 0.02,
|
36
|
+
:full_backups_per_month => 30 # Default 1 backup/day
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'rounds transfer cost metric up to nearest cent' do
|
41
|
+
db2s3 = DB2S3.new
|
42
|
+
# 1 KB DB
|
43
|
+
db2s3.stub!(:dump_db).and_return(stub("dump file", :size => 1024))
|
44
|
+
metrics = db2s3.metrics
|
45
|
+
metrics[:storage_cost].should == 0.01
|
46
|
+
metrics[:transfer_cost].should == 0.01
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
DROP TABLE IF EXISTS people;
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec'
|
2
|
+
require 'activerecord'
|
3
|
+
require File.dirname(__FILE__) + '/../lib/db2s3'
|
4
|
+
require File.dirname(__FILE__) + '/s3_config.rb'
|
5
|
+
|
6
|
+
DBConfig = {
|
7
|
+
:adapter => "mysql",
|
8
|
+
:encoding => "utf8",
|
9
|
+
:database => 'db2s3_unittest',
|
10
|
+
:user => "root"
|
11
|
+
}
|
12
|
+
|
13
|
+
ActiveRecord::Base.configurations = { 'production' => DBConfig }
|
14
|
+
ActiveRecord::Base.establish_connection(:production)
|
data/tasks/tasks.rake
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
namespace :db2s3 do
|
2
|
+
namespace :backup do
|
3
|
+
desc "Save a full back to S3"
|
4
|
+
task :full => :environment do
|
5
|
+
DB2S3.new.full_backup
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Restore your DB from S3"
|
9
|
+
task :restore => :environment do
|
10
|
+
DB2S3.new.restore
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Provide estimated costs for backing up your DB to S3"
|
15
|
+
task :metrics => :environment do
|
16
|
+
def format_size(size)
|
17
|
+
units = %w{B KB MB GB TB}
|
18
|
+
e = (Math.log(size)/Math.log(1024)).floor
|
19
|
+
s = "%.3f" % (size.to_f / 1024**e)
|
20
|
+
s.sub(/\.?0*$/, units[e])
|
21
|
+
end
|
22
|
+
|
23
|
+
def format_cost(cost)
|
24
|
+
"%.2f" % [cost]
|
25
|
+
end
|
26
|
+
|
27
|
+
metrics = DB2S3.new.metrics
|
28
|
+
puts <<-EOS
|
29
|
+
Estimates only, does not take into account metadata overhead
|
30
|
+
|
31
|
+
DB Size: #{format_size(metrics[:db_size])}
|
32
|
+
Full backups/month: #{metrics[:full_backups_per_month]}
|
33
|
+
Storage Cost $US: #{format_cost(metrics[:storage_cost])}
|
34
|
+
Transfer Cost $US: #{format_cost(metrics[:transfer_cost])}
|
35
|
+
Requests Cost $US: #{format_cost(metrics[:requests_cost])}
|
36
|
+
Total Cost $US: #{format_cost(metrics[:total_cost])}
|
37
|
+
EOS
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fcoury-db2s3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Felipe Coury
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-08 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: aws-s3
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.5.1
|
24
|
+
version:
|
25
|
+
description: db2s3 provides rake tasks for backing up and restoring your DB to S3
|
26
|
+
email: contact@felipecoury.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- README
|
35
|
+
- Rakefile
|
36
|
+
- db2s3.gemspec
|
37
|
+
- init.rb
|
38
|
+
- lib
|
39
|
+
- lib/db2s3.rb
|
40
|
+
- lib/db2s3/tasks.rb
|
41
|
+
- rails
|
42
|
+
- rails/init.rb
|
43
|
+
- spec
|
44
|
+
- spec/db2s3_spec.rb
|
45
|
+
- spec/mysql_drop_schema.sql
|
46
|
+
- spec/mysql_schema.sql
|
47
|
+
- spec/s3_config.example.rb
|
48
|
+
- spec/spec_helper.rb
|
49
|
+
- tasks
|
50
|
+
- tasks/tasks.rake
|
51
|
+
has_rdoc: false
|
52
|
+
homepage: http://github.com/fcoury/db2s3
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.2.0
|
74
|
+
signing_key:
|
75
|
+
specification_version: 2
|
76
|
+
summary: db2s3 provides rake tasks for backing up and restoring your DB to S3
|
77
|
+
test_files: []
|
78
|
+
|