네임스페이스

네임스페이스는 프로젝트와 관련 리소스의 컨테이너입니다. NamespaceGroup, ProjectNamespace, 그리고 UserNamespace의 서브클래스를 통해 인스턴스화됩니다.

graph TD 네임스페이스 -.- 그룹 네임스페이스 -.- 프로젝트네임스페이스 네임스페이스 -.- 유저네임스페이스

사용자는 하나의 UserNamespace를 가지며, 많은 네임스페이스의 멤버가 될 수 있습니다.

graph TD 네임스페이스 -.- 그룹 네임스페이스 -.- 프로젝트네임스페이스 네임스페이스 -.- 유저네임스페이스 사용자 -- has one --- UserNamespace 네임스페이스 --- 멤버 --- 사용자

그룹은 재귀적인 계층적 관계에 존재합니다. 그룹은 많은 프로젝트네임스페이스를 가지며, 하나의 프로젝트를 부모로 할 수 있습니다.

graph TD 그룹 -- has many --- 프로젝트네임스페이스 -- has one --- 프로젝트 그룹 -- has many --- 그룹

네임스페이스 조회

네임스페이스 계층을 조회하는 데 사용할 수 있는 메서드 집합이 있습니다. 이러한 메서드는 표준 Rails ActiveRecord::Relation 객체를 생성합니다. 이러한 메서드는 하나의 세트는 네임스페이스 객체에서 작동하고, 다른 세트는 조합 가능한 네임스페이스 스코프로 작동합니다.

그들의 성격상, 객체 메서드는 단일 네임스페이스 계층 내에서 작동하며, 스코프는 계층을 걸칠 수 있습니다.

다음은 Namespace 계층을 조회하는 메서드들의 일부 디렉터리입니다.

루트 네임스페이스

루트는 계층구조에서 가장 위에 있는 네임스페이스입니다. 루트는 nil parent_id를 가집니다.

graph TD classDef active fill:#f00,color:#fff classDef sel fill:#00f,color:#fff A --- A.A --- A.A.A A.A --- A.A.B A --- A.B --- A.B.A A.B --- A.B.B 클래스 A.A.B active 클래스 A sel
Namespace.where(...).roots

namespace_object.root_ancestor

후손 네임스페이스

네임스페이스의 후손은 해당 네임스페이스의 자식, 그들의 자식들, 등을 가리킵니다.

graph TD classDef active fill:#f00,color:#fff classDef sel fill:#00f,color:#fff A --- A.A --- A.A.A A.A --- A.A.B A --- A.B --- A.B.A A.B --- A.B.B 클래스 A.A active 클래스 A.A.A,A.A.B sel

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를 반환하는 것이 더 효율적일 수 있습니다: ```ruby 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 ```

조상 네임스페이스

네임스페이스의 조상은 해당 네임스페이스의 부모, 그들의 부모들, 등을 가리킵니다.

graph TD classDef active fill:#f00,color:#fff classDef sel fill:#00f,color:#fff A --- A.A --- A.A.A A.A --- A.A.B A --- A.B --- A.B.A A.B --- A.B.B 클래스 A.A active 클래스 A sel

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_ancestor_ids
Namespace.where(...).self_and_ancestor_ids(include_self: false)

namespace_object.self_and_ancestor_ids
namespace_object.ancestor_ids

계층구조

네임스페이스 계층은 네임스페이스, 그의 조상, 그리고 그의 후손으로 이루어집니다.

graph TD classDef active fill:#f00,color:#fff classDef sel fill:#00f,color:#fff A --- A.A --- A.A.A A.A --- A.A.B A --- A.B --- A.B.A A.B --- A.B.B 클래스 A.A active 클래스 A,A.A.A,A.A.B sel

우리는 네임스페이스 계층을 조회할 수 있습니다:

Namespace.where(...).self_and_hierarchy

namespace_object.self_and_hierarchy

재귀적 쿼리

위의 쿼리들은 선형 쿼리라고 알려져 있습니다. 왜냐하면 표준 SQL 쿼리를 수행하기 위해 namespaces.traversal_ids 열을 사용하기 때문입니다.

필요하다면 일련의 레거시 재귀 쿼리도 접근 가능합니다:

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_ancestor_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_ids 배열 열을 사용하여 실행됩니다. 각 배열은 루트 네임스페이스에서 현재 네임스페이스까지의 순서가 있는 Namespace ID의 집합을 나타냅니다.

다음과 같은 시나리오가 주어집니다:

graph TD classDef active fill:#f00,color:#fff classDef sel fill:#00f,color:#fff A --- A.A --- A.A.A A.A --- A.A.B A --- A.B --- A.B.A A.B --- A.B.B 클래스 A.A.B active

A.A.Btraversal_ids[A, A.A, A.A.B]가 될 것입니다.

traversal_ids는 다음과 같은 유용한 속성을 가지고 있습니다:

  • 네임스페이스의 루트는 traversal_ids[1]로 제공됩니다. PostgreSQL 배열 인덱스는 1에서 시작한다는 것을 주목하세요.
  • 현재 네임스페이스의 ID는 traversal_ids[array_length(traversal_ids, 1)]로 제공됩니다.
  • 네임스페이스의 조상들은 traversal_ids에 의해 나타내집니다.
  • 네임스페이스traversal_ids는 후손들의 traversal_ids의 부분집합입니다. traversal_ids = [1,2,3]네임스페이스는 모두 [1,2,3,...]로 시작하는 후손들을 가질 것입니다.
  • PostgreSQL 배열은 [1] < [1,1] < [2]와 같은 순서로 정렬됩니다.

이러한 속성들을 활용하여 rootancestors는 이미 traversal_ids에 의해 제공된다는 것을 알 수 있습니다.

객체 후손 쿼리에서는 @> 배열 연산자를 사용하여 배열 안에 다른 배열이 포함되는지를 테스트합니다. @> 연산자는 검색 공간이 커질수록 상당히 느려지는 것으로 밝혀졌습니다. 더 큰 검색 공간을 갖는 스코프 쿼리에서는 다른 방법이 사용됩니다. 우리는 스코프 쿼리에서 비교 연산자를 배열 순서 속성과 결합하여 조합합니다.

traversal_ids = [1,2,3]네임스페이스의 모든 후손들은 [1,2,3]보다 크면서 [1,2,4]보다 작습니다. 이 예에서 [1,2,3][1,2,4]는 형제이며, [1,2,4][1,2,3] 후에 나오는 형제입니다. next_traversal_ids_sibling이라는 SQL 함수가 traversal_ids의 다음 형제를 찾도록 제공됩니다.

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])

슈퍼셋

Namespace 쿼리는 중복된 결과를 반환하기 쉽습니다. 예를 들어 AA.A의 자손을 찾는 쿼리를 고려해보십시오.

graph TD classDef active fill:#f00,color:#fff classDef sel fill:#00f,color:#fff A --- A.A --- A.A.A A.A --- A.A.B A --- A.B --- A.B.A A.B --- A.B.B class A,A.A active class A.A.A,A.A.B,A.B,A.B.A,A.B.B sel
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

AA.A의 자손을 모두 찾는 것은 불필요합니다. 왜냐하면 A.A는 이미 A의 자손이기 때문입니다. 극단적인 경우에는 이로 인해 지나치게 많은 I/O가 발생하여 성능이 저하될 수 있습니다.

중복된 Namespaces는 쿼리에서 제거됩니다. 즉, traversal_ids 속성의 Namespace ID가 쿼리 중인 Namespaces 집합에 속하는 다른 NamespaceID와 일치하는 경우입니다. 이 조건이 일치하면 집합에 조상이 존재하고 현재 Namespace가 따라서 중복되었음을 나타냅니다. 이 최적화는 그렇지 않으면 매우 느릴 수 있는 경계 상황의 훨씬 더 나은 성능을 보장할 것입니다.