Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 39539
1
###
2
PostgreSQL -- operations code, e.g., backups, maintenance, etc.
3
4
COPYRIGHT : (c) 2017 SageMath, Inc.
5
LICENSE : AGPLv3
6
###
7
8
fs = require('fs')
9
async = require('async')
10
11
misc_node = require('smc-util-node/misc_node')
12
13
{defaults} = misc = require('smc-util/misc')
14
required = defaults.required
15
16
{SCHEMA} = require('smc-util/schema')
17
18
{PostgreSQL} = require('./postgres')
19
20
class exports.PostgreSQL extends PostgreSQL
21
# Backups up the indicated tables.
22
# WARNING: This is NOT guaranteed to give a point
23
# in time backup of the entire database across tables!
24
# The backup of each table is only consistent within that
25
# table. For SMC, this tends to be fine, due to our design.
26
# The advantage of this is that we can backup huge tables
27
# only once a week, and other smaller tables much more frequently.
28
29
# For tables:
30
# - a list of tables
31
# - 'all' (the string) -- backs up everything in the SMC schema (not the database!)
32
# - 'critical' -- backs up only smaller critical tables, which we would desparately
33
# need for disaster recovery
34
backup_tables: (opts) =>
35
opts = defaults opts,
36
tables : required # list of tables, 'all' or 'critical'
37
path : 'backup'
38
limit : 3 # number of tables to backup in parallel
39
bup : true # creates/updates a bup archive in backup/.bup,
40
# so we have snapshots of all past backups!
41
cb : required
42
tables = @_get_backup_tables(opts.tables)
43
dbg = @_dbg("backup_tables()")
44
dbg("backing up tables: #{misc.to_json(tables)}")
45
async.series([
46
(cb) =>
47
backup = (table, cb) =>
48
dbg("backup '#{table}'")
49
@_backup_table
50
table : table
51
path : opts.path
52
cb : cb
53
async.mapLimit(tables, opts.limit, backup, cb)
54
(cb) =>
55
@_backup_bup
56
path : opts.path
57
cb : cb
58
], (err) => opts.cb(err))
59
60
_backup_table: (opts) =>
61
opts = defaults opts,
62
table : required
63
path : 'backup'
64
cb : required
65
dbg = @_dbg("_backup_table(table='#{opts.table}')")
66
cmd = "mkdir -p #{opts.path}; time pg_dump -Fc --table #{opts.table} #{@_database} > #{opts.path}/#{opts.table}.bak"
67
dbg(cmd)
68
misc_node.execute_code
69
command : cmd
70
timeout : 0
71
home : '.'
72
env :
73
PGPASSWORD : @_password
74
PGUSER : 'smc'
75
PGHOST : @_host
76
err_on_exit : true
77
cb : opts.cb
78
79
_backup_bup: (opts) =>
80
opts = defaults opts,
81
path : 'backup'
82
cb : required
83
dbg = @_dbg("_backup_bup(path='#{opts.path}')")
84
# We use no compression because the backup files are already all highly compressed.
85
cmd = "mkdir -p '#{opts.path}' && export && bup init && bup index '#{opts.path}' && bup save --strip --compress=0 '#{opts.path}' -n master"
86
dbg(cmd)
87
misc_node.execute_code
88
command : cmd
89
timeout : 0
90
home : '.'
91
env :
92
BUP_DIR : "#{opts.path}/.bup"
93
err_on_exit : true
94
cb : opts.cb
95
96
_get_backup_tables: (tables) =>
97
if misc.is_array(tables)
98
return tables
99
all = (t for t,s of SCHEMA when not s.virtual)
100
if tables == 'all'
101
return all
102
else if tables == 'critical'
103
# TODO: critical for backup or not should probably be in the schema itself, not here.
104
v = []
105
non_critical = ['stats','syncstrings','file_use','eval_outputs','blobs','eval_inputs','patches','cursors']
106
for x in all
107
if x.indexOf('log') == -1 and x not in non_critical
108
v.push(x)
109
return v
110
else
111
return [tables]
112
113
# Restore the given tables from the backup in the given directory.
114
restore_tables: (opts) =>
115
opts = defaults opts,
116
tables : undefined # same as for backup_tables, or undefined to use whatever we have in the path
117
path : '/backup/postgres'
118
limit : 5
119
cb : required
120
backed_up_tables = (filename[...-4] for filename in fs.readdirSync(opts.path) when filename[-4..] == '.bak')
121
if not opts.tables?
122
tables = backed_up_tables
123
else
124
tables = @_get_backup_tables(opts.tables)
125
for table in tables
126
if table not in backed_up_tables
127
opts.cb("there is no backup of '#{table}'")
128
return
129
dbg = @_dbg("restore_tables()")
130
dbg("restoring tables: #{misc.to_json(tables)}")
131
restore = (table, cb) =>
132
dbg("restore '#{table}'")
133
@_restore_table
134
table : table
135
path : opts.path
136
cb : cb
137
async.mapLimit(tables, opts.limit, restore, (err)=>opts.cb(err))
138
139
_restore_table: (opts) =>
140
opts = defaults opts,
141
table : required
142
path : 'backup'
143
cb : required
144
dbg = @_dbg("_restore_table(table='#{opts.table}')")
145
async.series([
146
(cb) =>
147
dbg("dropping existing table if it exists")
148
@_query
149
query : "DROP TABLE IF EXISTS #{opts.table}"
150
cb : cb
151
(cb) =>
152
cmd = "time pg_restore -C -d #{@_database} #{opts.path}/#{opts.table}.bak"
153
dbg(cmd)
154
misc_node.execute_code
155
command : cmd
156
timeout : 0
157
home : '.'
158
env :
159
PGPASSWORD : @_password
160
PGUSER : 'smc'
161
PGHOST : @_host
162
err_on_exit : true
163
cb : cb
164
], (err) => opts.cb(err))
165
166
167