litestream 0.5.0-arm64-linux → 0.5.2-arm64-linux
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +75 -0
- data/lib/litestream/commands.rb +115 -73
- data/lib/litestream/version.rb +1 -1
- data/lib/tasks/litestream_tasks.rake +61 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 197d7afb6dbd50c7e2ebb422a43fbb951846e82fe41d495ee5606f41f3d6f3d5
|
4
|
+
data.tar.gz: 1ef3c19638a598ff1e55b4cf55accc9e43606f878e1f4b8b369bb53e0336ee5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e842650e592bb8cf83f68e4d26075984f285b2a95474ac460e4ed36d039eae8301fb94f5f756413be8a881eca106e177c71ee1b41754af5fb177240251abb9b6
|
7
|
+
data.tar.gz: 0c22d1bb96beeecad832533dd88047fa23c2c3b497d327ac7d2420b06d60a4511660c54cd2af2677e0285f2f89c323d3925defb966113ac9db14c697a4b6d992
|
data/README.md
CHANGED
@@ -165,6 +165,81 @@ 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
|
+
|
202
|
+
### Introspection
|
203
|
+
|
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:
|
205
|
+
|
206
|
+
```shell
|
207
|
+
bin/rails litestream:databases
|
208
|
+
```
|
209
|
+
|
210
|
+
This will return a list of databases and their configured replicas:
|
211
|
+
|
212
|
+
```
|
213
|
+
path replicas
|
214
|
+
/Users/you/Code/your-app/storage/production.sqlite3 s3
|
215
|
+
```
|
216
|
+
|
217
|
+
You can also list the generations of a specific database:
|
218
|
+
|
219
|
+
```shell
|
220
|
+
bin/rails litestream:generations -- --database=storage/production.sqlite3
|
221
|
+
```
|
222
|
+
|
223
|
+
This will list all generations for the specified database, including stats about their lag behind the primary database and the time range they cover.
|
224
|
+
|
225
|
+
```
|
226
|
+
name generation lag start end
|
227
|
+
s3 a295b16a796689f3 -156ms 2024-04-17T00:01:19Z 2024-04-17T00:01:19Z
|
228
|
+
```
|
229
|
+
|
230
|
+
Finally, you can list the snapshots available for a database:
|
231
|
+
|
232
|
+
```shell
|
233
|
+
bin/rails litestream:snapshots -- --database=storage/production.sqlite3
|
234
|
+
```
|
235
|
+
|
236
|
+
This command lists snapshots available for that specified database:
|
237
|
+
|
238
|
+
```
|
239
|
+
replica generation index size created
|
240
|
+
s3 a295b16a796689f3 1 4645465 2024-04-17T00:01:19Z
|
241
|
+
```
|
242
|
+
|
168
243
|
### Additional commands
|
169
244
|
|
170
245
|
The rake tasks are the recommended way to interact with the Litestream utility in your Rails application or Ruby project. But, you _can_ work directly with the Litestream CLI. Since the gem installs the native executable via Bundler, the `litestream` command will be available in your `PATH`.
|
data/lib/litestream/commands.rb
CHANGED
@@ -14,106 +14,148 @@ module Litestream
|
|
14
14
|
# raised when LITESTREAM_INSTALL_DIR does not exist
|
15
15
|
DirectoryNotFoundException = Class.new(StandardError)
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
# raised when a litestream command requires a database argument but it isn't provided
|
18
|
+
DatabaseRequiredException = Class.new(StandardError)
|
19
|
+
|
20
|
+
# raised when litestream fails to restore a database backup
|
21
|
+
BackupFailedException = Class.new(StandardError)
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def platform
|
25
|
+
[:cpu, :os].map { |m| Gem::Platform.local.send(m) }.join("-")
|
26
|
+
end
|
20
27
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
def executable(exe_path: DEFAULT_DIR)
|
29
|
+
litestream_install_dir = ENV["LITESTREAM_INSTALL_DIR"]
|
30
|
+
if litestream_install_dir
|
31
|
+
if File.directory?(litestream_install_dir)
|
32
|
+
warn "NOTE: using LITESTREAM_INSTALL_DIR to find litestream executable: #{litestream_install_dir}"
|
33
|
+
exe_path = litestream_install_dir
|
34
|
+
exe_file = File.expand_path(File.join(litestream_install_dir, "litestream"))
|
35
|
+
else
|
36
|
+
raise DirectoryNotFoundException, <<~MESSAGE
|
37
|
+
LITESTREAM_INSTALL_DIR is set to #{litestream_install_dir}, but that directory does not exist.
|
38
|
+
MESSAGE
|
39
|
+
end
|
28
40
|
else
|
29
|
-
|
30
|
-
|
31
|
-
|
41
|
+
if Litestream::Upstream::NATIVE_PLATFORMS.keys.none? { |p| Gem::Platform.match_gem?(Gem::Platform.new(p), GEM_NAME) }
|
42
|
+
raise UnsupportedPlatformException, <<~MESSAGE
|
43
|
+
litestream-ruby does not support the #{platform} platform
|
44
|
+
Please install litestream following instructions at https://litestream.io/install
|
45
|
+
MESSAGE
|
46
|
+
end
|
47
|
+
|
48
|
+
exe_file = Dir.glob(File.expand_path(File.join(exe_path, "*", "litestream"))).find do |f|
|
49
|
+
Gem::Platform.match_gem?(Gem::Platform.new(File.basename(File.dirname(f))), GEM_NAME)
|
50
|
+
end
|
32
51
|
end
|
33
|
-
|
34
|
-
if
|
35
|
-
raise
|
36
|
-
|
37
|
-
|
52
|
+
|
53
|
+
if exe_file.nil? || !File.exist?(exe_file)
|
54
|
+
raise ExecutableNotFoundException, <<~MESSAGE
|
55
|
+
Cannot find the litestream executable for #{platform} in #{exe_path}
|
56
|
+
|
57
|
+
If you're using bundler, please make sure you're on the latest bundler version:
|
58
|
+
|
59
|
+
gem install bundler
|
60
|
+
bundle update --bundler
|
61
|
+
|
62
|
+
Then make sure your lock file includes this platform by running:
|
63
|
+
|
64
|
+
bundle lock --add-platform #{platform}
|
65
|
+
bundle install
|
66
|
+
|
67
|
+
See `bundle lock --help` output for details.
|
68
|
+
|
69
|
+
If you're still seeing this message after taking those steps, try running
|
70
|
+
`bundle config` and ensure `force_ruby_platform` isn't set to `true`. See
|
71
|
+
https://github.com/fractaledmind/litestream-ruby#check-bundle_force_ruby_platform
|
72
|
+
for more details.
|
38
73
|
MESSAGE
|
39
74
|
end
|
40
75
|
|
41
|
-
exe_file
|
42
|
-
Gem::Platform.match_gem?(Gem::Platform.new(File.basename(File.dirname(f))), GEM_NAME)
|
43
|
-
end
|
76
|
+
exe_file
|
44
77
|
end
|
45
78
|
|
46
|
-
|
47
|
-
|
48
|
-
|
79
|
+
def replicate(async: true, **argv)
|
80
|
+
execute("replicate", argv, async: async)
|
81
|
+
end
|
49
82
|
|
50
|
-
|
83
|
+
def restore(database, async: true, **argv)
|
84
|
+
raise DatabaseRequiredException, "database argument is required for restore command, e.g. litestream:restore -- --database=path/to/database.sqlite" if database.nil?
|
51
85
|
|
52
|
-
|
53
|
-
|
86
|
+
dir, file = File.split(database)
|
87
|
+
ext = File.extname(file)
|
88
|
+
base = File.basename(file, ext)
|
89
|
+
now = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
90
|
+
backup = File.join(dir, "#{base}-#{now}#{ext}")
|
54
91
|
|
55
|
-
|
92
|
+
args = {
|
93
|
+
"-o" => backup
|
94
|
+
}.merge(argv)
|
56
95
|
|
57
|
-
|
58
|
-
bundle install
|
96
|
+
execute("restore", args, database, async: async)
|
59
97
|
|
60
|
-
|
98
|
+
backup
|
99
|
+
end
|
61
100
|
|
62
|
-
|
63
|
-
|
64
|
-
https://github.com/fractaledmind/litestream-ruby#check-bundle_force_ruby_platform
|
65
|
-
for more details.
|
66
|
-
MESSAGE
|
101
|
+
def databases(async: true, **argv)
|
102
|
+
execute("databases", argv, async: async)
|
67
103
|
end
|
68
104
|
|
69
|
-
|
70
|
-
|
105
|
+
def generations(database, async: true, **argv)
|
106
|
+
raise DatabaseRequiredException, "database argument is required for generations command, e.g. litestream:generations -- --database=path/to/database.sqlite" if database.nil?
|
71
107
|
|
72
|
-
|
73
|
-
if Litestream.configuration
|
74
|
-
ENV["LITESTREAM_REPLICA_BUCKET"] ||= Litestream.configuration.replica_bucket
|
75
|
-
ENV["LITESTREAM_ACCESS_KEY_ID"] ||= Litestream.configuration.replica_key_id
|
76
|
-
ENV["LITESTREAM_SECRET_ACCESS_KEY"] ||= Litestream.configuration.replica_access_key
|
108
|
+
execute("generations", argv, database, async: async)
|
77
109
|
end
|
78
110
|
|
79
|
-
|
80
|
-
"
|
81
|
-
}.merge(argv).to_a.flatten.compact
|
82
|
-
|
83
|
-
command = [executable, "replicate", *args]
|
84
|
-
puts command.inspect
|
111
|
+
def snapshots(database, async: true, **argv)
|
112
|
+
raise DatabaseRequiredException, "database argument is required for snapshots command, e.g. litestream:snapshots -- --database=path/to/database.sqlite" if database.nil?
|
85
113
|
|
86
|
-
|
87
|
-
# The forked process executes litestream and replaces itself.
|
88
|
-
if fork.nil?
|
89
|
-
exec(*command)
|
114
|
+
execute("snapshots", argv, database, async: async)
|
90
115
|
end
|
91
|
-
end
|
92
116
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
+
}
|
98
135
|
end
|
99
136
|
|
100
|
-
|
101
|
-
ext = File.extname(file)
|
102
|
-
base = File.basename(file, ext)
|
103
|
-
now = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
137
|
+
private
|
104
138
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
139
|
+
def execute(command, argv = {}, database = nil, async: true)
|
140
|
+
if Litestream.configuration
|
141
|
+
ENV["LITESTREAM_REPLICA_BUCKET"] ||= Litestream.configuration.replica_bucket
|
142
|
+
ENV["LITESTREAM_ACCESS_KEY_ID"] ||= Litestream.configuration.replica_key_id
|
143
|
+
ENV["LITESTREAM_SECRET_ACCESS_KEY"] ||= Litestream.configuration.replica_access_key
|
144
|
+
end
|
109
145
|
|
110
|
-
|
111
|
-
|
146
|
+
args = {
|
147
|
+
"--config" => Rails.root.join("config", "litestream.yml").to_s
|
148
|
+
}.merge(argv).to_a.flatten.compact
|
149
|
+
cmd = [executable, command, *args, database].compact
|
150
|
+
puts cmd.inspect if ENV["DEBUG"]
|
112
151
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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)
|
158
|
+
end
|
117
159
|
end
|
118
160
|
end
|
119
161
|
end
|
data/lib/litestream/version.rb
CHANGED
@@ -36,4 +36,65 @@ namespace :litestream do
|
|
36
36
|
|
37
37
|
Litestream::Commands.restore(options.delete("--database") || options.delete("-database"), options)
|
38
38
|
end
|
39
|
+
|
40
|
+
desc "List all databases and associated replicas in the config file, e.g. rake litestream:databases -- -no-expand-env"
|
41
|
+
task databases: :environment do
|
42
|
+
options = {}
|
43
|
+
if (separator_index = ARGV.index("--"))
|
44
|
+
ARGV.slice(separator_index + 1, ARGV.length)
|
45
|
+
.map { |pair| pair.split("=") }
|
46
|
+
.each { |opt| options[opt[0]] = opt[1] || nil }
|
47
|
+
end
|
48
|
+
|
49
|
+
Litestream::Commands.databases(options)
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "List all generations for a database or replica, e.g. rake litestream:generations -- -database=storage/production.sqlite3"
|
53
|
+
task generations: :environment do
|
54
|
+
options = {}
|
55
|
+
if (separator_index = ARGV.index("--"))
|
56
|
+
ARGV.slice(separator_index + 1, ARGV.length)
|
57
|
+
.map { |pair| pair.split("=") }
|
58
|
+
.each { |opt| options[opt[0]] = opt[1] || nil }
|
59
|
+
end
|
60
|
+
|
61
|
+
Litestream::Commands.generations(options.delete("--database") || options.delete("-database"), options)
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "List all snapshots for a database or replica, e.g. rake litestream:snapshots -- -database=storage/production.sqlite3"
|
65
|
+
task snapshots: :environment do
|
66
|
+
options = {}
|
67
|
+
if (separator_index = ARGV.index("--"))
|
68
|
+
ARGV.slice(separator_index + 1, ARGV.length)
|
69
|
+
.map { |pair| pair.split("=") }
|
70
|
+
.each { |opt| options[opt[0]] = opt[1] || nil }
|
71
|
+
end
|
72
|
+
|
73
|
+
Litestream::Commands.snapshots(options.delete("--database") || options.delete("-database"), options)
|
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
|
39
100
|
end
|