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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d10f18bacc47360ad1829628bffc116ecbeea74b0b49359f1cee7fba0a7e9d44
4
- data.tar.gz: e13f4acfa8992e3a00129322fef794b80877f723e0c59210016f64fee57c5cae
3
+ metadata.gz: 8b4deeb282c6d4c359d6fa8d46de2f18f3107746536227171ae4b47b5f80d7b0
4
+ data.tar.gz: 4f445caf46e5258694170c43ea429a3c6de4f25bd25f013617cf987d335077ad
5
5
  SHA512:
6
- metadata.gz: 0e61649208294598ecdf19ffca469cba3db7b77d31e0e0a065e4c82e47949e4ea87aa291c5fae18970318e0c27ac1dfcd7eb53143bd7b5acc279eb84c0f2b231
7
- data.tar.gz: 7692efc8d123ad178c3f3a66531723518e0fac2cb5999d01833bfa472ac8eb5bee06289dd193886fe66c369f3d49ebff689e68267eff4ab030ce746377a6c9a3
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Data
5
- VERSION = '1.3.7'
5
+ VERSION = '1.3.8'
6
6
  end
7
7
  end
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.7
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