Isar 개념 4 - Index

pharmDev·2025년 1월 1일

isar

목록 보기
5/7

Isar의 Indexes란?

Index는 데이터베이스에서 특정 데이터를 더 빠르게 검색할 수 있도록 도와주는 중요한 도구입니다. Isar는 고도로 최적화된 인덱스를 제공하며, 이는 대규모 데이터를 다룰 때 특히 유용합니다. 데이터를 효율적으로 검색하려면 적절한 인덱스를 설계하고 사용하는 것이 중요합니다.


Indexes란 무엇인가요?

컬렉션에 데이터가 저장될 때, 쿼리는 특정 데이터를 검색하기 위해 모든 데이터를 탐색해야 할 수도 있습니다. 이는 특히 데이터가 많을수록 비효율적입니다. 인덱스는 데이터를 정렬하여 특정 데이터를 빠르게 검색할 수 있도록 도와줍니다.

예를 들어, Isar에서는 다음과 같이 인덱스를 정의할 수 있습니다:

()
class Item {
  Id id = Isar.autoIncrement; // 자동 증가 ID

  late String name; // 아이템 이름

  late int price; // 아이템 가격
}

위 코드에서 name이나 price 필드를 인덱스로 설정할 수 있습니다.


데이터 예제

아래는 예제 데이터입니다:

idnameprice
1Book55
2Chair25
3Pencil5
4Laptop1000
5Pillow35
6Computer620
7Soap3

Index 없는 쿼리

인덱스가 없는 경우, 특정 조건에 맞는 데이터를 찾으려면 모든 데이터를 순차적으로 검색해야 합니다.

예를 들어, 가격이 620 이상인 데이터를 검색하는 경우:

final results = await isar.items.filter()
    .priceGreaterThan(620)
    .findAll();

이 경우, 데이터가 많아질수록 탐색 시간도 길어지게 됩니다.


Index를 추가한 쿼리

price 필드에 인덱스를 추가하면 검색 성능이 크게 향상됩니다. 인덱스는 데이터를 정렬된 형태로 저장하여 탐색 속도를 높입니다.

다음은 price 필드에 인덱스를 추가하는 방법입니다:

()
class Item {
  Id id = Isar.autoIncrement;

  late String name;

  ()
  late int price; // 인덱스를 추가한 필드
}

인덱스를 추가하면 쿼리 속도가 향상됩니다.


인덱스가 생성된 모습

priceid
37
53
252
355
551
6206
10004

이제 price가 620 이상인 데이터를 찾을 때, 쿼리는 정렬된 데이터에서 바로 해당 값으로 이동합니다. 전체 데이터를 순차적으로 탐색하지 않아도 됩니다.


Sorting (정렬)

인덱스를 사용하면 데이터 정렬 작업도 매우 빠르게 수행할 수 있습니다. 데이터베이스는 정렬을 위해 데이터를 메모리로 불러오고 처리해야 하므로 비용이 많이 들 수 있지만, 인덱스를 사용하면 이러한 과정을 최적화할 수 있습니다.

예제: 가장 저렴한 4개 상품 찾기

1. 인덱스 없는 기본 정렬 쿼리

final cheapest = await isar.products.filter()
    .sortByPrice() // 가격으로 정렬
    .limit(4)      // 상위 4개 선택
    .findAll();    // 결과 가져오기
  • 설명: 위 코드는 모든 데이터를 메모리에 로드한 다음 가격 기준으로 정렬하고, 상위 4개의 데이터를 반환합니다. 데이터가 많을수록 시간이 오래 걸릴 수 있습니다.

2. 인덱스를 사용한 정렬 쿼리

final cheapestFast = await isar.products.where()
    .anyPrice()    // 가격 기준 인덱스 사용
    .limit(4)      // 상위 4개 선택
    .findAll();    // 결과 가져오기
  • 설명:
    • anyPrice()는 가격 인덱스를 사용해 데이터를 정렬된 상태로 가져옵니다.
    • 따라서, 데이터베이스가 모든 데이터를 메모리에 로드하지 않아도 빠르게 정렬 결과를 반환할 수 있습니다.
    • .anyX()는 특정 인덱스를 정렬 목적으로 사용하도록 Isar에 지시합니다. 또는  .priceGreaterThan()와 같은 where 절을 사용하여 정렬된 결과를 얻을 수도 있습니다.

이제 인덱스를 활용한 정렬의 중요성과 방법을 이해할 수 있습니다. 이를 통해 대규모 데이터를 효율적으로 다룰 수 있습니다.

Unique Indexes (유니크 인덱스)


유니크 인덱스란?

유니크 인덱스는 데이터베이스에서 중복값이 저장되지 않도록 보장하는 기능입니다. 특정 필드나 필드 조합의 값이 항상 고유해야 한다면 유니크 인덱스를 설정해야 합니다.

  • 하나의 속성에 유니크 인덱스를 적용하면, 해당 속성의 값은 중복될 수 없습니다.
  • 여러 속성의 조합에 유니크 인덱스를 적용하면, 속성 값들의 조합이 중복되지 않도록 보장합니다.

예제: username 필드에 유니크 인덱스 적용하기

()
class User {
  Id? id; // 자동 증가 ID

  (unique: true) // 유니크 인덱스를 추가
  late String username; // 사용자 이름

  late int age; // 사용자 나이
}

위 예제에서는 username 필드에 유니크 인덱스를 추가하여 동일한 이름으로 데이터를 저장하지 못하도록 설정했습니다.


유니크 인덱스를 사용한 데이터 저장 예제

1. 정상적인 데이터 저장

final user1 = User()
  ..id = 1
  ..username = 'user1' // 고유한 사용자 이름
  ..age = 25;

await isar.users.put(user1); // -> 정상적으로 저장됨

2. 중복 데이터 저장 시도

final user2 = User()
  ..id = 2
  ..username = 'user1' // 중복된 사용자 이름
  ..age = 30;

// 동일한 username을 가진 데이터를 삽입 시도
await isar.users.put(user2); 
// -> 에러 발생: unique constraint violated

// 저장된 데이터 확인
print(await isar.users.where().findAll()); 
// 출력: [{id: 1, username: 'user1', age: 25}]

유니크 인덱스를 사용하면 다음과 같은 이점이 있습니다:

  1. 데이터 무결성 보장: 데이터베이스에 중복 데이터가 저장되지 않도록 방지합니다.
  2. 간편한 중복 검사: 별도의 중복 확인 로직을 작성할 필요 없이, 유니크 인덱스로 자동 처리됩니다.

Replace Indexes (대체 인덱스)


대체 인덱스란?

대체 인덱스는 유니크 제약 조건이 위반되었을 때, 에러를 발생시키는 대신 기존 객체를 새로운 객체로 교체하도록 설정할 수 있습니다. Isar에서 이는 인덱스의 replace 속성을 true로 설정하여 활성화할 수 있습니다.

예제: username 필드에 대체 인덱스 설정

()
class User {
  Id? id; // 자동 증가 ID

  (unique: true, replace: true) // 유니크 인덱스 + 대체 설정
  late String username; // 사용자 이름

  late int age; // 사용자 나이
}

위 코드에서는 username 값이 중복될 경우, 기존 객체를 교체하도록 설정합니다.


대체 인덱스를 사용한 데이터 저장 예제

1. 기존 데이터가 없는 경우

final user1 = User()
  ..id = 1
  ..username = 'user1' // 사용자 이름
  ..age = 25;          // 나이

await isar.users.put(user1); // -> 데이터 저장
print(await isar.users.where().findAll());
// 출력: [{id: 1, username: 'user1', age: 25}]

2. 중복 데이터 삽입 시

final user2 = User()
  ..id = 2
  ..username = 'user1' // 동일한 사용자 이름
  ..age = 30;          // 새로운 나이

await isar.users.put(user2); // -> 기존 데이터를 교체
print(await isar.users.where().findAll());
// 출력: [{id: 2, username: 'user1', age: 30}]
  • 설명:
    • user2username이 기존 user1과 동일하기 때문에 기존 데이터가 교체됩니다.
    • 새로운 데이터가 저장되며, 이전 데이터는 삭제됩니다.

putByX() 메서드 사용

대체 인덱스는 기존 객체를 교체하는 대신 특정 키를 기준으로 데이터를 업데이트할 수도 있습니다. 이를 위해 Isar는 putByX() 메서드를 제공합니다.

putByX() 예제

final user1 = User()
  ..id = 1
  ..username = 'user1' // 사용자 이름
  ..age = 25;          // 나이

await isar.users.putByUsername(user1); // -> 데이터 저장
print(await isar.users.where().findAll());
// 출력: [{id: 1, username: 'user1', age: 25}]

final user2 = User()
  ..id = 2
  ..username = 'user1' // 동일한 사용자 이름
  ..age = 30;          // 새로운 나이

await isar.users.putByUsername(user2); // -> 기존 데이터를 업데이트
print(await isar.users.where().findAll());
// 출력: [{id: 1, username: 'user1', age: 30}]
  • 설명:
    • putByUsername()은 기존 ID를 재사용하며, 링크 데이터도 유지됩니다.

요약

  • 대체 인덱스는 유니크 제약 조건을 위반할 때 데이터를 교체할 수 있습니다.
  • putByX() 메서드를 사용하면 기존 객체를 업데이트하면서 ID와 링크를 유지할 수 있습니다.
  • 이를 통해 효율적으로 데이터를 관리할 수 있으며, 중복 데이터 처리 로직을 단순화할 수 있습니다.

Case-insensitive Indexes (대소문자 구분 없는 인덱스)


대소문자 구분 없는 인덱스란?

기본적으로 StringList<String> 속성의 인덱스는 대소문자를 구분합니다. 하지만, 대소문자에 관계없이 데이터를 검색하려면 인덱스를 생성할 때 caseSensitive 옵션을 false로 설정해야 합니다.


예제: 대소문자 구분 없는 인덱스 생성

()
class Person {
  Id? id; // 자동 증가 ID

  (caseSensitive: false) // 대소문자 구분 없음 설정
  late String name; // 이름

  (caseSensitive: false) // 대소문자 구분 없음 설정
  late List<String> tags; // 태그 목록
}
  • name 필드: 이름을 대소문자 구분 없이 검색할 수 있도록 설정.
  • tags 필드: 태그 목록의 값을 대소문자 구분 없이 검색 가능.

대소문자 구분 없는 인덱스 사용 예제

데이터 저장

final person1 = Person()
  ..id = 1
  ..name = 'Alice' // 대소문자 섞인 이름
  ..tags = ['Dart', 'Flutter'];

await isar.persons.put(person1); // 데이터 저장

데이터 검색

final result1 = await isar.persons.filter()
    .nameEqualTo('alice') // 대소문자 구분 없음
    .findAll();

print(result1); 
// 출력: [{id: 1, name: 'Alice', tags: ['Dart', 'Flutter']}]

final result2 = await isar.persons.filter()
    .tagsElementEqualTo('flutter') // 태그 검색 (대소문자 무시)
    .findAll();

print(result2);
// 출력: [{id: 1, name: 'Alice', tags: ['Dart', 'Flutter']}]
  • nameEqualTo: 이름을 검색할 때 대소문자를 무시하고 Alicealice로 검색 가능.
  • tagsElementEqualTo: 태그 목록 내 요소를 검색할 때 대소문자를 무시.

요약

  • caseSensitive: false를 설정하면 StringList<String> 속성을 대소문자에 관계없이 검색 가능.
  • 대소문자를 구분하지 않아야 하는 검색 요구사항에 유용.
  • 인덱스를 활용하면 성능을 유지하면서 유연한 검색을 구현할 수 있습니다.

Index Type (인덱스 유형)


인덱스 유형이란?

데이터를 효과적으로 관리하기 위해 Isar는 세 가지 주요 인덱스 유형을 제공합니다. 각 유형은 데이터의 성격과 검색 방식에 따라 사용됩니다.


1. Value Index (값 인덱스)

값 인덱스는 기본 인덱스 유형으로, 모든 기본 타입(숫자, 문자열 등)과 리스트(List)에 사용할 수 있습니다. 이 인덱스는 데이터의 각 값을 기준으로 인덱스를 생성합니다.

  • 장점: 가장 유연하며 리스트 요소 하나하나를 검색할 수 있음.
  • 단점: 다른 인덱스 유형보다 저장 공간을 더 많이 사용.

사용 시기:

  • startsWith()와 같은 접두사 검색이 필요한 경우.
  • 리스트 요소를 개별적으로 검색해야 하는 경우.

코드 예제:

()
class Product {
  Id? id;

  (type: IndexType.value) // 기본 값 인덱스
  late String name;

  (type: IndexType.value)
  late List<String> categories;
}

2. Hash Index (해시 인덱스)

해시 인덱스는 문자열과 리스트의 데이터를 해시 처리하여 저장 공간을 절약합니다. 하지만 해시 기반으로 저장되기 때문에 접두사 검색(startsWith())은 지원하지 않습니다.

  • 장점: 저장 공간 효율적.
  • 단점: 접두사 검색 불가.

사용 시기:

  • 데이터가 크고, 접두사 검색이 필요 없는 경우.

코드 예제:

()
class Product {
  Id? id;

  (type: IndexType.hash) // 해시 인덱스
  late String name;

  (type: IndexType.hash)
  late List<String> tags;
}

3. HashElements Index (해시 요소 인덱스)

리스트 데이터를 전체가 아닌 개별 요소로 해싱하여 저장합니다. 이를 통해 리스트 요소를 효과적으로 검색할 수 있습니다.

  • 장점: 리스트의 개별 요소를 빠르게 검색 가능.
  • 단점: 접두사 검색 불가.

사용 시기:

  • 리스트 내 특정 요소를 검색해야 하는 경우.

코드 예제:

()
class Product {
  Id? id;

  (type: IndexType.hashElements) // 리스트의 각 요소를 해싱
  late List<String> tags;
}

요약

  • Value Index: 가장 일반적인 인덱스, 접두사 검색 가능.
  • Hash Index: 저장 공간을 절약하며, 접두사 검색이 필요 없을 때 사용.
  • HashElements Index: 리스트 요소를 개별적으로 검색해야 할 때 유용.

선택 가이드

  1. 접두사 검색이 필요한 경우: Value Index 사용.
  2. 저장 공간 최적화가 중요한 경우: Hash Index 사용.
  3. 리스트 내 특정 요소 검색이 필요한 경우: HashElements Index 사용.

Composite Indexes (복합 인덱스)


복합 인덱스란?

복합 인덱스는 여러 속성(최대 5개)을 조합하여 인덱스를 생성하는 방식입니다. 이는 다중 속성 인덱스라고도 불립니다. 복합 인덱스를 사용하면 여러 속성을 조합하여 효율적으로 데이터를 검색할 수 있습니다.


복합 인덱스 생성 예제

다음은 agename 속성을 조합하여 복합 인덱스를 생성하는 예제입니다:

()
class Person {
  Id? id; // 자동 증가 ID

  (composite: [CompositeIndex('name')]) // 복합 인덱스 생성
  late int age; // 나이

  late String name; // 이름

  late String hometown; // 고향
}

데이터 예제

idagenamehometown
124DanielBerlin
230CarlParis
324DavidSan Diego
424CarlMunich
530AudreyPrague
624SmithLondon

생성된 복합 인덱스

agenameid
24Carl2
24David3
24Daniel1
24Smith6
30Audrey5
30Carl4

복합 인덱스는 age에 따라 정렬되고, 같은 age 내에서는 name을 기준으로 정렬됩니다.


복합 인덱스 사용 예제

데이터 검색

final result = await isar.persons.where()
    .ageEqualTo(24) // 첫 번째 속성 조건
    .nameEqualTo('David') // 두 번째 속성 조건
    .findAll();

print(result);
// 출력: [{id: 3, age: 24, name: 'David', hometown: 'San Diego'}]

범위 조건 사용

final result = await isar.persons.where()
    .ageEqualTo(24) // 첫 번째 속성 조건
    .nameGreaterThan('D') // 두 번째 속성 범위 조건
    .findAll();

print(result);
// 출력: [{id: 3, age: 24, name: 'David', hometown: 'San Diego'}, {id: 6, age: 24, name: 'Smith', hometown: 'London'}]

복합 인덱스의 장점

  1. 다중 속성 기반 검색: 한 번의 인덱스로 여러 속성을 기준으로 데이터를 검색 가능.
  2. 정렬 효율성: 인덱스가 생성된 순서대로 자동 정렬.
  3. 범위 조건 지원: 복합 인덱스의 마지막 속성에서는 범위 조건(>, <)을 사용할 수 있음.

요약

  • 복합 인덱스는 여러 속성을 결합하여 데이터 검색을 최적화합니다.
  • 적절한 조건과 순서로 검색하면 성능을 크게 향상시킬 수 있습니다.
  • 마지막 속성에서 범위 조건을 사용할 수 있어 유연한 검색이 가능합니다.

Multi-entry Indexes (다중 항목 인덱스)


다중 항목 인덱스란?

Isar에서 리스트(List<String> 등)를 인덱싱하면 자동으로 다중 항목 인덱스가 생성됩니다. 리스트의 각 요소는 독립적인 인덱스 항목으로 처리되어 검색 시 활용됩니다. 이 방식은 태그, 키워드와 같은 데이터를 검색하거나, 전체 텍스트 검색을 구현할 때 유용합니다.


다중 항목 인덱스 생성 예제

()
class Product {
  Id? id; // 자동 증가 ID

  late String description; // 설명

  (type: IndexType.value, caseSensitive: false) 
  late List<String> descriptionWords; // 설명에 포함된 단어들
}
  • descriptionWords 필드는 리스트 형태의 데이터를 인덱싱하며, 각 단어가 개별적으로 검색 가능해집니다.
  • caseSensitive: false로 설정하면 대소문자 구분 없이 검색할 수 있습니다.

데이터 예제

iddescriptiondescriptionWords
1Comfortable black sofacomfortable, black, sofa
2Black leather reclinerblack, leather, recliner
3Recliner with soft cushionsrecliner, soft, cushions

생성된 인덱스

descriptionWordsid
black1, 2
comfortable1
cushions3
leather2
recliner2, 3
sofa1
soft3

다중 항목 인덱스 사용 예제

특정 단어 검색

final result = await isar.products.where()
    .descriptionWordsElementEqualTo('black') // 'black' 포함된 데이터 검색
    .findAll();

print(result);
// 출력: [
//   {id: 1, description: 'Comfortable black sofa'},
//   {id: 2, description: 'Black leather recliner'}
// ]

여러 단어 검색

final result = await isar.products.filter()
    .descriptionWordsElementEqualTo('recliner') // 'recliner' 포함
    .descriptionWordsElementEqualTo('soft') // 'soft' 포함
    .findAll();

print(result);
// 출력: [{id: 3, description: 'Recliner with soft cushions'}]

추가 팁: 음운 알고리즘 활용

Isar는 Unicode Annex #29를 준수하여 텍스트를 단어로 분리합니다. 그러나 동일한 소리가 나는 단어를 검색하려면 음운 알고리즘(예: Soundex)을 사용하는 것이 유용할 수 있습니다.


요약

  • 다중 항목 인덱스는 리스트의 각 요소를 독립적으로 인덱싱하여 검색 가능성을 확장합니다.
  • 태그, 키워드, 전체 텍스트 검색 등에 적합합니다.
  • descriptionWordsElementEqualTo와 같은 메서드를 사용하여 리스트 요소를 효율적으로 검색할 수 있습니다.

질문이나 더 알고 싶은 점이 있다면 언제든 말씀해주세요! 😊

profile
코딩을 배우는 초보

0개의 댓글