legion-data 1.3.7 → 1.3.8
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 +8 -0
- data/lib/legion/data/archival/policy.rb +46 -0
- data/lib/legion/data/archival.rb +92 -0
- data/lib/legion/data/migrations/021_add_archive_tables.rb +57 -0
- data/lib/legion/data/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8b4deeb282c6d4c359d6fa8d46de2f18f3107746536227171ae4b47b5f80d7b0
|
|
4
|
+
data.tar.gz: 4f445caf46e5258694170c43ea429a3c6de4f25bd25f013617cf987d335077ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '06930c4164c05d58320e1e5a35b9be15b76e647ba06826b05df8bdd4db156283e232a2109f930e31f78cb892f4cd25324ac941810a377760ee6956577c64fa97'
|
|
7
|
+
data.tar.gz: 1cbfe73b30462c6b3807d15a494e48b7c4cb986e93b472a1357f722f5e13fb597ff2c70fae0f31911f1ae4711855d91a078c16fbf048835d5483aa849da0d252
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Legion::Data Changelog
|
|
2
2
|
|
|
3
|
+
## v1.3.8
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `Legion::Data::Archival`: hot/warm/cold archival pipeline for tasks and metering records
|
|
7
|
+
- `Legion::Data::Archival::Policy`: configurable retention policies (warm_after_days, cold_after_days, batch_size)
|
|
8
|
+
- Archive, restore, and cross-table search operations with dry-run support
|
|
9
|
+
- Migration 021: archive tables for tasks and metering_records
|
|
10
|
+
|
|
3
11
|
## v1.3.7
|
|
4
12
|
|
|
5
13
|
### Added
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Data
|
|
5
|
+
module Archival
|
|
6
|
+
class Policy
|
|
7
|
+
DEFAULTS = {
|
|
8
|
+
warm_after_days: 7,
|
|
9
|
+
cold_after_days: 90,
|
|
10
|
+
batch_size: 1000,
|
|
11
|
+
tables: %w[tasks metering_records].freeze
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
attr_reader :warm_after_days, :cold_after_days, :batch_size, :tables
|
|
15
|
+
|
|
16
|
+
def initialize(**opts)
|
|
17
|
+
config = DEFAULTS.merge(opts)
|
|
18
|
+
@warm_after_days = config[:warm_after_days]
|
|
19
|
+
@cold_after_days = config[:cold_after_days]
|
|
20
|
+
@batch_size = config[:batch_size]
|
|
21
|
+
@tables = config[:tables]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def warm_cutoff
|
|
25
|
+
Time.now - (warm_after_days * 86_400)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def cold_cutoff
|
|
29
|
+
Time.now - (cold_after_days * 86_400)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.from_settings
|
|
33
|
+
return new unless defined?(Legion::Settings)
|
|
34
|
+
|
|
35
|
+
data_settings = Legion::Settings[:data]
|
|
36
|
+
archival = data_settings.is_a?(Hash) ? data_settings[:archival] : nil
|
|
37
|
+
return new unless archival.is_a?(Hash)
|
|
38
|
+
|
|
39
|
+
new(**archival.slice(:warm_after_days, :cold_after_days, :batch_size, :tables))
|
|
40
|
+
rescue StandardError
|
|
41
|
+
new
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'archival/policy'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Data
|
|
7
|
+
module Archival
|
|
8
|
+
ARCHIVE_TABLE_MAP = {
|
|
9
|
+
tasks: :tasks_archive,
|
|
10
|
+
metering_records: :metering_records_archive
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def archive!(policy: Policy.new, dry_run: false)
|
|
15
|
+
results = {}
|
|
16
|
+
policy.tables.each do |table_name|
|
|
17
|
+
table = table_name.to_sym
|
|
18
|
+
archive_table = ARCHIVE_TABLE_MAP[table]
|
|
19
|
+
next unless archive_table && db_ready?(table) && db_ready?(archive_table)
|
|
20
|
+
|
|
21
|
+
count = archive_table!(
|
|
22
|
+
source: table, destination: archive_table,
|
|
23
|
+
cutoff: policy.warm_cutoff, batch_size: policy.batch_size, dry_run: dry_run
|
|
24
|
+
)
|
|
25
|
+
results[table] = count
|
|
26
|
+
end
|
|
27
|
+
results
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def restore(table:, ids:)
|
|
31
|
+
source_table = table.to_sym
|
|
32
|
+
archive_table = ARCHIVE_TABLE_MAP[source_table]
|
|
33
|
+
return 0 unless archive_table && db_ready?(archive_table)
|
|
34
|
+
|
|
35
|
+
conn = Legion::Data.connection
|
|
36
|
+
restored = 0
|
|
37
|
+
conn.transaction do
|
|
38
|
+
conn[archive_table].where(original_id: ids).each do |row|
|
|
39
|
+
restore_row = row.except(:id, :archived_at, :original_id, :original_created_at, :original_updated_at)
|
|
40
|
+
restore_row[:id] = row[:original_id]
|
|
41
|
+
restore_row[:created_at] = row[:original_created_at]
|
|
42
|
+
restore_row[:updated_at] = row[:original_updated_at]
|
|
43
|
+
conn[source_table].insert(restore_row)
|
|
44
|
+
restored += 1
|
|
45
|
+
end
|
|
46
|
+
conn[archive_table].where(original_id: ids).delete
|
|
47
|
+
end
|
|
48
|
+
restored
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def search(table:, where: {})
|
|
52
|
+
source_table = table.to_sym
|
|
53
|
+
archive_table = ARCHIVE_TABLE_MAP[source_table]
|
|
54
|
+
return [] unless db_ready?(source_table)
|
|
55
|
+
|
|
56
|
+
conn = Legion::Data.connection
|
|
57
|
+
hot = conn[source_table].where(where).all
|
|
58
|
+
warm = db_ready?(archive_table) ? conn[archive_table].where(where).all : []
|
|
59
|
+
hot + warm
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def archive_table!(source:, destination:, cutoff:, batch_size:, dry_run:)
|
|
65
|
+
conn = Legion::Data.connection
|
|
66
|
+
candidates = conn[source].where { created_at < cutoff }.limit(batch_size)
|
|
67
|
+
count = candidates.count
|
|
68
|
+
return count if dry_run || count.zero?
|
|
69
|
+
|
|
70
|
+
conn.transaction do
|
|
71
|
+
candidates.each do |row|
|
|
72
|
+
archive_row = row.dup
|
|
73
|
+
archive_row[:original_id] = archive_row.delete(:id)
|
|
74
|
+
archive_row[:original_created_at] = archive_row.delete(:created_at)
|
|
75
|
+
archive_row[:original_updated_at] = archive_row.delete(:updated_at)
|
|
76
|
+
archive_row[:archived_at] = Time.now
|
|
77
|
+
conn[destination].insert(archive_row)
|
|
78
|
+
end
|
|
79
|
+
conn[source].where(id: candidates.select(:id)).delete
|
|
80
|
+
end
|
|
81
|
+
count
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def db_ready?(table)
|
|
85
|
+
defined?(Legion::Data) && Legion::Data.connection&.table_exists?(table)
|
|
86
|
+
rescue StandardError
|
|
87
|
+
false
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
unless table_exists?(:tasks_archive)
|
|
6
|
+
create_table(:tasks_archive) do
|
|
7
|
+
primary_key :id
|
|
8
|
+
Integer :original_id, null: false
|
|
9
|
+
String :function_name
|
|
10
|
+
String :status
|
|
11
|
+
String :runner_class
|
|
12
|
+
column :args, :text
|
|
13
|
+
column :result, :text
|
|
14
|
+
String :queue
|
|
15
|
+
Integer :relationship_id
|
|
16
|
+
String :chain_id
|
|
17
|
+
DateTime :original_created_at
|
|
18
|
+
DateTime :original_updated_at
|
|
19
|
+
DateTime :archived_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
20
|
+
index :original_id
|
|
21
|
+
index :chain_id
|
|
22
|
+
index :archived_at
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
unless table_exists?(:metering_records_archive)
|
|
27
|
+
create_table(:metering_records_archive) do
|
|
28
|
+
primary_key :id
|
|
29
|
+
Integer :original_id, null: false
|
|
30
|
+
String :worker_id
|
|
31
|
+
String :event_type
|
|
32
|
+
String :extension
|
|
33
|
+
String :runner_function
|
|
34
|
+
String :status
|
|
35
|
+
Integer :tokens_in
|
|
36
|
+
Integer :tokens_out
|
|
37
|
+
Float :cost_usd
|
|
38
|
+
Integer :wall_clock_ms
|
|
39
|
+
Integer :cpu_time_ms
|
|
40
|
+
Integer :external_api_calls
|
|
41
|
+
String :model
|
|
42
|
+
String :tenant_id
|
|
43
|
+
DateTime :original_created_at
|
|
44
|
+
DateTime :archived_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
45
|
+
index :original_id
|
|
46
|
+
index :worker_id
|
|
47
|
+
index :tenant_id
|
|
48
|
+
index :archived_at
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
down do
|
|
54
|
+
drop_table(:metering_records_archive) if table_exists?(:metering_records_archive)
|
|
55
|
+
drop_table(:tasks_archive) if table_exists?(:tasks_archive)
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/legion/data/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legion-data
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -86,6 +86,8 @@ files:
|
|
|
86
86
|
- exe/legionio_migrate
|
|
87
87
|
- legion-data.gemspec
|
|
88
88
|
- lib/legion/data.rb
|
|
89
|
+
- lib/legion/data/archival.rb
|
|
90
|
+
- lib/legion/data/archival/policy.rb
|
|
89
91
|
- lib/legion/data/connection.rb
|
|
90
92
|
- lib/legion/data/encryption/cipher.rb
|
|
91
93
|
- lib/legion/data/encryption/key_provider.rb
|
|
@@ -114,6 +116,7 @@ files:
|
|
|
114
116
|
- lib/legion/data/migrations/018_add_governance_events.rb
|
|
115
117
|
- lib/legion/data/migrations/019_add_audit_hash_chain.rb
|
|
116
118
|
- lib/legion/data/migrations/020_add_webhooks.rb
|
|
119
|
+
- lib/legion/data/migrations/021_add_archive_tables.rb
|
|
117
120
|
- lib/legion/data/model.rb
|
|
118
121
|
- lib/legion/data/models/apollo_access_log.rb
|
|
119
122
|
- lib/legion/data/models/apollo_entry.rb
|