DataPipeline/Elasticsearch

엘라스틱서치 바이블 - 3장 인덱스 설계

wave35 2025. 4. 27. 22:13

[ 인덱스 설정 ]

GET [index_name]/_settings

인덱스 설정은 인덱스명 뒤에 _settings를 넣어 GET 메서드로 호출한다.

 

예제

PUT /my_index
{
    "settings": {
        "number_of_shards": 2,
        "number_of_replicas": 2
    }
}
>>> 
{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "my_index"
}


GET my_index
>>>
{
  "my_index": {
    "aliases": {},
    "mappings": {},
    "settings": {
      "index": {
        "routing": {
          "allocation": {
            "include": {
              "_tier_preference": "data_content"
            }
          }
        },
        "number_of_shards": "2",
        "provided_name": "my_index",
        "creation_date": "1745115115705",
        "number_of_replicas": "2",
        "uuid": "3XaOBSOKTmi-PfcTLvPjiw",
        "version": {
          "created": "8521000"
        }
      }
    }
  }
}

 

number_of_shards

  • 인덱스 데이터를 몇 개의 샤드로 쪼갤 것인지 지정하는 값
  • 한번 지정하면 바꾸기가 쉽지 않음
  • 샤드 당 루씬 인덱스가 하나씩 더 생성되어 너무 크게 설정하면 클러스터 성능이 떨어짐
  • 너무 적게 설정하면 샤드 크기가 커져 복구시간이 오래 걸리고 안정성이 떨어짐
  • 기본 값은 1

number_of_replicas

  • 주 샤드 하나당 본제본 샤드를 몇 개 생성할 것인지 지정하는 값
  • 인덱스 생성 후에도 동적으로 변경 가능

refresh_interval

  • 엘라스틱서치가 인덱스 대상으로 refresh를 얼마나 자주 수행할 것인지 지정하는 값
  • 기본 값은 "1s", 1초

 

 

[ 맵핑 필드와 타입 ]

동적 맵핑

아무 내용이 없던 mappings 항목에 필드의 타입과 정보가 추가된 것을 확인할 수 있다.

인덱스에 문서가 색인 될 때, 기존에 매핑 정보가 없으면 자동으로 적당한 필드 타입을 지정하며 생성한다.

POST /my_index/_doc/1
{
    "title": "hellow!",
    "views": 1234,
    "public": true,
    "point": 4.5,
    "created": "2025-04-20T14:00:54.123Z"
}

>>>
{
  "my_index": {
    "aliases": {},
    "mappings": {
      "properties": {
        "created": {
          "type": "date"
        },
        "point": {
          "type": "float"
        },
        "public": {
          "type": "boolean"
        },
        "title": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "views": {
          "type": "long"
        }
      }
    },
    ...
}

 

명시적 맵핑

맵핑 설정은 한 번 지정이되면 변경하기가 힘들다.

운영시 명시적 맵핑을 활용해야하며, 신규 필드 추가시에도 명시적 맵핑을 이용하는 것이 좋다.

PUT /my_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      },
      "views": {
        "type": "integer"
      },
      "public": {
        "type": "boolean"
      },
      "point": {
        "type": "float"
      },
      "created": {
        "type": "date",
        "format": "strict_date_time" 
        // ISO 8601, 예: 2025-04-20T14:00:54.123Z
      }
    }
  }
}

# 적용확인
GET my_index/_mapping

 

필드타입

필드타입은 boolean, text, keyword, long, integer, short, byte, double, float, half_float, scaled_float,

date, ip, array, object, nested, geo_point, geo_shape 등이 있으며

작은 비트를 사용하는 자료형은 색인과 검색시 이득이 있다. 

다만 저장할 때는 실제 값에 맞춰 최적화되기에 디스크 사용량에는 이득이 없다.

 

text 타입과 keyword 타입

text :

  • 애널라이저가 적용된 후 색인된다.
  • 즉 들어온 값은 분석하여 여러 토큰으로 쪼개어 역색인을 구성한다.
  • 전문검색에 적합합니다.

keywork :

  • 문자열 값을 여러 토큰으로 쪼개지 않고 역색인한다.
  • 간단한 전처리만을 실행하는 노멀라이저를 적용한다.
  • 정렬과 집계과 작은 작업일 때 적절하다.

 

타입 참조 :

https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/field-data-types

 

 

[ 애널라이저와 토크나이저 ]

애널라이저는 3단계로 이루어져있다.

캐릭터 필터 (Character Filter)

  • 원본 텍스트에서 특수 문자 제거 등의 전처리
  • 0개 이상의 캐릭터필터를 지정할 수 있으며 순서대로 수행된다.

토크나이저 (Tokenizer)

  • 텍스트를 개별 토큰으로 분리
  • 한 개의 토크나이저만 지정할 수 있다.

토큰 필터 (Token Filter)

  • 토큰 변형, 추가 또는 제거
  • lowercase / pattern_replace / trim / tuncate 등이 있다. 

 

예시

curl -X GET "localhost:9200/_analyze" -H 'Content-Type: application/json' -d '
{
  "char_filter": ["html_strip"],
  "tokenizer": "standard",
  "filter": ["uppercase"],
  "text": "<p>hellow, hi word!</p>"
}'

>>>
{
  "tokens": [
    {
      "token": "HELLOW",
      "start_offset": 0,
      "end_offset": 6,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "HI",
      "start_offset": 8,
      "end_offset": 10,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "WORD",
      "start_offset": 11,
      "end_offset": 15,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

 

 

[ 템플릿 ]

인덱스를 설정할 때마다 이런 설정값을 매번지정하는 번거로움이 있기에

사전에 템플릿을 정의해 두면 반복적인 작업을 줄여준다.

 

인덱스 템플릿

인덱스 패턴에는 * 와일드카드를 사용할 수 있다.

priority 값을 이용하면 인덱스 템플릿 간 우선 적용순위를 조정한다. (높을수록 우선순위 높음)

 

예시

PUT /_template/my_template
{
  "index_patterns": ["test-te*", "bar*"],
  "priority": 1,
  "template" {
      "settings": {
        "number_of_shards": 1
      },
      "mappings": {
        "properties": {
          "host_name": {
            "type": "keyword"
          },
          "created_at": {
            "type": "date",
            "format": "EEE MMM dd HH:mm:ss Z yyyy"
          }
        }
      }
   }
}
# 생성된 템플릿 조회
GET /_template/my_template

# 위 템플릿에 맞는 인덱스 생성
PUT test-text-1 
GET test-text-1

 

컴포넌트 템플릿

인덱스 템플릿을 많이 만들면 중복되는 부분이 생김나.

이를 재사용할 수 있는 작은 템플릿 블록으로 쪼갠 것이 컴포넌트 템플릿이다.

 

예제

PUT /_component_template/log_mappings
{
  "template": {
    "mappings": {
      "properties": {
        "host_name": {
          "type": "keyword"
        },
        "created_at": {
          "type": "date",
          "format": "EEE MMM dd HH:mm:ss Z yyyy"
        },
        "message": {
          "type": "text"
        },
        "level": {
          "type": "keyword"
        }
      }
    }
  }
}

PUT /_index_template/logs_template
{
  "index_patterns": ["logs-*", "app-logs-*"],
  "composed_of": ["log_mappings"],
  "priority": 100,
  "template": {
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 1
    }
  }
}

PUT /_component_template/basic_settings
{
  "template": {
    "settings": {
      "number_of_shards": 2,
      "number_of_replicas": 1
    }
  }
}

PUT /_index_template/complete_logs_template
{
  "index_patterns": ["logs-*"],
  "composed_of": ["basic_settings", "log_mappings"],
  "priority": 200
}

 

동적 템플릿

동적 템플릿은 인덱스에 새로 들어온 필드의 매핑을 사전에 정의한대로 동적 생성한다.

 

예제

- description_text와 같이 _text로 끝나는 문자열 필드는 text 타입으로 매핑

- user_id_keyword와 같이 _keyword로 끝나는 문자열 필드는 keyword 타입으로 매핑

PUT my-index
{
  "mappings": {
    "dynamic_templates": [
      {
        "text_fields": {
          "match_mapping_type": "string",
          "match": "*_text",
          "mapping": {
            "type": "text"
          }
        }
      },
      {
        "keyword_fields": {
          "match_mapping_type": "string",
          "match": "*_keyword",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}

 

 

[ 라우팅 ]

엘라스틱서치가 인덱스를 구성하는 샤드 중, 몇 번 샤드를 대상으로 작업을 수행하지 지정하기 위해 사용하는 값

데이터 분산과 검색 성능에 중요한 영향을 미침

 

예제

- 5개의 샤드를 가진 익덱스를 생성

- routing 값으로 라우팅을 지정

PUT my-index
{
	"settings":{
    	"number_of_shards" 5,
        "number_of_replicas": 1
    }
}

PUT my-index/_doc/1?routing=user1
{
  "title": "This is a document"
}

 

조회

GET my-index/_search
{
  "took": 5,
  "timed_out": false,
  "_shards": {
    "total": 2,            // 실제 검색이 수행된 샤드 수 (user1, user2에 해당하는 샤드만)
    "successful": 2,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 42,
      "relation": "eq"
    },
    "max_score": 1.0,
    "hits": [
      {
        "_index": "my-index",
        "_id": "1",
        "_score": 1.0,
        "_routing": "user1",    // 이 문서는 user1 라우팅 값으로 저장됨
        "_source": {
          "title": "Document for User 1",
          "content": "This is content for user1",
          "timestamp": "2023-07-15T10:30:00Z",
          "user_id": "user1"
        }
      },
      {
        "_index": "my-index",
        "_id": "3",
        "_score": 1.0,
        "_routing": "user2",    // 이 문서는 user2 라우팅 값으로 저장됨
        "_source": {
          "title": "Document for User 2",
          "content": "This is content for user2",
          "timestamp": "2023-07-14T14:20:00Z",
          "user_id": "user2"
        }
      },
	  ...
    ]
  }
}

 

운영에서는 라우팅을 지정하는 것을 권장

- 라우팅 값을 필수로 설정하는 예제

PUT my-index
{
  "mappings": {
    "_routing": {
      "required": true
    }
  }
}