lato_storage 3.0.11 → 3.1.1
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f60e0848acdfd5b0de5f20980534819168517e704d057f733b7545de563ddf8
|
|
4
|
+
data.tar.gz: 58d3cd2919869438494d2eb985cfde1639a3495870cdbf64caee275cadf60f19
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4213b787cc1f1d84ebceeeef5f9cb5f8ed540ad67bba1883fd5cbd7c657d024bc5004f29ffb72a7f04666eed126d574d8f738f7caf1169cb5e3fb32773cbd942
|
|
7
|
+
data.tar.gz: 011650c33d774e48efcb857c4947dbbefd99ef52bdecf4bb4874fa108777ff99f794542ef5528696e9e55255c14c9fe5bd275df6db26292120019a808dbf8c65
|
|
@@ -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
|
|
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 =
|
|
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,14 +27,19 @@ module LatoStorage
|
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
|
-
|
|
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 =
|
|
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
|
|
35
39
|
total_storage = ActiveStorage::Blob.sum(:byte_size)
|
|
36
40
|
avg_storage = blobs_count.positive? ? total_storage / blobs_count : 0
|
|
37
41
|
content_types = ActiveStorage::Blob.group(:content_type).count.sort_by { |_, count| -count }.first(5)
|
|
42
|
+
|
|
38
43
|
largest_blobs = ActiveStorage::Blob.order(byte_size: :desc).limit(3).map do |blob|
|
|
39
44
|
{
|
|
40
45
|
id: blob.id,
|
|
@@ -60,6 +65,69 @@ module LatoStorage
|
|
|
60
65
|
}
|
|
61
66
|
end
|
|
62
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
|
+
|
|
63
131
|
def deletable_blobs_count_loader
|
|
64
132
|
count = 0
|
|
65
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|
|
|
@@ -67,5 +135,40 @@ module LatoStorage
|
|
|
67
135
|
end
|
|
68
136
|
count
|
|
69
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
|
|
70
173
|
end
|
|
71
174
|
end
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
</h5>
|
|
60
60
|
</div>
|
|
61
61
|
<div class="card-body">
|
|
62
|
-
<% if @data[:content_types]
|
|
62
|
+
<% if @data[:content_types]&.any? %>
|
|
63
63
|
<% total = @data[:content_types].sum { |_, count| count } %>
|
|
64
64
|
<ul class="list-group list-group-flush">
|
|
65
65
|
<% @data[:content_types].each do |content_type, count| %>
|
|
@@ -114,36 +114,38 @@
|
|
|
114
114
|
</div>
|
|
115
115
|
</div>
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
<div class="card
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
</
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
148
|
+
<% end %>
|
|
147
149
|
</div>
|
|
148
150
|
|
|
149
151
|
</div>
|
data/config/locales/en.yml
CHANGED
|
@@ -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
|
|
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."
|
data/config/locales/it.yml
CHANGED
|
@@ -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
|
|
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."
|
data/lib/lato_storage/version.rb
CHANGED
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.
|
|
4
|
+
version: 3.1.1
|
|
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-
|
|
11
|
+
date: 2025-11-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|