hekenga 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +16 -0
- data/docker-compose.yml +24 -5
- data/exe/hekenga +14 -0
- data/lib/hekenga/document_task.rb +2 -1
- data/lib/hekenga/document_task_executor.rb +2 -0
- data/lib/hekenga/dsl/document_task.rb +4 -0
- data/lib/hekenga/failure_report.rb +60 -0
- data/lib/hekenga/master_process.rb +22 -5
- data/lib/hekenga/version.rb +1 -1
- data/lib/hekenga.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d81f0def52813a113005c985703355b9d207a9fb91acae8e4948d01dc43f7b33
|
|
4
|
+
data.tar.gz: f462545e892ababf6a251dd5d366ef59cfc0f799b1ba3456102ee0483cab652d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 62e018a5ad375a280f641847c3c85c18b190ff7ef83d558a53210b062a80eecb183168af77baac2d2a7f2b7d5d16438b2f6057faaaa7a52e410f952a46673d69
|
|
7
|
+
data.tar.gz: 10b34cb83cf1f8d643c3cd11f06389ae6911d84c6afa7ff7901a681a735a83bb24108178a162ba0005ef7ed1992c54448ac3eff4479498533505a7ab65a0a71e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.2.0
|
|
4
|
+
|
|
5
|
+
- Added a `hekenga watch <path_or_pkey>` CLI command to attach to a running
|
|
6
|
+
migration and report its status periodically. Accepts an `--interval`
|
|
7
|
+
option (defaults to `Hekenga.config.report_sleep`).
|
|
8
|
+
- Added a `hekenga failures <path_or_pkey>` CLI command to print all failed and
|
|
9
|
+
invalid document IDs across a migration's document tasks.
|
|
10
|
+
- Document tasks now support a `skip_validation!` option to write documents
|
|
11
|
+
without running `.valid?`.
|
|
12
|
+
|
|
3
13
|
## v2.1.0
|
|
4
14
|
|
|
5
15
|
- `per_document` task `scope` will no longer let you specify `.only` or
|
data/README.md
CHANGED
|
@@ -43,6 +43,8 @@ $ hekenga run! <path_or_pkey> # Run a specific migration
|
|
|
43
43
|
$ hekenga run! <path_or_pkey> --test # Dry run (no writes persisted)
|
|
44
44
|
$ hekenga run! <path_or_pkey> --clear # Clear logs before running
|
|
45
45
|
$ hekenga recover! <path_or_pkey> # Re-process failed/invalid records
|
|
46
|
+
$ hekenga watch <path_or_pkey> # Attach to a running migration, report status periodically
|
|
47
|
+
$ hekenga failures <path_or_pkey> # Print all failed/invalid document IDs for a migration
|
|
46
48
|
$ hekenga cancel # Cancel all active migrations
|
|
47
49
|
$ hekenga skip <path_or_pkey> # Mark a migration as skipped
|
|
48
50
|
$ hekenga clear! <path_or_pkey> # Remove all logs/failures for a migration
|
|
@@ -118,6 +120,7 @@ per_document "Process records" do
|
|
|
118
120
|
timeless! # Don't update Mongoid timestamps
|
|
119
121
|
always_write! # Write even if the document didn't change
|
|
120
122
|
skip_prepare! # Skip Mongoid callbacks on load
|
|
123
|
+
skip_validation! # Write documents without running Mongoid validations
|
|
121
124
|
use_transaction! # Wrap each batch in a MongoDB transaction
|
|
122
125
|
batch_size 50 # Override migration-level batch size
|
|
123
126
|
write_strategy :update # :update (default) or :delete_then_insert
|
|
@@ -143,12 +146,25 @@ Or via the CLI:
|
|
|
143
146
|
|
|
144
147
|
$ hekenga run! <path_or_pkey> --test
|
|
145
148
|
|
|
149
|
+
### Monitoring
|
|
150
|
+
|
|
151
|
+
Attach to a migration that's already running (for example one launched in another process or via a background job) and print its status on a fixed interval:
|
|
152
|
+
|
|
153
|
+
$ hekenga watch <path_or_pkey>
|
|
154
|
+
$ hekenga watch <path_or_pkey> --interval 5 # report every 5 seconds
|
|
155
|
+
|
|
156
|
+
The reporting interval defaults to `Hekenga.config.report_sleep`.
|
|
157
|
+
|
|
146
158
|
### Recovery
|
|
147
159
|
|
|
148
160
|
When a migration fails (due to errors, invalid records, or write failures), Hekenga logs the failures and marks the migration as failed. You can re-process only the failed records:
|
|
149
161
|
|
|
150
162
|
$ hekenga recover! <path_or_pkey>
|
|
151
163
|
|
|
164
|
+
To inspect exactly which documents failed or were invalid across all document tasks in a migration:
|
|
165
|
+
|
|
166
|
+
$ hekenga failures <path_or_pkey>
|
|
167
|
+
|
|
152
168
|
## Development
|
|
153
169
|
|
|
154
170
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/docker-compose.yml
CHANGED
|
@@ -9,22 +9,41 @@ networks:
|
|
|
9
9
|
services:
|
|
10
10
|
mongo:
|
|
11
11
|
image: mongo:6
|
|
12
|
-
command: ["--replSet", "rs0", "--
|
|
12
|
+
command: ["--replSet", "rs0", "--bind_ip_all"]
|
|
13
13
|
volumes:
|
|
14
14
|
- mongo:/data/db
|
|
15
15
|
ports:
|
|
16
16
|
- 27017:27017
|
|
17
17
|
networks:
|
|
18
18
|
- hekenga-net
|
|
19
|
+
healthcheck:
|
|
20
|
+
# mongo:6 ships mongosh (the legacy `mongo` shell was removed in 6.0).
|
|
21
|
+
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping')"]
|
|
22
|
+
interval: 5s
|
|
23
|
+
timeout: 5s
|
|
24
|
+
retries: 12
|
|
19
25
|
|
|
20
26
|
mongosetup:
|
|
21
27
|
image: mongo:6
|
|
22
28
|
depends_on:
|
|
23
|
-
|
|
29
|
+
mongo:
|
|
30
|
+
condition: service_healthy
|
|
24
31
|
restart: "no"
|
|
25
32
|
entrypoint:
|
|
26
|
-
-
|
|
27
|
-
- "
|
|
28
|
-
- "
|
|
33
|
+
- mongosh
|
|
34
|
+
- "--quiet"
|
|
35
|
+
- "--host"
|
|
36
|
+
- "mongo:27017"
|
|
37
|
+
- "--eval"
|
|
38
|
+
# Idempotent: initiate the single-member replica set only if it hasn't
|
|
39
|
+
# been configured yet, so re-running compose doesn't error. The member
|
|
40
|
+
# advertises as localhost:27017 so the driver on the host can reach it
|
|
41
|
+
# after replica-set topology discovery (required for transactions).
|
|
42
|
+
- >
|
|
43
|
+
try { rs.status(); print('replica set already initialised'); }
|
|
44
|
+
catch (e) {
|
|
45
|
+
rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'localhost:27017'}]});
|
|
46
|
+
print('replica set initiated');
|
|
47
|
+
}
|
|
29
48
|
networks:
|
|
30
49
|
- hekenga-net
|
data/exe/hekenga
CHANGED
|
@@ -71,6 +71,20 @@ class HekengaCLI < Thor
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
desc "watch PATH_OR_PKEY", "Attach to a running migration and report its status periodically."
|
|
75
|
+
option :interval, type: :numeric, desc: "Seconds between status reports (default: Hekenga.config.report_sleep)."
|
|
76
|
+
def watch(path_or_pkey)
|
|
77
|
+
migration = load_migration(path_or_pkey)
|
|
78
|
+
interval = options[:interval] || Hekenga.config.report_sleep
|
|
79
|
+
Hekenga::MasterProcess.new(migration).watch!(interval: interval)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
desc "failures PATH_OR_PKEY", "Print all failed and invalid document IDs for a migration."
|
|
83
|
+
def failures(path_or_pkey)
|
|
84
|
+
migration = load_migration(path_or_pkey)
|
|
85
|
+
Hekenga::FailureReport.new(migration).print!
|
|
86
|
+
end
|
|
87
|
+
|
|
74
88
|
desc "cancel", "Cancel all active migrations."
|
|
75
89
|
def cancel
|
|
76
90
|
Hekenga::Log.where(done: false).set(cancel: true)
|
|
@@ -5,7 +5,7 @@ module Hekenga
|
|
|
5
5
|
attr_reader :ups, :downs, :setups, :filters, :after_callbacks
|
|
6
6
|
attr_accessor :parallel, :scope, :timeless, :batch_size, :cursor_timeout
|
|
7
7
|
attr_accessor :description, :invalid_strategy, :skip_prepare, :write_strategy
|
|
8
|
-
attr_accessor :always_write, :use_transaction
|
|
8
|
+
attr_accessor :always_write, :use_transaction, :skip_validation
|
|
9
9
|
|
|
10
10
|
def initialize
|
|
11
11
|
@ups = []
|
|
@@ -16,6 +16,7 @@ module Hekenga
|
|
|
16
16
|
@invalid_strategy = :continue
|
|
17
17
|
@write_strategy = :update
|
|
18
18
|
@skip_prepare = false
|
|
19
|
+
@skip_validation = false
|
|
19
20
|
@batch_size = nil
|
|
20
21
|
@always_write = false
|
|
21
22
|
@use_transaction = false
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Hekenga
|
|
2
|
+
class FailureReport
|
|
3
|
+
def initialize(migration)
|
|
4
|
+
@migration = migration
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Per-task summaries for document tasks that have any failed or invalid
|
|
8
|
+
# document IDs.
|
|
9
|
+
# => [{ idx:, description:, failed_ids: [...], invalid_ids: [...] }, ...]
|
|
10
|
+
def tasks
|
|
11
|
+
@migration.tasks.each.with_index.filter_map do |task, idx|
|
|
12
|
+
next unless task.is_a?(Hekenga::DocumentTask)
|
|
13
|
+
|
|
14
|
+
records = @migration.task_records(idx).any_of(
|
|
15
|
+
{ :failed_ids.ne => [] },
|
|
16
|
+
{ :invalid_ids.ne => [] }
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
failed_ids = []
|
|
20
|
+
invalid_ids = []
|
|
21
|
+
records.pluck(:failed_ids, :invalid_ids).each do |failed, invalid|
|
|
22
|
+
failed_ids.concat(failed) if failed
|
|
23
|
+
invalid_ids.concat(invalid) if invalid
|
|
24
|
+
end
|
|
25
|
+
next if failed_ids.empty? && invalid_ids.empty?
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
idx: idx,
|
|
29
|
+
description: task.description,
|
|
30
|
+
failed_ids: failed_ids,
|
|
31
|
+
invalid_ids: invalid_ids
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def print!
|
|
37
|
+
Hekenga.log("Failures for #{@migration.to_key}")
|
|
38
|
+
|
|
39
|
+
summaries = tasks
|
|
40
|
+
if summaries.empty?
|
|
41
|
+
Hekenga.log(" No failed or invalid documents.")
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
failed_ids = summaries.flat_map { |summary| summary[:failed_ids] }
|
|
46
|
+
invalid_ids = summaries.flat_map { |summary| summary[:invalid_ids] }
|
|
47
|
+
|
|
48
|
+
print_ids("Failed", failed_ids)
|
|
49
|
+
print_ids("Invalid", invalid_ids)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def print_ids(label, ids)
|
|
55
|
+
return if ids.empty?
|
|
56
|
+
|
|
57
|
+
Hekenga.log(" #{label} (#{ids.length}): #{ids.map { |id| "\"#{id}\"" }.join(", ")}")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -33,18 +33,35 @@ module Hekenga
|
|
|
33
33
|
true
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
def watch!(interval: Hekenga.config.report_sleep)
|
|
37
|
+
if Hekenga.status(@migration) == :naught
|
|
38
|
+
Hekenga.log "Migration #{@migration.to_key} has not started yet."
|
|
39
|
+
return false
|
|
40
|
+
end
|
|
41
|
+
Hekenga.log "Watching migration #{@migration.to_key}: #{@migration.description}"
|
|
42
|
+
@migration.tasks.each.with_index do |task, idx|
|
|
43
|
+
report_while_active(task, idx, interval: interval)
|
|
44
|
+
rescue Hekenga::TaskFailedError
|
|
45
|
+
return false
|
|
46
|
+
end
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
|
|
36
50
|
private
|
|
37
51
|
|
|
38
|
-
def report_while_active(task, idx)
|
|
39
|
-
# Wait for the log to be generated
|
|
52
|
+
def report_while_active(task, idx, interval: Hekenga.config.report_sleep)
|
|
53
|
+
# Wait for the log to be generated. When watching an out-of-process
|
|
54
|
+
# migration there is no @active_thread guaranteeing it, so bail out if
|
|
55
|
+
# the migration finished without ever reaching this task.
|
|
40
56
|
until (@migration.log(idx) rescue nil)
|
|
57
|
+
return if %i[complete skipped].include?(Hekenga.status(@migration))
|
|
41
58
|
sleep 1
|
|
42
59
|
end
|
|
43
|
-
# Periodically report on
|
|
60
|
+
# Periodically report on progress
|
|
44
61
|
until @migration.log(idx).reload.done
|
|
45
|
-
@active_thread
|
|
62
|
+
@active_thread&.join
|
|
46
63
|
report_status(task, idx)
|
|
47
|
-
sleep
|
|
64
|
+
sleep interval
|
|
48
65
|
end
|
|
49
66
|
report_status(task, idx) if task.is_a?(Hekenga::DocumentTask)
|
|
50
67
|
report_result(task, idx)
|
data/lib/hekenga/version.rb
CHANGED
data/lib/hekenga.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hekenga
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tapio Saarinen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -175,6 +175,7 @@ files:
|
|
|
175
175
|
- lib/hekenga/failure/error.rb
|
|
176
176
|
- lib/hekenga/failure/validation.rb
|
|
177
177
|
- lib/hekenga/failure/write.rb
|
|
178
|
+
- lib/hekenga/failure_report.rb
|
|
178
179
|
- lib/hekenga/id_iterator.rb
|
|
179
180
|
- lib/hekenga/invalid.rb
|
|
180
181
|
- lib/hekenga/irreversible.rb
|