summaryrefslogtreecommitdiff
path: root/lib/backup/database.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/backup/database.rb')
-rw-r--r--lib/backup/database.rb189
1 files changed, 109 insertions, 80 deletions
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index cf19b4fa8ff..61dd6033eb0 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -6,7 +6,7 @@ module Backup
class Database < Task
extend ::Gitlab::Utils::Override
include Backup::Helper
- attr_reader :force, :config
+ attr_reader :force
IGNORED_ERRORS = [
# Ignore warnings
@@ -18,98 +18,108 @@ module Backup
].freeze
IGNORED_ERRORS_REGEXP = Regexp.union(IGNORED_ERRORS).freeze
- def initialize(database_name, progress, force:)
+ def initialize(progress, force:)
super(progress)
- @database_name = database_name
- @config = base_model.connection_db_config.configuration_hash
@force = force
end
override :dump
- def dump(db_file_name, backup_id)
- FileUtils.mkdir_p(File.dirname(db_file_name))
- FileUtils.rm_f(db_file_name)
- compress_rd, compress_wr = IO.pipe
- compress_pid = spawn(gzip_cmd, in: compress_rd, out: [db_file_name, 'w', 0600])
- compress_rd.close
-
- dump_pid =
- case config[:adapter]
- when "postgresql" then
- progress.print "Dumping PostgreSQL database #{database} ... "
- pg_env
- pgsql_args = ["--clean"] # Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
- pgsql_args << '--if-exists'
-
- if Gitlab.config.backup.pg_schema
- pgsql_args << '-n'
- pgsql_args << Gitlab.config.backup.pg_schema
+ def dump(destination_dir, backup_id)
+ snapshot_ids = base_models_for_backup.each_with_object({}) do |(database_name, base_model), snapshot_ids|
+ base_model.connection.begin_transaction(isolation: :repeatable_read)
- Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
- pgsql_args << '-n'
- pgsql_args << schema.to_s
- end
- end
+ snapshot_ids[database_name] =
+ base_model.connection.execute("SELECT pg_export_snapshot() as snapshot_id;").first['snapshot_id']
+ end
+
+ FileUtils.mkdir_p(destination_dir)
+
+ snapshot_ids.each do |database_name, snapshot_id|
+ base_model = base_models_for_backup[database_name]
+
+ config = base_model.connection_db_config.configuration_hash
+
+ db_file_name = file_name(destination_dir, database_name)
+ FileUtils.rm_f(db_file_name)
+
+ pg_database = config[:database]
- Process.spawn('pg_dump', *pgsql_args, database, out: compress_wr)
+ progress.print "Dumping PostgreSQL database #{pg_database} ... "
+ pg_env(config)
+ pgsql_args = ["--clean"] # Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
+ pgsql_args << '--if-exists'
+ pgsql_args << "--snapshot=#{snapshot_ids[database_name]}"
+
+ if Gitlab.config.backup.pg_schema
+ pgsql_args << '-n'
+ pgsql_args << Gitlab.config.backup.pg_schema
+
+ Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
+ pgsql_args << '-n'
+ pgsql_args << schema.to_s
+ end
end
- compress_wr.close
- success = [compress_pid, dump_pid].all? do |pid|
- Process.waitpid(pid)
- $?.success?
- end
+ success = Backup::Dump::Postgres.new.dump(pg_database, db_file_name, pgsql_args)
+
+ base_model.connection.rollback_transaction
- report_success(success)
- progress.flush
+ raise DatabaseBackupError.new(config, db_file_name) unless success
- raise DatabaseBackupError.new(config, db_file_name) unless success
+ report_success(success)
+ progress.flush
+ end
end
override :restore
- def restore(db_file_name)
- unless File.exist?(db_file_name)
- raise(Backup::Error, "Source database file does not exist #{db_file_name}") if main_database?
+ def restore(destination_dir)
+ base_models_for_backup.each do |database_name, base_model|
+ config = base_model.connection_db_config.configuration_hash
- progress.puts "Source backup for the database #{@database_name} doesn't exist. Skipping the task"
- return
- end
+ db_file_name = file_name(destination_dir, database_name)
+ database = config[:database]
- unless force
- progress.puts 'Removing all tables. Press `Ctrl-C` within 5 seconds to abort'.color(:yellow)
- sleep(5)
- end
+ unless File.exist?(db_file_name)
+ raise(Backup::Error, "Source database file does not exist #{db_file_name}") if main_database?(database_name)
- # Drop all tables Load the schema to ensure we don't have any newer tables
- # hanging out from a failed upgrade
- puts_time 'Cleaning the database ... '.color(:blue)
- Rake::Task['gitlab:db:drop_tables'].invoke
- puts_time 'done'.color(:green)
-
- decompress_rd, decompress_wr = IO.pipe
- decompress_pid = spawn(*%w(gzip -cd), out: decompress_wr, in: db_file_name)
- decompress_wr.close
-
- status, @errors =
- case config[:adapter]
- when "postgresql" then
- progress.print "Restoring PostgreSQL database #{database} ... "
- pg_env
- execute_and_track_errors(pg_restore_cmd, decompress_rd)
+ progress.puts "Source backup for the database #{database_name} doesn't exist. Skipping the task"
+ return false
end
- decompress_rd.close
- Process.waitpid(decompress_pid)
- success = $?.success? && status.success?
+ unless force
+ progress.puts 'Removing all tables. Press `Ctrl-C` within 5 seconds to abort'.color(:yellow)
+ sleep(5)
+ end
- if @errors.present?
- progress.print "------ BEGIN ERRORS -----\n".color(:yellow)
- progress.print @errors.join.color(:yellow)
- progress.print "------ END ERRORS -------\n".color(:yellow)
- end
+ # Drop all tables Load the schema to ensure we don't have any newer tables
+ # hanging out from a failed upgrade
+ drop_tables(database_name)
+
+ decompress_rd, decompress_wr = IO.pipe
+ decompress_pid = spawn(*%w(gzip -cd), out: decompress_wr, in: db_file_name)
+ decompress_wr.close
+
+ status, @errors =
+ case config[:adapter]
+ when "postgresql" then
+ progress.print "Restoring PostgreSQL database #{database} ... "
+ pg_env(config)
+ execute_and_track_errors(pg_restore_cmd(database), decompress_rd)
+ end
+ decompress_rd.close
+
+ Process.waitpid(decompress_pid)
+ success = $?.success? && status.success?
- report_success(success)
- raise Backup::Error, 'Restore failed' unless success
+ if @errors.present?
+ progress.print "------ BEGIN ERRORS -----\n".color(:yellow)
+ progress.print @errors.join.color(:yellow)
+ progress.print "------ END ERRORS -------\n".color(:yellow)
+ end
+
+ report_success(success)
+ raise Backup::Error, 'Restore failed' unless success
+ end
end
override :pre_restore_warning
@@ -144,16 +154,22 @@ module Backup
protected
- def database
- @config[:database]
+ def base_models_for_backup
+ @base_models_for_backup ||= Gitlab::Database.database_base_models_with_gitlab_shared
end
- def base_model
- Gitlab::Database.database_base_models[@database_name]
+ def main_database?(database_name)
+ database_name.to_sym == :main
end
- def main_database?
- @database_name == :main
+ def file_name(base_dir, database_name)
+ prefix = if database_name.to_sym != :main
+ "#{database_name}_"
+ else
+ ''
+ end
+
+ File.join(base_dir, "#{prefix}database.sql.gz")
end
def ignore_error?(line)
@@ -189,7 +205,7 @@ module Backup
end
end
- def pg_env
+ def pg_env(config)
args = {
username: 'PGUSER',
host: 'PGHOST',
@@ -223,7 +239,20 @@ module Backup
private
- def pg_restore_cmd
+ def drop_tables(database_name)
+ if Rake::Task.task_defined? "gitlab:db:drop_tables:#{database_name}"
+ puts_time 'Cleaning the database ... '.color(:blue)
+ Rake::Task["gitlab:db:drop_tables:#{database_name}"].invoke
+ puts_time 'done'.color(:green)
+ elsif Gitlab::Database.database_base_models.one?
+ # In single database, we do not have rake tasks per database
+ puts_time 'Cleaning the database ... '.color(:blue)
+ Rake::Task["gitlab:db:drop_tables"].invoke
+ puts_time 'done'.color(:green)
+ end
+ end
+
+ def pg_restore_cmd(database)
['psql', database]
end
end