네임스페이스
네임스페이스는 프로젝트 및 관련 리소스를 위한 컨테이너입니다. Namespace
는 Group
, ProjectNamespace
, 및 UserNamespace
의 하위 클래스를 통해 인스턴스화됩니다.
User
는 하나의 UserNamespace
를 가지고 있으며, 여러 Namespaces
의 구성원이 될 수 있습니다.
Group
은 재귀적인 계층적 관계에서 존재합니다. Groups
는 하나의 Project
를 부모로 가진 여러 ProjectNamespaces
를 가지고 있습니다.
네임스페이스 쿼리
네임스페이스 계층을 쿼리하기 위해 제공되는 방법이 있습니다. 이 방법들은 표준 Rails ActiveRecord::Relation
객체를 생성합니다.
방법들은 두 개의 유사한 절반으로 나눌 수 있습니다. 한 세트의 방법은 Namespace
객체에서 작동하고, 다른 세트는 조합 가능한 Namespace 스코프로 작동합니다.
본질적으로, 객체 방법은 단일 Namespace
계층 내에서 작동하는 반면, 스코프는 계층을 가로지를 수 있습니다.
다음은 Namespace
계층을 쿼리하기 위한 비포괄적인 방법 목록입니다.
루트 네임스페이스
루트는 계층의 가장 위에 있는 Namespace
입니다. 루트는 nil
parent_id
를 가집니다.
Namespace.where(...).roots
namespace_object.root_ancestor
자식 네임스페이스
네임스페이스의 자식은 그 자식, 자식의 자식 등입니다.
self_and_descendants
를 통해 자신과 자식들을 반환할 수 있습니다.
Namespace.where(...).self_and_descendants
namespace_object.self_and_descendants
자신을 제외한 자식들만 반환할 수 있습니다:
Namespace.where(...).self_and_descendants(include_self: false)
namespace_object.descendants
.descendants
라는 스코프 방법을 이름 지을 수 없었던 이유는 동일한 이름의 Object
메소드를 덮어쓰기 때문입니다.
전체 레코드 대신 자식 ID를 반환하는 것이 더 효율적일 수 있습니다:
Namespace.where(...).self_and_descendant_ids
Namespace.where(...).self_and_descendant_ids(include_self: false)
namespace_object.self_and_descendant_ids
namespace_object.descendant_ids
조상 네임스페이스
네임스페이스의 조상은 부모, 부모의 부모 등입니다.
self_and_ancestors
를 통해 자신과 조상들을 반환할 수 있습니다.
Namespace.where(...).self_and_ancestors
namespace_object.self_and_ancestors
자신을 제외한 조상들만 반환할 수 있습니다:
Namespace.where(...).self_and_ancestors(include_self: false)
namespace_object.ancestors
.ancestors
라는 스코프 방법을 이름 지을 수 없었던 이유는 동일한 이름의 Module
메소드를 덮어쓰기 때문입니다.
전체 레코드 대신 조상 ID를 반환하는 것이 더 효율적일 수 있습니다:
Namespace.where(...).self_and_ancstor_ids
Namespace.where(...).self_and_ancestor_ids(include_self: false)
namespace_object.self_and_ancestor_ids
namespace_object.ancestor_ids
계층 구조
네임스페이스 계층 구조는 Namespace
, 그 조상 및 자손입니다.
네임스페이스 계층 구조를 쿼리할 수 있습니다:
Namespace.where(...).self_and_hierarchy
namespace_object.self_and_hierarchy
재귀 쿼리
위의 쿼리는 namespaces.traversal_ids
열을 사용하여 표준 SQL 쿼리를 실행하기 때문에 선형 쿼리로 알려져 있습니다.
필요한 경우 레거시 재귀 쿼리 세트도 접근할 수 있습니다:
Namespace.where(...).recursive_self_and_descendants
Namespace.where(...).recursive_self_and_descendants(include_self: false)
Namespace.where(...).recursive_self_and_descendant_ids
Namespace.where(...).recursive_self_and_descendant_ids(include_self: false)
Namespace.where(...).recursive_self_and_ancestors
Namespace.where(...).recursive_self_and_ancestors(include_self: false)
Namespace.where(...).recursive_self_and_ancstor_ids
Namespace.where(...).recursive_self_and_ancestor_ids(include_self: false)
Namespace.where(...).recursive_self_and_hierarchy
namespace_object.recursive_root_ancestor
namespace_object.recursive_self_and_descendants
namespace_object.recursive_descendants
namespace_object.recursive_self_and_descendant_ids
namespace_object.recursive_descendant_ids
namespace_object.recursive_self_and_ancestors
namespace_object.recursive_ancestors
namespace_object.recursive_self_and_ancestor_ids
namespace_object.recursive_ancestor_ids
namespace_object.recursive_self_and_hierarchy
트라이 데이터 구조를 사용한 검색
Namespaces::Traversal::TrieNode
는 네임스페이스 세트를 위한 namespaces.traveral_ids
계층 내에서 효율적으로 검색하기 위해 트라이 데이터 구조를 구현합니다.
traversal_ids = Namespace.where(...).map(&:traversal_ids)
# [9970, 123] 및 [9970, 456] 포함
trie = Namespaces::Traversal::TrieNode.build(traversal_ids)
trie.prefix_search([9970]) # [[9970, 123], [9970, 456]] 반환
trie.covered?([9970]) # false 반환
trie.covered?([9970, 123]) # true 반환
trie.covered?([9970, 123, 789]) # true 반환
네임스페이스 쿼리 구현
선형 쿼리는 namespaces.traversal_ids
배열 열을 사용하여 실행됩니다. 각 배열은 루트 Namespace
에서 현재 Namespace
까지의 Namespace
ID의 정렬된 세트를 나타냅니다.
시나리오를 고려해 보세요:
Namespace
A.A.B
의 traversal_ids
는 [A, A.A, A.A.B]
입니다.
traversal_ids
에는 이 영역에서 작업할 때 유용한 몇 가지 속성이 있습니다:
- 모든
Namespace
의 루트는traversal_ids[1]
에 의해 제공됩니다. PostgreSQL 배열 인덱스는 1부터 시작한다는 점에 유의하세요. - 현재
Namespace
의 ID는traversal_ids[array_length(traversal_ids, 1)]
에 의해 제공됩니다. -
Namespace
의 조상은traversal_ids
로 표현됩니다. -
Namespace
의traversal_ids
는 하위traversal_ids
의 하위 집합입니다.traversal_ids = [1,2,3]
인Namespace
는 모두[1,2,3,...]
로 시작하는 자손을 가집니다. - PostgreSQL 배열은
[1] < [1,1] < [2]
와 같이 정렬됩니다.
이 속성을 사용하여 root
와 ancestors
가 이미 traversal_ids
에 의해 제공된 것을 확인합니다.
객체 자손 쿼리에서는 포함 관계를 테스트하는 @>
배열 연산자에 의존합니다.
검색 공간이 커질수록 @>
연산자는 상당히 느리게 작동합니다. 더 큰 검색 공간을 가진 스코프 쿼리에 대해서는 다른 방법이 사용됩니다.
스코프 쿼리에서는 비교 연산자와 배열 정렬 속성을 결합합니다.
traversal_ids = [1,2,3]
인 Namespace
의 모든 자손은 traversal_ids
가 [1,2,3]
보다 크지만 [1,2,4]
보다 작은 값을 가집니다.
이 예에서 [1,2,3]
과 [1,2,4]
는 형제이고, [1,2,4]
는 [1,2,3]
다음의 형제입니다. traversal_ids
의 다음 형제를 찾기 위해 next_traversal_ids_sibling
이라는 SQL 함수를 제공합니다.
gitlabhq_development=# select next_traversal_ids_sibling(ARRAY[1,2,3]);
next_traversal_ids_sibling
----------------------------
{1,2,4}
(1 row)
그런 다음 비교 연산자를 사용하여 자손 선형 쿼리 스코프를 생성합니다:
WHERE namespaces.traversal_ids > ARRAY[1,2,3]
AND namespaces.traversal_ids < next_traversal_ids_sibling(ARRAY[1,2,3])
Superset
Namespace
쿼리는 중복 결과를 반환할 가능성이 있습니다. 예를 들어, A
와 A.A
의 자손을 찾는 쿼리를 고려해 보세요:
namespaces = Namespace.where(name: ['A', 'A.A'])
namespaces.self_and_descendants
=> A.A, A.A.A, A.A.B, A.B, A.B.A, A.B.B
A
와 A.A
의 자손을 검색하는 것은 불필요합니다. 왜냐하면 A.A
는 이미 A
의 자손이기 때문입니다.
극단적인 경우에는 이로 인해 과도한 I/O가 발생하여 성능이 저하될 수 있습니다.
쿼리에서 중복된 Namespaces
는 traversal_ids
속성의 Namespace
ID
가 쿼리 중인 Namespaces
집합에 속한 다른 Namespace
의 ID
와 일치할 경우 제거됩니다.
이 조건의 일치는 쿼리 중인 Namespaces
집합에 조상이 존재함을 나타내며, 현재 Namespace
가 따라서 중복된 것입니다.
이 최적화는 그렇지 않으면 매우 느릴 수 있는 엣지 케이스의 성능을 훨씬 향상시킬 것입니다.