lato_storage 3.0.10 → 3.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09bf05f957ba1bd4dfbf84522cfa922db04444b088e306f475d28305e2d2405e'
4
- data.tar.gz: 476467276b2221df684726a4a3f40f43f4fccd194f19d209c6e89774f0ae2b00
3
+ metadata.gz: a13ab3e65acf06d7619745a47cedc82fe3f6bda167cb2bb51262d91537711068
4
+ data.tar.gz: f945005d21633029cfeba26cc13a84faa4d68346cc2f50ff1f41c7f2e018f4dc
5
5
  SHA512:
6
- metadata.gz: b47c584c88f8ffd90e773f722d6ebdc5b12efb73a9534616cf63f6e9bd862e5f9e3f819fabd8cb8484b9a895244fe69f4175d6cf685fe27a1f492246dc26e9d5
7
- data.tar.gz: c1d6c4cf79b6d5ab9a4bed8aa99f9450639ee3f4140f0ed6e7a6a458e830263e5bcb0ed1c480312b3aac22a28b5f1774ab4c0df4d75008a5050b648fe155fac7
6
+ metadata.gz: 63d4288183f841424f5a3cf4ca0362ad0cdff35a3bb503abb0d81896a2b0003b722abd4b2cdfc4484c32d08c4f5b36cab411b392e3edb94dcab6c5335dcb5455
7
+ data.tar.gz: e45c65e4e75df74143495f043125ae36fb9b455764494f604b11281debaf20e1a6aa14e6568e7f6f3a325576c918a608e3120dc8521f84035f23cbef81109934
@@ -4,7 +4,7 @@ module LatoStorage
4
4
  DATA_CACHE_KEY = 'LatoStorage::PreloadDashboardDataJob/data'
5
5
 
6
6
  def perform(force_execution = false)
7
- return load_data unless LatoStorage.config.optimize_performances
7
+ return load_data_without_optimizations unless LatoStorage.config.optimize_performances
8
8
 
9
9
  data = Rails.cache.read(DATA_CACHE_KEY)
10
10
  return data || {} if Rails.cache.read(SEMAPHORE_CACHE_KEY)
@@ -15,7 +15,7 @@ module LatoStorage
15
15
  end
16
16
 
17
17
  Rails.cache.write(SEMAPHORE_CACHE_KEY, true, expires_in: 30.minutes)
18
- data = load_data
18
+ data = load_data_with_optimizations
19
19
  Rails.cache.write(DATA_CACHE_KEY, data, expires_in: 12.hours)
20
20
  return data
21
21
  rescue StandardError => e
@@ -27,17 +27,19 @@ module LatoStorage
27
27
 
28
28
  private
29
29
 
30
- def load_data
30
+ # Non-optimized data loading method
31
+ # NOTE: More precise but can be slow on large datasets
32
+ ######################################################################################################
33
+
34
+ def load_data_without_optimizations
31
35
  blobs_count = ActiveStorage::Blob.count
32
36
  attachments_count = ActiveStorage::Attachment.count
33
37
  variant_records_count = defined?(ActiveStorage::VariantRecord) ? ActiveStorage::VariantRecord.count : 0
34
- # deletable_blobs_count = ActiveStorage::Blob.unattached.where('active_storage_blobs.created_at < ?', 12.hours.ago).count
35
- deletable_blobs_count = ActiveStorage::Blob.joins("LEFT JOIN active_storage_attachments ON active_storage_attachments.blob_id = active_storage_blobs.id")
36
- .where("active_storage_attachments.blob_id IS NULL")
37
- .where("active_storage_blobs.created_at < ?", 12.hours.ago).count
38
+ deletable_blobs_count = ActiveStorage::Blob.left_joins(:attachments).where(active_storage_attachments: { blob_id: nil }).where('active_storage_blobs.created_at < ?', 12.hours.ago).count
38
39
  total_storage = ActiveStorage::Blob.sum(:byte_size)
39
40
  avg_storage = blobs_count.positive? ? total_storage / blobs_count : 0
40
41
  content_types = ActiveStorage::Blob.group(:content_type).count.sort_by { |_, count| -count }.first(5)
42
+
41
43
  largest_blobs = ActiveStorage::Blob.order(byte_size: :desc).limit(3).map do |blob|
42
44
  {
43
45
  id: blob.id,
@@ -62,5 +64,111 @@ module LatoStorage
62
64
  largest_blobs: largest_blobs
63
65
  }
64
66
  end
67
+
68
+ # Optimized data loading method
69
+ # NOTE: Less precise but faster on large datasets
70
+ ######################################################################################################
71
+
72
+ def load_data_with_optimizations
73
+ blobs_count = blobs_count_loader
74
+ attachments_count = attachments_count_loader
75
+ variant_records_count = variant_records_count_loader
76
+ deletable_blobs_count = deletable_blobs_count_loader
77
+ total_storage = total_storage_loader(blobs_count)
78
+ avg_storage = blobs_count.positive? ? total_storage / blobs_count : 0
79
+ content_types = content_types_loader
80
+
81
+ {
82
+ blobs_count: blobs_count,
83
+ attachments_count: attachments_count,
84
+ variant_records_count: variant_records_count,
85
+ deletable_blobs_count: deletable_blobs_count,
86
+ total_storage: total_storage,
87
+ avg_storage: avg_storage,
88
+ content_types: content_types,
89
+ largest_blobs: []
90
+ }
91
+ end
92
+
93
+ def blobs_count_loader
94
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?('mysql')
95
+ result = ActiveRecord::Base.connection.execute('SELECT TABLE_ROWS FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = \'active_storage_blobs\'')
96
+ result.first['TABLE_ROWS'].to_i
97
+ elsif ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
98
+ result = ActiveRecord::Base.connection.execute('SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname = \'active_storage_blobs\'')
99
+ result.first['estimate'].to_i
100
+ else
101
+ ActiveStorage::Blob.count
102
+ end
103
+ end
104
+
105
+ def attachments_count_loader
106
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?('mysql')
107
+ result = ActiveRecord::Base.connection.execute('SELECT TABLE_ROWS FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = \'active_storage_attachments\'')
108
+ result.first['TABLE_ROWS'].to_i
109
+ elsif ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
110
+ result = ActiveRecord::Base.connection.execute('SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname = \'active_storage_attachments\'')
111
+ result.first['estimate'].to_i
112
+ else
113
+ ActiveStorage::Attachment.count
114
+ end
115
+ end
116
+
117
+ def variant_records_count_loader
118
+ return 0 unless defined?(ActiveStorage::VariantRecord)
119
+
120
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?('mysql')
121
+ result = ActiveRecord::Base.connection.execute('SELECT TABLE_ROWS FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = \'active_storage_variant_records\'')
122
+ result.first['TABLE_ROWS'].to_i
123
+ elsif ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
124
+ result = ActiveRecord::Base.connection.execute('SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname = \'active_storage_variant_records\'')
125
+ result.first['estimate'].to_i
126
+ else
127
+ ActiveStorage::VariantRecord.count
128
+ end
129
+ end
130
+
131
+ def deletable_blobs_count_loader
132
+ count = 0
133
+ ActiveStorage::Blob.left_joins(:attachments).where(active_storage_attachments: { blob_id: nil }).where('active_storage_blobs.created_at < ?', 12.hours.ago).find_each(batch_size: 1000) do |blob|
134
+ count += 1
135
+ end
136
+ count
137
+ end
138
+
139
+ # Estimate the total storage by multiplying average size of a group sample by total blobs count
140
+ def total_storage_loader(blobs_count)
141
+ sample_size = 1000
142
+ total_size = 0
143
+ sampled_count = 0
144
+
145
+ ActiveStorage::Blob.order('RANDOM()').limit(sample_size).each do |blob|
146
+ total_size += blob.byte_size
147
+ sampled_count += 1
148
+ end
149
+
150
+ return 0 if sampled_count == 0
151
+
152
+ average_size = total_size / sampled_count
153
+ average_size * blobs_count
154
+ end
155
+
156
+ # Estimate content types by sampling blobs
157
+ def content_types_loader
158
+ content_type_counts = Hash.new(0)
159
+ sample_size = 5000
160
+ sampled_count = 0
161
+
162
+ ActiveStorage::Blob.order('RANDOM()').limit(sample_size).each do |blob|
163
+ content_type_counts[blob.content_type] += 1
164
+ sampled_count += 1
165
+ end
166
+
167
+ return [] if sampled_count == 0
168
+
169
+ # Scale counts to estimated total blobs count
170
+ content_type_counts.transform_values! { |count| (count.to_f / sampled_count) * blobs_count_loader }
171
+ content_type_counts.sort_by { |_, count| -count }.first(5)
172
+ end
65
173
  end
66
174
  end
@@ -114,36 +114,38 @@
114
114
  </div>
115
115
  </div>
116
116
 
117
- <div class="card">
118
- <div class="card-header">
119
- <h5 class="card-title mb-0">
120
- <%= I18n.t('lato_storage.largest_blobs') %>
121
- </h5>
122
- </div>
123
- <div class="card-body">
124
- <% if @data[:largest_blobs].any? %>
125
- <div class="list-group list-group-flush">
126
- <% @data[:largest_blobs].each do |blob| %>
127
- <a class="list-group-item list-group-item-action" href="<%= blob[:url] %>" target="_blank" rel="noopener noreferrer" download>
128
- <div class="d-flex justify-content-between align-items-center">
129
- <strong class="text-truncate"><%= blob[:filename] %></strong>
130
- <div class="d-flex align-items-center ms-3">
131
- <span class="badge bg-primary rounded-pill">
132
- <%= format_bytes blob[:byte_size] %>
133
- </span>
134
- <i class="bi bi-download ms-2"></i>
117
+ <% unless LatoStorage.config.optimize_performances %>
118
+ <div class="card">
119
+ <div class="card-header">
120
+ <h5 class="card-title mb-0">
121
+ <%= I18n.t('lato_storage.largest_blobs') %>
122
+ </h5>
123
+ </div>
124
+ <div class="card-body">
125
+ <% if @data[:largest_blobs].any? %>
126
+ <div class="list-group list-group-flush">
127
+ <% @data[:largest_blobs].each do |blob| %>
128
+ <a class="list-group-item list-group-item-action" href="<%= blob[:url] %>" target="_blank" rel="noopener noreferrer" download>
129
+ <div class="d-flex justify-content-between align-items-center">
130
+ <strong class="text-truncate"><%= blob[:filename] %></strong>
131
+ <div class="d-flex align-items-center ms-3">
132
+ <span class="badge bg-primary rounded-pill">
133
+ <%= format_bytes blob[:byte_size] %>
134
+ </span>
135
+ <i class="bi bi-download ms-2"></i>
136
+ </div>
135
137
  </div>
136
- </div>
137
- </a>
138
- <% end %>
139
- </div>
140
- <% else %>
141
- <div class="alert alert-info mb-0">
142
- <%= I18n.t('lato_storage.no_largest_blobs') %>
143
- </div>
144
- <% end %>
138
+ </a>
139
+ <% end %>
140
+ </div>
141
+ <% else %>
142
+ <div class="alert alert-info mb-0">
143
+ <%= I18n.t('lato_storage.no_largest_blobs') %>
144
+ </div>
145
+ <% end %>
146
+ </div>
145
147
  </div>
146
- </div>
148
+ <% end %>
147
149
  </div>
148
150
 
149
151
  </div>
@@ -17,4 +17,4 @@ en:
17
17
  blobs: "Blobs"
18
18
  attachments: "Attachments"
19
19
  variant_records: "Variant Records"
20
- optimize_performances_active: "The displayed data is cached to optimize performance. The shown values are therefore not updated in real-time and may not reflect the most recent changes."
20
+ optimize_performances_active: "The displayed data is cached to optimize performance. The shown values are therefore not updated in real-time and calculated with an estimation method that may differ from the actual values."
@@ -17,4 +17,4 @@ it:
17
17
  blobs: "Blob"
18
18
  attachments: "Allegati"
19
19
  variant_records: "Varianti"
20
- optimize_performances_active: "I dati visualizzati sono cachati per ottimizzare le prestazioni. I valori mostrati non sono quindi aggiornati in tempo reale e potrebbero non riflettere le modifiche più recenti."
20
+ optimize_performances_active: "I dati visualizzati sono cachati per ottimizzare le prestazioni. I valori mostrati non sono quindi aggiornati in tempo reale e calcolati con un metodo di stima che può differire dai valori effettivi."
@@ -1,3 +1,3 @@
1
1
  module LatoStorage
2
- VERSION = "3.0.10"
2
+ VERSION = "3.1.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lato_storage
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.10
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregorio Galante
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-29 00:00:00.000000000 Z
11
+ date: 2025-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails