diff options
Diffstat (limited to 'app/models/namespace')
-rw-r--r-- | app/models/namespace/root_storage_size.rb | 31 | ||||
-rw-r--r-- | app/models/namespace/root_storage_statistics.rb | 26 | ||||
-rw-r--r-- | app/models/namespace/traversal_hierarchy.rb | 84 |
3 files changed, 107 insertions, 34 deletions
diff --git a/app/models/namespace/root_storage_size.rb b/app/models/namespace/root_storage_size.rb deleted file mode 100644 index d61917e468e..00000000000 --- a/app/models/namespace/root_storage_size.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -class Namespace::RootStorageSize - def initialize(root_namespace) - @root_namespace = root_namespace - end - - def above_size_limit? - return false if limit == 0 - - usage_ratio > 1 - end - - def usage_ratio - return 0 if limit == 0 - - current_size.to_f / limit.to_f - end - - def current_size - @current_size ||= root_namespace.root_storage_statistics&.storage_size - end - - def limit - @limit ||= Gitlab::CurrentSettings.namespace_storage_size_limit.megabytes - end - - private - - attr_reader :root_namespace -end diff --git a/app/models/namespace/root_storage_statistics.rb b/app/models/namespace/root_storage_statistics.rb index ae9b2f14343..2ad6ea59588 100644 --- a/app/models/namespace/root_storage_statistics.rb +++ b/app/models/namespace/root_storage_statistics.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true class Namespace::RootStorageStatistics < ApplicationRecord - STATISTICS_ATTRIBUTES = %w(storage_size repository_size wiki_size lfs_objects_size build_artifacts_size packages_size).freeze + SNIPPETS_SIZE_STAT_NAME = 'snippets_size'.freeze + STATISTICS_ATTRIBUTES = %W(storage_size repository_size wiki_size lfs_objects_size build_artifacts_size packages_size #{SNIPPETS_SIZE_STAT_NAME}).freeze self.primary_key = :namespace_id @@ -13,11 +14,15 @@ class Namespace::RootStorageStatistics < ApplicationRecord delegate :all_projects, to: :namespace def recalculate! - update!(attributes_from_project_statistics) + update!(merged_attributes) end private + def merged_attributes + attributes_from_project_statistics.merge!(attributes_from_personal_snippets) { |key, v1, v2| v1 + v2 } + end + def attributes_from_project_statistics from_project_statistics .take @@ -34,7 +39,22 @@ class Namespace::RootStorageStatistics < ApplicationRecord 'COALESCE(SUM(ps.wiki_size), 0) AS wiki_size', 'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size', 'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size', - 'COALESCE(SUM(ps.packages_size), 0) AS packages_size' + 'COALESCE(SUM(ps.packages_size), 0) AS packages_size', + "COALESCE(SUM(ps.snippets_size), 0) AS #{SNIPPETS_SIZE_STAT_NAME}" ) end + + def attributes_from_personal_snippets + # Return if the type of namespace does not belong to a user + return {} unless namespace.type.nil? + + from_personal_snippets.take.slice(SNIPPETS_SIZE_STAT_NAME) + end + + def from_personal_snippets + PersonalSnippet + .joins('INNER JOIN snippet_statistics s ON s.snippet_id = snippets.id') + .where(author: namespace.owner_id) + .select("COALESCE(SUM(s.repository_size), 0) AS #{SNIPPETS_SIZE_STAT_NAME}") + end end diff --git a/app/models/namespace/traversal_hierarchy.rb b/app/models/namespace/traversal_hierarchy.rb new file mode 100644 index 00000000000..cfb6cfdde74 --- /dev/null +++ b/app/models/namespace/traversal_hierarchy.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true +# +# A Namespace::TraversalHierarchy is the collection of namespaces that descend +# from a root Namespace as defined by the Namespace#traversal_ids attributes. +# +# This class provides operations to be performed on the hierarchy itself, +# rather than individual namespaces. +# +# This includes methods for synchronizing traversal_ids attributes to a correct +# state. We use recursive methods to determine the correct state so we don't +# have to depend on the integrity of the traversal_ids attribute values +# themselves. +# +class Namespace + class TraversalHierarchy + attr_accessor :root + + def self.for_namespace(namespace) + new(recursive_root_ancestor(namespace)) + end + + def initialize(root) + raise StandardError.new('Must specify a root node') if root.parent_id + + @root = root + end + + # Update all traversal_ids in the current namespace hierarchy. + def sync_traversal_ids! + # An issue in Rails since 2013 prevents this kind of join based update in + # ActiveRecord. https://github.com/rails/rails/issues/13496 + # Ideally it would be: + # `incorrect_traversal_ids.update_all('traversal_ids = cte.traversal_ids')` + sql = """ + UPDATE namespaces + SET traversal_ids = cte.traversal_ids + FROM (#{recursive_traversal_ids}) as cte + WHERE namespaces.id = cte.id + AND namespaces.traversal_ids <> cte.traversal_ids + """ + Namespace.connection.exec_query(sql) + end + + # Identify all incorrect traversal_ids in the current namespace hierarchy. + def incorrect_traversal_ids + Namespace + .joins("INNER JOIN (#{recursive_traversal_ids}) as cte ON namespaces.id = cte.id") + .where('namespaces.traversal_ids <> cte.traversal_ids') + end + + private + + # Determine traversal_ids for the namespace hierarchy using recursive methods. + # Generate a collection of [id, traversal_ids] rows. + # + # Note that the traversal_ids represent a calculated traversal path for the + # namespace and not the value stored within the traversal_ids attribute. + def recursive_traversal_ids + root_id = Integer(@root.id) + + """ + WITH RECURSIVE cte(id, traversal_ids, cycle) AS ( + VALUES(#{root_id}, ARRAY[#{root_id}], false) + UNION ALL + SELECT n.id, cte.traversal_ids || n.id, n.id = ANY(cte.traversal_ids) + FROM namespaces n, cte + WHERE n.parent_id = cte.id AND NOT cycle + ) + SELECT id, traversal_ids FROM cte + """ + end + + # This is essentially Namespace#root_ancestor which will soon be rewritten + # to use traversal_ids. We replicate here as a reliable way to find the + # root using recursive methods. + def self.recursive_root_ancestor(namespace) + Gitlab::ObjectHierarchy + .new(Namespace.where(id: namespace)) + .base_and_ancestors + .reorder(nil) + .find_by(parent_id: nil) + end + end +end |