litestream 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7942f70131f11cfcfad19c07442ab78571ad5bb28f67d8e38f1c35573a04413
4
- data.tar.gz: 7584c2778dff02a7ded7078656ba57247146a67924c2ef05f4e1fe8407ea04e9
3
+ metadata.gz: 4b38d63aa3c23ba58c57dd91f178e069e898519f06b38e7ece5d0f9b9238f20d
4
+ data.tar.gz: 730e40b5ba2fd39a726902052333c049943adebbde252d2a769013585172d789
5
5
  SHA512:
6
- metadata.gz: a48912bbcafb448473ebfd6ff378db5faa0a7f1830b786dff1b6574f8bd9ce210fd1f6b2f6652555544a38923a68fd87562abbca8e497db4282facc91a353ce0
7
- data.tar.gz: a2a798e38ccf63d1fd21008bf1ef626a77652f8a8d1ef192fb1eeb11344f933d209f661dc3d24afd905d534ad5c295f4a0afb07b5cc45781ce152b2431746d72
6
+ metadata.gz: b07f6c0cd590a5ec095f778a1b473a6168adc5e747aadfbef2ce8dbea4047ef27ed9861ce0c36ac4d78bcdf7ea819a4e2d57db4c989ee8da9190523a77ae86cf
7
+ data.tar.gz: 0f9436ff88c9adf3b521c853e3a8e770b8aea4590fd2d8eb2cb575d48dfd7044a6156982b2e2924724c1bd5a5b97840eba567b143084abaf28786a4e50523135
data/README.md CHANGED
@@ -165,6 +165,40 @@ You can forward arguments in whatever order you like, you simply need to ensure
165
165
  Disables environment variable expansion in configuration file.
166
166
  ```
167
167
 
168
+ ### Verification
169
+
170
+ You can verify the integrity of your backed-up databases using the gem's provided `litestream:verify` rake task. This rake task requires that you specify which specific database you want to verify. As with the `litestream:restore` tasks, you pass arguments to the rake task via argument forwarding. For example, to verify the production database, you would run:
171
+
172
+ ```shell
173
+ bin/rails litestream:verify -- --database=storage/production.sqlite3
174
+ # or
175
+ bundle exec rake litestream:verify -- --database=storage/production.sqlite3
176
+ ```
177
+
178
+ The `litestream:verify` rake task takes the same options as the `litestream:restore` rake task. After restoring the backup, the rake task will verify the integrity of the restored database by ensuring that the restored database file
179
+
180
+ 1. exists,
181
+ 2. can be opened by SQLite, and
182
+ 3. sufficiently matches the original database file.
183
+
184
+ Since point 3 is subjective, the rake task will output a message providing both the file size and number of tables of both the "original" and "restored" databases. You must manually verify that the restored database is within an acceptable range of the original database.
185
+
186
+ The rake task will output a message similar to the following:
187
+
188
+ ```
189
+ size
190
+ original 21688320
191
+ restored 21688320
192
+ delta 0
193
+
194
+ tables
195
+ original 9
196
+ restored 9
197
+ delta 0
198
+ ```
199
+
200
+ After restoring the backup, the `litestream:verify` rake task will delete the restored database file. If you need the restored database file, use the `litestream:restore` rake task instead.
201
+
168
202
  ### Introspection
169
203
 
170
204
  Litestream offers a handful of commands that allow you to introspect the state of your replication. The gem provides a few rake tasks that wrap these commands for you. For example, you can list the databases that Litestream is configured to replicate:
@@ -17,6 +17,9 @@ module Litestream
17
17
  # raised when a litestream command requires a database argument but it isn't provided
18
18
  DatabaseRequiredException = Class.new(StandardError)
19
19
 
20
+ # raised when litestream fails to restore a database backup
21
+ BackupFailedException = Class.new(StandardError)
22
+
20
23
  class << self
21
24
  def platform
22
25
  [:cpu, :os].map { |m| Gem::Platform.local.send(m) }.join("-")
@@ -73,44 +76,67 @@ module Litestream
73
76
  exe_file
74
77
  end
75
78
 
76
- def replicate(argv = {})
77
- execute("replicate", argv)
79
+ def replicate(async: true, **argv)
80
+ execute("replicate", argv, async: async)
78
81
  end
79
82
 
80
- def restore(database, argv = {})
83
+ def restore(database, async: true, **argv)
81
84
  raise DatabaseRequiredException, "database argument is required for restore command, e.g. litestream:restore -- --database=path/to/database.sqlite" if database.nil?
82
85
 
83
86
  dir, file = File.split(database)
84
87
  ext = File.extname(file)
85
88
  base = File.basename(file, ext)
86
89
  now = Time.now.utc.strftime("%Y%m%d%H%M%S")
90
+ backup = File.join(dir, "#{base}-#{now}#{ext}")
87
91
 
88
92
  args = {
89
- "-o" => File.join(dir, "#{base}-#{now}#{ext}")
93
+ "-o" => backup
90
94
  }.merge(argv)
91
95
 
92
- execute("restore", args, database)
96
+ execute("restore", args, database, async: async)
97
+
98
+ backup
93
99
  end
94
100
 
95
- def databases(argv = {})
96
- execute("databases", argv)
101
+ def databases(async: true, **argv)
102
+ execute("databases", argv, async: async)
97
103
  end
98
104
 
99
- def generations(database, argv = {})
105
+ def generations(database, async: true, **argv)
100
106
  raise DatabaseRequiredException, "database argument is required for generations command, e.g. litestream:generations -- --database=path/to/database.sqlite" if database.nil?
101
107
 
102
- execute("generations", argv, database)
108
+ execute("generations", argv, database, async: async)
103
109
  end
104
110
 
105
- def snapshots(database, argv = {})
111
+ def snapshots(database, async: true, **argv)
106
112
  raise DatabaseRequiredException, "database argument is required for snapshots command, e.g. litestream:snapshots -- --database=path/to/database.sqlite" if database.nil?
107
113
 
108
- execute("snapshots", argv, database)
114
+ execute("snapshots", argv, database, async: async)
115
+ end
116
+
117
+ def verify(database, async: true, **argv)
118
+ raise DatabaseRequiredException, "database argument is required for verify command, e.g. litestream:verify -- --database=path/to/database.sqlite" if database.nil? || !File.exist?(database)
119
+
120
+ backup = restore(database, async: false, **argv)
121
+
122
+ raise BackupFailedException, "Failed to create backup for validation" unless File.exist?(backup)
123
+
124
+ restored_tables_count = `sqlite3 #{backup} "select count(*) from sqlite_schema where type='table';"`.chomp.to_i
125
+ restored_size = File.size(backup)
126
+ original_tables_count = `sqlite3 #{database} "select count(*) from sqlite_schema where type='table';"`.chomp.to_i
127
+ original_size = File.size(database)
128
+
129
+ Dir.glob(backup + "*").each { |file| File.delete(file) }
130
+
131
+ {
132
+ size: {original: original_size, restored: restored_size},
133
+ tables: {original: original_tables_count, restored: restored_tables_count}
134
+ }
109
135
  end
110
136
 
111
137
  private
112
138
 
113
- def execute(command, argv = {}, database = nil)
139
+ def execute(command, argv = {}, database = nil, async: true)
114
140
  if Litestream.configuration
115
141
  ENV["LITESTREAM_REPLICA_BUCKET"] ||= Litestream.configuration.replica_bucket
116
142
  ENV["LITESTREAM_ACCESS_KEY_ID"] ||= Litestream.configuration.replica_key_id
@@ -121,12 +147,14 @@ module Litestream
121
147
  "--config" => Rails.root.join("config", "litestream.yml").to_s
122
148
  }.merge(argv).to_a.flatten.compact
123
149
  cmd = [executable, command, *args, database].compact
150
+ puts cmd.inspect if ENV["DEBUG"]
124
151
 
125
- # To release the resources of the Ruby process, just fork and exit.
126
- # The forked process executes litestream and replaces itself.
127
- if fork.nil?
128
- puts cmd.inspect if ENV["DEBUG"]
129
- exec(*cmd)
152
+ if async
153
+ # To release the resources of the Ruby process, just fork and exit.
154
+ # The forked process executes litestream and replaces itself.
155
+ exec(*cmd) if fork.nil?
156
+ else
157
+ system(*cmd)
130
158
  end
131
159
  end
132
160
  end
@@ -1,3 +1,3 @@
1
1
  module Litestream
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.2"
3
3
  end
@@ -72,4 +72,29 @@ namespace :litestream do
72
72
 
73
73
  Litestream::Commands.snapshots(options.delete("--database") || options.delete("-database"), options)
74
74
  end
75
+
76
+ desc "verify backup of SQLite database from a Litestream replica, e.g. rake litestream:verify -- -database=storage/production.sqlite3"
77
+ task verify: :environment do
78
+ options = {}
79
+ if (separator_index = ARGV.index("--"))
80
+ ARGV.slice(separator_index + 1, ARGV.length)
81
+ .map { |pair| pair.split("=") }
82
+ .each { |opt| options[opt[0]] = opt[1] || nil }
83
+ end
84
+
85
+ result = Litestream::Commands.verify(options.delete("--database") || options.delete("-database"), options)
86
+
87
+ puts <<~TXT if result
88
+
89
+ size
90
+ original #{result[:size][:original]}
91
+ restored #{result[:size][:restored]}
92
+ delta #{result[:size][:original] - result[:size][:restored]}
93
+
94
+ tables
95
+ original #{result[:tables][:original]}
96
+ restored #{result[:tables][:restored]}
97
+ delta #{result[:tables][:original] - result[:tables][:restored]}
98
+ TXT
99
+ end
75
100
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: litestream
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Margheim