1. 색인(Index)의 개념과 활용

1.1 색인이란

색인은 데이터베이스 관리 시스템에서 데이터 검색 작업을 가속화하는 데 사용되는 데이터 구조입니다. 이는 데이터베이스 내에서 "디렉토리"로 볼 수 있으며, 데이터 테이블에서 데이터를 빠르게 찾아내어 전체 테이블 스캔을 피하고 쿼리 효율성을 크게 향상시킵니다. 실제 응용 프로그램에서 색인을 올바르게 사용하면 데이터베이스의 성능을 크게 향상시킬 수 있습니다.

1.2 색인의 종류

여러 가지 종류의 색인이 있으며, 각각이 다른 응용 시나리오에 대한 설계와 최적화를 제공합니다.

  • 단일 필드 색인: 하나의 필드만 포함하는 색인으로, 빠른 쿼리를 위해 하나의 조건에 의존하는 시나리오에 적합합니다.
  • 복합 색인: 여러 필드를 포함하는 색인으로, 이러한 필드를 포함하는 쿼리에 대한 최적화를 제공합니다.
  • 고유 색인: 색인 필드의 고유성을 보장하여 중복 값을 허용하지 않습니다. 고유 색인은 단일 필드 또는 복합 색인일 수 있습니다.

2. 색인 정의

2.1 필드 색인 정의

단일 필드 색인을 생성하려면 데이터 테이블의 특정 열에 색인을 설정해야 합니다. 이는 Fields 메서드를 사용하여 달성할 수 있습니다. 다음 예제는 User 엔티티의 phone 필드에 색인을 생성하는 방법을 보여줍니다.

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("phone"),
    }
}
func (User) Indexes() []ent.Index {
    // 단일 필드 색인 생성.
    return []ent.Index{
        index.Fields("phone"),
    }
}

이 코드 스니펫에서 phone 필드가 색인화됩니다. 이를 통해 시스템은 phone 필드를 쿼리할 때 빠른 검색을 위해 색인을 활용할 수 있습니다.

2.2 고유 색인 정의

고유 색인은 색인 열의 데이터 고유성을 보장합니다. 이를 위해 필드에 Unique 메서드를 추가하여 생성할 수 있습니다. 다음 예제는 단일 및 다중 필드에 대해 고유 색인을 생성하는 방법을 보여줍니다.

단일 필드에 대한 고유 색인 생성 예시:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("email").Unique(),
    }
}

이 예에서 email 필드가 고유하게 지정되어 있어 각 사용자의 email이 데이터베이스 내에서 고유하게 유지됨을 보장합니다.

여러 필드 조합에 대한 고유 색인 생성 예시:

func (User) Indexes() []ent.Index {
    return []ent.Index{
        // 여러 필드에 대한 고유 색인 생성
        index.Fields("first_name", "last_name").Unique(),
    }
}

이 코드는 first_namelast_name 필드의 결합에 대한 복합 고유 색인을 정의하여 두 필드에서 동일한 값의 레코드가 발생하지 않도록 합니다.

2.3 복합 색인 정의

쿼리 조건이 여러 필드를 포함할 때 복합 색인이 유용합니다. 복합 색인은 인덱스에 정의된 순서대로 데이터를 저장합니다. 인덱스의 순서는 쿼리 성능에 상당한 영향을 미치므로 복합 색인을 정의할 때는 필드의 순서를 쿼리 패턴에 기반하여 결정해야 합니다.

다음은 복합 색인을 생성하는 예시입니다:

func (User) Indexes() []ent.Index {
    return []ent.Index{
        // 여러 필드를 포함하는 복합 색인 생성
        index.Fields("country", "city"),
    }
}

이 예에서 countrycity 필드에 대한 복합 색인이 생성됩니다. 이는 이 두 필드를 포함하는 쿼리 작업을 수행할 때 데이터베이스가 빠르게 조건에 부합하는 데이터를 찾을 수 있음을 의미합니다.

복합 색인은 쿼리 성능을 향상시키는 것뿐만 아니라 색인 기반의 정렬 작업을 지원하여 더 효율적인 데이터 검색 성능을 제공합니다. 복합 색인을 설계할 때는 일반적으로 선택도가 높은 필드를 인덱스 앞에 배치하여 데이터베이스 최적화기가 색인을 더 잘 활용할 수 있도록 하는 것이 일반적입니다.

3. 엣지 인덱스

ent 프레임워크에서 엣지 인덱스는 관계(엣지)를 통해 인덱스를 정의하는 방법입니다. 이 매커니즘은 일반적으로 특정 관계 아래의 필드의 고유성을 보장하는 데 사용됩니다. 예를 들어, 데이터베이스 모델에 도시와 거리가 포함되어 있고 각 도시의 거리 이름이 고유해야 하는 경우, 엣지 인덱스를 사용하여 이를 달성할 수 있습니다.

// ent/schema/street.go 파일은 Street 엔티티의 스키마를 정의합니다.
type Street struct {
    ent.Schema
}

func (Street) Fields() []ent.Field {
    // 필드 정의
    return []ent.Field{
        field.String("name"),
    }
}

func (Street) Edges() []ent.Edge {
    // Street가 속한 City와의 엣지 관계를 정의합니다.
    return []ent.Edge{
        edge.From("city", City.Type).
            Ref("streets").
            Unique(),
    }
}

func (Street) Indexes() []ent.Index {
    // 동일한 도시 내 거리 이름의 고유성을 보장하기 위해 엣지를 통해 고유한 인덱스를 생성합니다.
    return []ent.Index{
        index.Fields("name").
            Edges("city").
            Unique(),
    }
}

이 예에서는 Street 엔티티를 생성하고 City 엔티티와 연결합니다. Street 엔티티의 Indexes 메서드에서 엣지 인덱스를 정의하여 각 도시의 거리 이름이 고유함을 보장합니다.

5장: 고급 인덱스 옵션

5.1 전체 텍스트 및 해시 인덱스

전체 텍스트 인덱스와 해시 인덱스는 MySQL 및 PostgreSQL의 두 가지 고유한 인덱스 유형으로, 각각 다른 쿼리 최적화 시나리오에 사용됩니다.

전체 텍스트 인덱스는 주로 텍스트 데이터 검색에 사용되며, 단어 일치 검색과 같이 복잡한 검색을 수행해야 할 때 특히 유용합니다. MySQL과 PostgreSQL 데이터베이스 모두 전체 텍스트 인덱싱을 지원합니다. 예를 들어, MySQL에서는 다음과 같이 전체 텍스트 인덱스를 정의할 수 있습니다.

// ent/schema/user.go 파일은 User 엔티티의 스키마를 정의합니다.
func (User) Indexes() []ent.Index {
    // MySQL의 FULLTEXT 카테고리를 사용하여 전체 텍스트 인덱스를 생성합니다.
    return []ent.Index{
        index.Fields("description").
            Annotations(entsql.IndexTypes(map[string]string{
                dialect.MySQL: "FULLTEXT",
            })),
    }
}

해시 인덱스는 특히 동등 쿼리에 적합하며, 정렬 및 범위 쿼리를 지원하지 않습니다. PostgreSQL에서는 다음과 같이 해시 인덱스를 사용할 수 있습니다.

func (User) Indexes() []ent.Index {
    // HASH 유형의 인덱스를 정의합니다.
    return []ent.Index{
        index.Fields("c4").
            Annotations(entsql.IndexType("HASH")),
    }
}

5.2 부분 인덱스와 인덱스 접두사

부분 인덱스는 특정 조건을 충족하는 테이블 행만 인덱싱하는 인덱스 유형입니다. SQLite 및 PostgreSQL에서 WHERE 절을 사용하여 부분 인덱스를 생성할 수 있습니다.

예를 들어, PostgreSQL에서 부분 인덱스를 정의하는 방법은 다음과 같습니다.

func (User) Indexes() []ent.Index {
    // "active"가 true인 행만 포함하는 "nickname" 필드에 부분 인덱스를 생성합니다.
    return []ent.Index{
        index.Fields("nickname").
            Annotations(
                entsql.IndexWhere("active"),
            ),
    }
}

인덱스 접두사는 특히 MySQL에서 텍스트 필드에 유용합니다. 인덱스 생성 시간을 단축시키고, 인덱스가 차지하는 공간을 줄이며, 우수한 성능을 제공할 수 있습니다. 아래 예시에서는 MySQL에서 접두사가 있는 인덱스를 정의하는 방법을 보여줍니다.

func (User) Indexes() []ent.Index {
    // 접두사를 사용하여 인덱스를 생성합니다.
    return []ent.Index{
        index.Fields("description").
            Annotations(entsql.Prefix(128)),
    }
}

5.3 인덱스 주석 및 사용자 정의

ent에서 Annotations는 인덱스를 사용자 정의하는 기능입니다. 인덱스 유형을 정의하고, 정렬 규칙을 설정하는 등 다양한 사용자 정의를 할 수 있습니다.

예를 들어, 다음 코드는 인덱스의 열 정렬 규칙을 지정하는 방법을 보여줍니다.

func (User) Indexes() []ent.Index {
    return []ent.Index{
        // 주석을 사용하여 인덱스의 열 정렬 규칙을 정의합니다.
        index.Fields("c1", "c2", "c3").
            Annotations(entsql.DescColumns("c1", "c2")),
    }
}

주석 기능을 사용하면 개발자가 유연하게 인덱스를 사용자 정의하여 데이터베이스 성능과 구조를 최적화할 수 있습니다.