Blog

Gemme de sauvegarde

Icône flèche bleue vers la gauche
Retour au blog
Gemme de sauvegarde

Gemme de sauvegarde

November 14, 2013

We all know that data is the most valuable asset of our client. Even if we know that fact, we tend to not handle it with the care it deserves and far too often we find ourselves in a position where we need to fix it or retrieve its previous state. That’s when most of us think to theirselves “I knew I should have setup the backup last week/month/year”

But why didn’t we do it ? Because it is booOOooring !

Rejoice everyone ! With the backup gem ( https://github.com/meskyanichi/backup) you can now : - setup a generational backup of your files and your DB in a few minutes ( + the time for reading the excellent documentation ) - keep your backup settings with your project files - schedule precisely when your backup should be performed - be notified of your failing backup - store your backup locally and/or in the cloud

The basic setup proposed on the official doc is to setup the backup directly on the server but we found it much easier to keep it with our project files. Therefore the first step is to add it to your Gemfile (without requiring it since it is not a dependency of your production code ). We are also going to add the whenever gem in order to setup some cron jobs for performing the backup regularly.

# Backup utilities
gem "backup" , :require => false
gem "whenever" , :require => false

Next step is to configure the backup. Since we are working in a rails project, we will put the backup configuration under a “config/backup” subdirectory. The main config file will be used to define some configuration properties shared between the multiple triggers. We will then setup multiple triggers in order to have a multi-generational backup.

# config/backup/config.rb

# since we are stroring the backup config with the project files and not on the server
# it is a good idea to keep track of the project root
FS_ROOT = Pathname.new(Config.config_file).join('..', '..', '..')

# * * * * * * * * * * * * * * * * * * * *
# Do Not Edit Below Here.
# All Configuration Should Be Made Above.

##
# Load all models from the models directory.

Dir[File.join(File.dirname(Config.config_file), "models", "*.rb")].each do |model|
instance_eval(File.read(model))
end

The default backup config generator creates a subdirectory named “models” where it stores the different triggers. Even if we can easily change that directory name (and change the main config file accordingly) we find it a good idea to keep the same nomenclature as the other users of the gem.

# config/backup/models/hourly.rb
# https://github.com/meskyanichi/backup

Backup::Model.new(:hourly, 'Backup DB and files hourly and keep them 48 hours') do

split_into_chunks_of 250

archive :avatars do |archive|
archive.add FS_ROOT.join("public/avatars")
end

archive :generated_pdfs do |archive|
archive.add FS_ROOT.join("public/generated_pdfs")
end

archive :uploads do |archive|
archive.add FS_ROOT.join("public/uploads")
end

database PostgreSQL do |db|
require 'yaml'
db_config_file = FS_ROOT.join('config', 'database.yml')
env = ENV.fetch('RAILS_ENV'){'development'}
parsed_file = ERB.new(File.read(db_config_file)).result
db_config = YAML.load(parsed_file)[env]
db.name = db_config['database']
db.username = db_config['username']
db.password = db_config['password']
db.host = db_config['host']
db.additional_options = ["-E=utf8"]
end

store_with Local do |local|
local.path = FS_ROOT.join('backups').expand_path
local.keep = 48
end

compress_with Gzip

end

# config/backup/models/daily.rb
# https://github.com/meskyanichi/backup

Backup::Model.new(:daily, 'Backup DB and files daily and keep them 15 days') do

split_into_chunks_of 250

archive :avatars do |archive|
archive.add FS_ROOT.join("public/avatars")
end

archive :generated_pdfs do |archive|
archive.add FS_ROOT.join("public/generated_pdfs")
end

archive :uploads do |archive|
archive.add FS_ROOT.join("public/uploads")
end

database PostgreSQL do |db|
require 'yaml'
db_config_file = FS_ROOT.join('config', 'database.yml')
env = ENV.fetch('RAILS_ENV'){'development'}
parsed_file = ERB.new(File.read(db_config_file)).result
db_config = YAML.load(parsed_file)[env]
db.name = db_config['database']
db.username = db_config['username']
db.password = db_config['password']
db.host = db_config['host']
db.additional_options = ["-E=utf8"]
end

store_with Local do |local|
local.path = FS_ROOT.join('backups').expand_path
local.keep = 15
end

compress_with Gzip
end

These two file are very simple examples of trigger files. The doc is really excellent at explaining all the options for other DBs, storing to the cloud, notifying successful and failed backups, etc.

The main details in those two files are the different name of the triggers and the number of backup to keep. The store path is the same because the trigger name will automatically be appended to it. The database block uses the database.yml file in order to make a dump of the database.

On our server, the “project_root/backups” directory is automatically replaced by a symbolic link targetting a special directory on the server. This configuration allows us to have a structure working either in our development environment, our staging environment and our production environment.

We can now use the whenever gem to schedule the different triggers :

#config/shedule.rb

set :environment, ENV.fetch(environment_variable){'development'}

job_type :bundle_exec, "cd :path && :environment_variable=:environment bundle exec :task :output"

every 1.hour do
bundle_exec 'backup perform --trigger hourly --config-file ./config/backup/config.rb --data-path ./backups --tmp-path ./tmp'
end

every 1.day do
bundle_exec 'backup perform --trigger daily --config-file ./config/backup/config.rb --data-path ./backups --tmp-path ./tmp'
end

every 1.week do
bundle_exec 'backup perform --trigger weekly --config-file ./config/backup/config.rb --data-path ./backups --tmp-path ./tmp'
end

every 1.month do
bundle_exec 'backup perform --trigger monthly --config-file ./config/backup/config.rb --data-path ./backups --tmp-path ./tmp'
end

Prêt à créer votre produit logiciel ? Contactez-nous !