๋ณธ๋ฌธ์œผ๋กœ ๋ฐ”๋กœ๊ฐ€๊ธฐ

 

๋ฌธ์ œ ์ƒํ™ฉ

์–ด๋А ๋‚  ๊ฐ‘์ž๊ธฐ ํŒ€์›์ด ์ฐพ์•„์™€์„œ ๋งํ–ˆ๋‹ค. 

 

"์ง€๊ธˆ ๋กœ๊ทธ์—์„œ ์‚ฌ์šฉ์ž ์„ ํ˜ธ ๊ตญ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์‹œ๊ฐํ™”ํ•˜๋Š”๋ฐ, 5๊ฐœ๋ฅผ ๊ณ ๋ฅธ ์œ ์ €์™€ 1๊ฐœ๋งŒ ๊ณ ๋ฅธ ์œ ์ €์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ™์ด ์Œ“์ด๊ณ  ์žˆ์–ด์„œ ์™œ๊ณก์ด ์ƒ๊ธฐ๋Š” ๊ฒƒ ๊ฐ™์•„์š”. ๊ตญ๊ฐ€๋ณ„๋กœ ๊ด‘๊ณ  ์บ ํŽ˜์ธ์„ ์ง‘ํ–‰ํ•ด์•ผํ•˜๋Š”๋ฐ, ๋นจ๋ฆฌ ํ™•์ธํ•ด๋ณผ ์ˆ˜ ์žˆ์„๊นŒ์š”?"

 

๋งž๋Š” ๋ง์ด์—ˆ๋‹ค. 'ํ•œ๊ตญ'๋งŒ์„ ์„ ํ˜ธํ•˜๋Š” ์‚ฌ์šฉ์ž์™€ 5๊ฐœ ๊ตญ๊ฐ€ ์ค‘ ํ•˜๋‚˜๋กœ ํ•œ๊ตญ์„ ํฌํ•จ์‹œํ‚จ ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋™์ผํ•œ ๊ฐ€์ค‘์น˜๋กœ ์ง‘๊ณ„๋œ๋‹ค๋ฉด, ์‹ค์ œ ์„ ํ˜ธ๋„๋ฅผ ์ œ๋Œ€๋กœ ๋ฐ˜์˜ํ•˜๊ธฐ ์–ด๋ ค์šธ ๊ฒƒ์ด๋‹ค.

 

"๊ทธ๋Ÿผ, ์„ ํƒํ•œ ๊ฐœ์ˆ˜์— ๋”ฐ๋ผ ๊ฐ€์ค‘์น˜๋ฅผ ๊ณ„์‚ฐํ•ด์„œ ํ•„๋“œ๋กœ ์ œ๊ณตํ•ด๋“œ๋ฆด๊นŒ์š”? ์˜ˆ๋ฅผ ๋“ค์–ด 1๊ฐœ ์„ ํƒํ•˜๋ฉด 1.0, 5๊ฐœ ์„ ํƒํ•˜๋ฉด ๊ฐ๊ฐ 0.2์”ฉ ๋ถ€์—ฌํ•˜๋Š” ์‹์ด๋ฉด ์ •ํ™•ํ•˜์ง„ ์•Š์•„๋„, ์ง€๊ธˆ๋ณด๋‹ค๋Š” ํ›จ์”ฌ ์ •ํ™•ํ•  ๊ฑฐ์—์š”." 

 

ํŒ€์›๋„ ์ข‹์€ ์•„์ด๋””์–ด๋ผ๊ณ  ๋™์˜ํ•˜๋ฉฐ ๋ฐ”๋กœ ํ˜„์žฌ ์ƒํ™ฉ์„ ๋ถ„์„ํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค. ๊ธฐ์กด Elasticsearch์—๋Š” ์ด๋ฏธ ์ˆ˜์‹ญ์–ต ๊ฑด์˜ ๋กœ๊ทธ๊ฐ€ ์Œ“์—ฌ์žˆ์—ˆ๊ณ , user.preferences.preferRegion ํ•„๋“œ๋Š” ์กด์žฌํ–ˆ์ง€๋งŒ ๊ฐ€์ค‘์น˜ ํ•„๋“œ๋Š” ์—†๋Š” ์ƒํ™ฉ์ด์—ˆ๋‹ค. ๋”์šฑ์ด ์ด ๋ฌธ์ œ๋Š” ์—„์ฒญ๋‚œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ณ„์† ์œ ์ž…๋˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์ด์—ˆ๋‹ค.

 

 

 

์ฒซ ๋ฒˆ์งธ ์ ‘๊ทผ: Update By Query์˜ ํ•จ์ •

์ฒ˜์Œ ์ƒ๊ฐ์€ ๊ฐ„๋‹จํ–ˆ๋‹ค. "๊ทธ๋ƒฅ update_by_query๋กœ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฉด ๋˜๊ฒ ๋„ค!"

POST /*-log-[service]-*/_update_by_query
{
  "script": {
    "source": """
    def preferRegion = ctx._source.user?.preferences?.preferRegion;
    def size = 0;
    
    if (preferRegion instanceof String) {
      size = (preferRegion == "ALL") ? 0 : 1;
    } else if (preferRegion instanceof List) {
      size = preferRegion.size();
    }
    
    if (size >= 5) ctx._source.preferRegion_weight = 0.2;
    else if (size == 4) ctx._source.preferRegion_weight = 0.25;
    // ... ์ดํ•˜ ์ƒ๋žต
    """
  }
}

ํ…Œ์ŠคํŠธ ์ธ๋ฑ์Šค๋กœ ๋Œ๋ ค๋ณด๋‹ˆ๊นŒ... 1N์–ต ๊ฑด ์ฒ˜๋ฆฌํ•ด์•ผํ•˜๋Š”๋ฐ ๋Œ€๋žต 80์‹œ๊ฐ„์ด ๊ฑธ๋ฆด ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ๋‚˜์™”๋‹ค. ์ด๊ฑฐ ์šด์˜์—์„œ ๋Œ๋ฆฌ๋ฉด ์„œ๋น„์Šค ํ„ฐ์ง„๋‹ค. ๊ทผ๋ฐ ๋กœ๊ทธ ํŠน์„ฑ์ƒ ์ตœ์‹  ๋กœ๊ทธ๋ฅผ ๋ฐ˜์˜ํ•˜๋ฉด ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ , ์ตœ๊ทผ 24์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ถ€ํ„ฐ ์ฒ˜๋ฆฌํ•ด๋ณผ๊นŒ?" ์‹ถ์–ด์„œ ๋ฒ”์œ„๋ฅผ ์ขํ˜€์„œ ์‹œ๋„ํ•ด๋ดค๋‹ค.

 

 

```json

POST /*-log-[service]-*/_update_by_query
{
  "query": {
    "range": {
      "@timestamp": {
        "gte": "now-24h"
      }
    }
  },
  "script": {
    "source": "..."
  }
}

 

 

์ตœ๊ทผ ํ•˜๋ฃจ ๋ฐ์ดํ„ฐ๋Š” ๋Œ€๋žต 1000๋งŒ ๊ฑด ์ •๋„์˜€๋Š”๋ฐ, ์ด๊ฒƒ๋„ 30๋ถ„์€ ๊ฑธ๋ ธ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฌด์—‡๋ณด๋‹ค ์น˜๋ช…์ ์ธ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค - ์ƒˆ๋กœ ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ์—๋Š” ์—ฌ์ „ํžˆ ๊ฐ€์ค‘์น˜๊ฐ€ ์ถ”๊ฐ€๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ! ๋งค์ผ ์ด๋Ÿฐ ์‹์œผ๋กœ ๋ฐฐ์น˜๋ฅผ ๋Œ๋ ค์•ผ ํ•œ๋‹ค๋ฉด ์šด์˜ ๋ถ€๋‹ด์ด ๋„ˆ๋ฌด ํด ๊ฒƒ ๊ฐ™์•˜๋‹ค.

 

 

 

๋‘ ๋ฒˆ์งธ ์‹œ๋„: Ingest Pipeline ๋ฐœ๊ฒฌ

๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด๋‹ค๊ฐ€ Ingest Pipeline์ด๋ผ๋Š” ๊ฑธ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค. ์ƒˆ๋กœ ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ์— ์ž๋™์œผ๋กœ ์ „์ฒ˜๋ฆฌ๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด์—ˆ๋‹ค.

 
json
PUT _ingest/pipeline/add_region_weight
{
  "description": "Add preferRegion_weight based on preferRegion size",
  "processors": [
    {
      "script": {
        "source": """
        if (ctx.user?.preferences?.preferRegion != null) {
          def preferRegion = ctx.user.preferences.preferRegion;
          def size = 0;
          
          if (preferRegion instanceof String) {
            size = (preferRegion == "ALL") ? 0 : 1;
          } else if (preferRegion instanceof List) {
            size = preferRegion.size();
          }
          
          if (size >= 5) ctx.preferRegion_weight = 0.2;
          else if (size == 4) ctx.preferRegion_weight = 0.25;
          else if (size == 3) ctx.preferRegion_weight = 0.33;
          else if (size == 2) ctx.preferRegion_weight = 0.5;
          else ctx.preferRegion_weight = 1.0;
        } else {
          ctx.preferRegion_weight = 1.0;
        }
        """
      }
    }
  ]
}

ํ…Œ์ŠคํŠธํ•ด๋ณด๋‹ˆ ์ž˜ ์ž‘๋™ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ ์ฒซ ๋ฒˆ์งธ ์‚ฝ์งˆ์ด ์‹œ์ž‘๋˜์—ˆ๋‹ค.

 

 

 

์„ธ ๋ฒˆ์งธ ๋‚œ๊ด€: Index Template ์šฐ์„ ์ˆœ์œ„ ์ง€์˜ฅ

ํŒŒ์ดํ”„๋ผ์ธ๋งŒ ๋งŒ๋“ค์–ด์„œ๋Š” ์ƒˆ๋กœ ์ƒ์„ฑ๋˜๋Š” ์ธ๋ฑ์Šค์— ์ž๋™์œผ๋กœ ์ ์šฉ๋˜์ง€ ์•Š์•˜๋‹ค. Index Template์ด ํ•„์š”ํ–ˆ๋‹ค.

 
json
PUT _index_template/log-service-template
{
  "index_patterns": ["*-log-[service]-*"],
  "priority": 200,
  "template": {
    "settings": {
      "index.default_pipeline": "add_region_weight"
    }
  }
}

๊ทธ๋Ÿฐ๋ฐ ์ด๊ฒŒ ์›ฌ๊ฑธ? ์—๋Ÿฌ๊ฐ€ ํ„ฐ์กŒ๋‹ค.

 
index template [log-service-template] has index patterns [*-log-[service]-*] 
matching patterns from existing templates with patterns that have the same priority [200]

์•Œ๊ณ  ๋ณด๋‹ˆ Elasticsearch์—๋Š” ์ˆ˜๋งŽ์€ ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ๋“ค์ด ์žˆ์—ˆ๊ณ , ๋‹ค๋“ค 200์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. 500์œผ๋กœ ์˜ฌ๋ ค๋„ ์•ˆ ๋˜๊ณ , 1000์œผ๋กœ ์˜ฌ๋ ค๋„ ์•ˆ ๋˜๊ณ ... ๊ฒฐ๊ตญ 3000์œผ๋กœ ์˜ฌ๋ ค์„œ์•ผ ์„ฑ๊ณตํ–ˆ๋‹ค.

 
json
{
  "index_patterns": ["*-log-[service]-*"],
  "priority": 3000,
  "template": {
    "settings": {
      "index.default_pipeline": "add_region_weight"
    }
  }
}

 

 

 

๋„ค ๋ฒˆ์งธ ๊นจ๋‹ฌ์Œ: ๊ธฐ์กด ์ธ๋ฑ์Šค๋Š” ๋”ฐ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค

Template์€ ์ƒˆ๋กœ ์ƒ์„ฑ๋˜๋Š” ์ธ๋ฑ์Šค์—๋งŒ ์ ์šฉ๋œ๋‹ค๋Š” ๊ฑธ ๋’ค๋Šฆ๊ฒŒ ์•Œ์•˜๋‹ค. ๊ธฐ์กด ์ธ๋ฑ์Šค๋“ค์€ ์ˆ˜๋™์œผ๋กœ ํŒŒ์ดํ”„๋ผ์ธ์„ ์ ์šฉํ•ด์•ผ ํ–ˆ๋‹ค.

 
json
PUT /*-log-[service]-*/_settings
{
  "index.default_pipeline": "add_region_weight"
}

๋‹คํ–‰ํžˆ ์ด ์ž‘์—…์€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋งŒ ์ˆ˜์ •ํ•˜๋Š” ๊ฑฐ๋ผ ๋ช‡ ์ดˆ ๋งŒ์— ๋๋‚ฌ๋‹ค.

๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฑด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. _simulate API๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํŒŒ์ดํ”„๋ผ์ธ์„ ํ…Œ์ŠคํŠธํ•ด๋ดค๋‹ค.

 
json
POST _ingest/pipeline/add_region_weight/_simulate
{
  "docs": [
    {
      "_source": {
        "user": {
          "preferences": {
            "preferRegion": ["KR", "US"]
          }
        }
      }
    }
  ]
}

๊ฒฐ๊ณผ๋ฅผ ๋ณด๋‹ˆ ์ •ํ™•ํžˆ 0.5๊ฐ€ ๋‚˜์™”๋‹ค. ๋“œ๋””์–ด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•œ๋‹ค!

 

 

 

์šด์˜ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์ง€์†์ ์ธ ๊ด€๋ฆฌ

ํŒŒ์ดํ”„๋ผ์ธ์„ ์ ์šฉํ•œ ํ›„์—๋Š” ์ง€์†์ ์ธ ๋ชจ๋‹ˆํ„ฐ๋ง์ด ํ•„์š”ํ–ˆ๋‹ค. ์‹ค์ œ ๋ฐ์ดํ„ฐ์— ํ•„๋“œ๊ฐ€ ์ž˜ ์ถ”๊ฐ€๋˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

 
json
GET /*-log-[service]-*/_search
{
  "query": {
    "range": {
      "@timestamp": {
        "gte": "now-1h"
      }
    }
  },
  "_source": ["@timestamp", "preferRegion_weight", "user.preferences.preferRegion"],
  "size": 10
}

๊ฐ€์ค‘์น˜ ๋ถ„ํฌ๋„ ํ™•์ธํ•ด์„œ ์˜๋„ํ•œ ๋Œ€๋กœ ์ฒ˜๋ฆฌ๋˜๊ณ  ์žˆ๋Š”์ง€ ์ฒดํฌํ–ˆ๋‹ค.

 
json
GET /*-log-[service]-*/_search
{
  "size": 0,
  "aggs": {
    "weight_distribution": {
      "terms": {
        "field": "preferRegion_weight",
        "size": 10
      }
    }
  }
}

 

 

 

๋งˆ์ง€๋ง‰ ํ•จ์ •: Kibana์—์„œ ํ•„๋“œ๊ฐ€ ์•ˆ ๋ณด์ธ๋‹ค

Kibana Discover์—์„œ ์ƒˆ ํ•„๋“œ๊ฐ€ ๋ณด์ด์ง€ ์•Š์•˜๋‹ค. ์•Œ๊ณ  ๋ณด๋‹ˆ Data View์—์„œ ํ•„๋“œ ๋ชฉ๋ก์„ ์ƒˆ๋กœ๊ณ ์นจํ•ด์•ผ ํ–ˆ๋‹ค. Stack Management → Data Views → Refresh field list๋ฅผ ๋ˆ„๋ฅด๋‹ˆ๊นŒ ๋“œ๋””์–ด ํ•„๋“œ๊ฐ€ ๋ณด์˜€๋‹ค.

 

 

 

๋งˆ์น˜๋ฉฐ

์ด๋ฒˆ ์ž‘์—…์„ ํ†ตํ•ด ๋ฐฐ์šด ๊ฒƒ๋“ค์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

Ingest Pipeline ์ž˜์“ฐ์ž: Update By Query๋กœ ๋ช‡ ์‹œ๊ฐ„ ๊ฑธ๋ฆด ์ž‘์—…์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์ƒˆ๋กœ ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ์— ์ž๋™์œผ๋กœ ์ ์šฉ๋˜๋‹ˆ๊นŒ ํ•œ ๋ฒˆ ์„ค์ •ํ•ด๋†“์œผ๋ฉด ๋์ด๋‹ค.

Index Template ์šฐ์„ ์ˆœ์œ„: Elasticsearch ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ๋“ค์ด ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ๋‹ค. ์‚ฌ์šฉ์ž ์ •์˜ ํ…œํ”Œ๋ฆฟ์€ 1000 ์ด์ƒ์˜ ๋†’์€ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์•ˆ์ „ํ•˜๋‹ค.

๋‹จ๊ณ„๋ณ„ ๊ฒ€์ฆ์˜ ์ค‘์š”์„ฑ: _simulate API๋กœ ํŒŒ์ดํ”„๋ผ์ธ ๋กœ์ง์„ ๋ฏธ๋ฆฌ ๊ฒ€์ฆํ•˜๊ณ , ์‹ค์ œ ๋ฐ์ดํ„ฐ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ณ , Kibana UI๊นŒ์ง€ ํ™•์ธํ•˜๋Š” ์ „์ฒด ํ”Œ๋กœ์šฐ๋ฅผ ๊ฒ€์ฆํ•ด์•ผ ํ•œ๋‹ค.

์ง€์†์  ๋ชจ๋‹ˆํ„ฐ๋ง: ํŒŒ์ดํ”„๋ผ์ธ์„ ์ ์šฉํ•œ ํ›„์—๋„ ์‹ค์ œ ๋ฐ์ดํ„ฐ ์ ์šฉ๋ฅ , ์„ฑ๋Šฅ ์˜ํ–ฅ, ๊ฐ€์ค‘์น˜ ๋ถ„ํฌ ๋“ฑ์„ ์ง€์†์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•ด์•ผ ํ•œ๋‹ค.

๊ฒฐ๊ตญ ์ฒ˜์Œ ์˜ˆ์ƒํ–ˆ๋˜ ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ๋ณต์žกํ–ˆ์ง€๋งŒ, ํ•œ ๋ฒˆ ์ œ๋Œ€๋กœ ๊ตฌ์ถ•ํ•ด๋†“์œผ๋‹ˆ ๋งค์ผ ์ˆ˜๋ฐฑ๋งŒ ๊ฑด์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž๋™์œผ๋กœ ์ „์ฒ˜๋ฆฌ๋˜๊ณ  ์žˆ๋‹ค.

๋ฐ˜์‘ํ˜•