Spec-Zone.ru › Elasticsearch 8
›Elasticsearch Guide [8.17] ›Оптимизации

Настройка для скорости поиска

Предоставление памяти кешу файловой системы

Elasticsearch сильно полагается на кеш файловой системы, чтобы сделать поиск быстрым. В целом, вы должны убедиться, что как минимум половина доступной памяти выделена кешу файловой системы, чтобы Elasticsearch мог хранить горячие области индекса в физической памяти.

Избегайте фрагментации кеша страниц, используя умеренные значения readahead в Linux

Поиск может вызвать много случайных операций ввода-вывода. Когда у базового блочного устройства высокое значение readahead, может быть выполнено много ненужных операций ввода-вывода, особенно когда файлы открываются с помощью отображения в память (см. типы хранения).

Большинство дистрибутивов Linux используют разумное значение readahead в 128KiB для одного простого устройства, однако, при использовании программного RAID, LVM или dm-crypt, результирующее блочное устройство (поддерживающее Elasticsearch путь path.data) может иметь очень большое значение readahead (в диапазоне нескольких мегабайт). Это обычно приводит к серьезной фрагментации кеша страниц (файловой системы), что отрицательно сказывается на производительности поиска (или обновления).

Вы можете проверить текущее значение в KiB, используя lsblk -o NAME,RA,MOUNTPOINT,TYPE,SIZE. Обратитесь к документации вашего дистрибутива, чтобы узнать, как изменить это значение (например, с помощью правила udev для сохранения после перезагрузки или через blockdev --setra как временную настройку). Мы рекомендуем значение 128KiB для readahead.

blockdev ожидает значения в секторах по 512 байт, в то время как lsblk сообщает значения в KiB. В качестве примера, чтобы временно установить readahead на 128KiB для /dev/nvme0n1, укажите blockdev --setra 256 /dev/nvme0n1.

Используйте более быстрое оборудование

Если ваши поисковые запросы ограничены операциями ввода-вывода, рассмотрите увеличение размера кеша файловой системы (см. выше) или использование более быстрого хранилища. Каждый поиск включает смесь последовательных и случайных чтений по нескольким файлам, и может быть много одновременных поисков на каждом фрагменте, поэтому твердотельные накопители (SSD) обычно работают лучше, чем жесткие диски с вращающимися дисками.

Если ваши поисковые запросы ограничены процессором, рассмотрите использование большего количества более быстрых процессоров.

Локальное хранилище против удаленного хранилища

Подключенное напрямую (локальное) хранилище, как правило, работает лучше, чем удаленное хранилище, потому что его проще настроить и оно избегает накладных расходов на общение.

Некоторые удаленные хранилища работают очень плохо, особенно при нагрузке, которую накладывает Elasticsearch. Однако с тщательной настройкой иногда можно достичь приемлемой производительности и с использованием удаленного хранилища. Перед принятием решения об определенной архитектуре хранилища, протестируйте свою систему с реалистичной рабочей нагрузкой, чтобы определить влияние параметров настройки. Если вы не можете достичь ожидаемой производительности, обратитесь к поставщику вашей системы хранения данных, чтобы определить проблему.

Моделирование документов

Документы должны быть смоделированы таким образом, чтобы операции во время поиска были как можно дешевле.

В частности, следует избегать объединений. nested может сделать запросы на несколько порядков медленнее, а отношения родитель-ребенок могут сделать запросы на сотни порядков медленнее. Поэтому, если те же вопросы можно ответить без объединений, денормализуя документы, можно ожидать значительного ускорения.

Используйте как можно меньше полей поиска

Чем больше полей нацелены запрос query_string или multi_match, тем медленнее он выполняется. Общим методом повышения скорости поиска по нескольким полям является копирование их значений в одно поле во время индексирования, а затем использование этого поля при поиске. Это можно автоматизировать с помощью директивы copy-to сопоставлений, не изменяя исходные документы. Вот пример индекса, содержащего фильмы, который оптимизирует запросы, выполняющие поиск по названию и сюжету фильма, индексируя оба значения в поле name_and_plot.

resp = client.indices.create(
    index="movies",
    mappings={
        "properties": {
            "name_and_plot": {
                "type": "text"
            },
            "name": {
                "type": "text",
                "copy_to": "name_and_plot"
            },
            "plot": {
                "type": "text",
                "copy_to": "name_and_plot"
            }
        }
    },
)
print(resp)
response = client.indices.create(
  index: 'movies',
  body: {
    mappings: {
      properties: {
        name_and_plot: {
          type: 'text'
        },
        name: {
          type: 'text',
          copy_to: 'name_and_plot'
        },
        plot: {
          type: 'text',
          copy_to: 'name_and_plot'
        }
      }
    }
  }
)
puts response
const response = await client.indices.create({
  index: "movies",
  mappings: {
    properties: {
      name_and_plot: {
        type: "text",
      },
      name: {
        type: "text",
        copy_to: "name_and_plot",
      },
      plot: {
        type: "text",
        copy_to: "name_and_plot",
      },
    },
  },
});
console.log(response);
PUT movies
{
  "mappings": {
    "properties": {
      "name_and_plot": {
        "type": "text"
      },
      "name": {
        "type": "text",
        "copy_to": "name_and_plot"
      },
      "plot": {
        "type": "text",
        "copy_to": "name_and_plot"
      }
    }
  }
}

Предварительная индексация данных

Вы должны использовать шаблоны в своих запросах, чтобы оптимизировать способ индексирования данных. Например, если у всех ваших документов есть поле price и большинство запросов выполняют агрегации range по фиксированному списку диапазонов, вы можете ускорить эту агрегацию, предварительно индексируя диапазоны в индекс и используя агрегации terms.

Например, если документы выглядят так:

resp = client.index(
    index="index",
    id="1",
    document={
        "designation": "spoon",
        "price": 13
    },
)
print(resp)
response = client.index(
  index: 'index',
  id: 1,
  body: {
    designation: 'spoon',
    price: 13
  }
)
puts response
const response = await client.index({
  index: "index",
  id: 1,
  document: {
    designation: "spoon",
    price: 13,
  },
});
console.log(response);
PUT index/_doc/1
{
  "designation": "spoon",
  "price": 13
}

и запросы поиска выглядят так:

resp = client.search(
    index="index",
    aggs={
        "price_ranges": {
            "range": {
                "field": "price",
                "ranges": [
                    {
                        "to": 10
                    },
                    {
                        "from": 10,
                        "to": 100
                    },
                    {
                        "from": 100
                    }
                ]
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'index',
  body: {
    aggregations: {
      price_ranges: {
        range: {
          field: 'price',
          ranges: [
            {
              to: 10
            },
            {
              from: 10,
              to: 100
            },
            {
              from: 100
            }
          ]
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "index",
  aggs: {
    price_ranges: {
      range: {
        field: "price",
        ranges: [
          {
            to: 10,
          },
          {
            from: 10,
            to: 100,
          },
          {
            from: 100,
          },
        ],
      },
    },
  },
});
console.log(response);
GET index/_search
{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 10 },
          { "from": 10, "to": 100 },
          { "from": 100 }
        ]
      }
    }
  }
}

Тогда документы можно обогатить полем price_range во время индексирования, которое должно быть отображено как keyword:

resp = client.indices.create(
    index="index",
    mappings={
        "properties": {
            "price_range": {
                "type": "keyword"
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="index",
    id="1",
    document={
        "designation": "spoon",
        "price": 13,
        "price_range": "10-100"
    },
)
print(resp1)
response = client.indices.create(
  index: 'index',
  body: {
    mappings: {
      properties: {
        price_range: {
          type: 'keyword'
        }
      }
    }
  }
)
puts response

response = client.index(
  index: 'index',
  id: 1,
  body: {
    designation: 'spoon',
    price: 13,
    price_range: '10-100'
  }
)
puts response
const response = await client.indices.create({
  index: "index",
  mappings: {
    properties: {
      price_range: {
        type: "keyword",
      },
    },
  },
});
console.log(response);

const response1 = await client.index({
  index: "index",
  id: 1,
  document: {
    designation: "spoon",
    price: 13,
    price_range: "10-100",
  },
});
console.log(response1);
PUT index
{
  "mappings": {
    "properties": {
      "price_range": {
        "type": "keyword"
      }
    }
  }
}

PUT index/_doc/1
{
  "designation": "spoon",
  "price": 13,
  "price_range": "10-100"
}

А затем запросы поиска могли бы агрегировать это новое поле, а не запускать агрегацию range по полю price.

resp = client.search(
    index="index",
    aggs={
        "price_ranges": {
            "terms": {
                "field": "price_range"
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'index',
  body: {
    aggregations: {
      price_ranges: {
        terms: {
          field: 'price_range'
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "index",
  aggs: {
    price_ranges: {
      terms: {
        field: "price_range",
      },
    },
  },
});
console.log(response);
GET index/_search
{
  "aggs": {
    "price_ranges": {
      "terms": {
        "field": "price_range"
      }
    }
  }
}

Рассмотрите сопоставление идентификаторов как keyword

Не все числовые данные должны быть сопоставлены как данные типа поля числового поля. Elasticsearch оптимизирует числовые поля, такие как integer или long, для запросов range. Однако поля keyword лучше подходят для запросов term и других запросов на уровне термов.

Идентификаторы, такие как ISBN или идентификатор продукта, редко используются в запросах range. Однако они часто извлекаются с помощью запросов на уровне термов.

Рассмотрите сопоставление числового идентификатора как keyword, если:

  • Вы не планируете искать данные идентификатора с помощью запросов range.
  • Быстрое получение данных имеет важное значение. Поисковые запросы на основе запроса term по полям keyword часто быстрее, чем запросы term по числовым полям.

Если вы не уверены, какой из них использовать, вы можете использовать многопольное поле для сопоставления данных как keyword и числового типа данных.

Избегайте скриптов

Если это возможно, избегайте использования сортировки на основе скриптов, скриптов в агрегациях и запроса script_score. См. Скрипты, кэширование и скорость поиска.

Поиск округленных дат

Запросы к полям дат, которые используют now, обычно не могут быть кэшированы, так как диапазон, который соответствует, постоянно меняется. Однако переключение на округленную дату часто приемлемо с точки зрения пользовательского опыта и имеет преимущество в лучшем использовании кэша запросов.

Например, запрос ниже:

resp = client.index(
    index="index",
    id="1",
    document={
        "my_date": "2016-05-11T16:30:55.328Z"
    },
)
print(resp)

resp1 = client.search(
    index="index",
    query={
        "constant_score": {
            "filter": {
                "range": {
                    "my_date": {
                        "gte": "now-1h",
                        "lte": "now"
                    }
                }
            }
        }
    },
)
print(resp1)
response = client.index(
  index: 'index',
  id: 1,
  body: {
    my_date: '2016-05-11T16:30:55.328Z'
  }
)
puts response

response = client.search(
  index: 'index',
  body: {
    query: {
      constant_score: {
        filter: {
          range: {
            my_date: {
              gte: 'now-1h',
              lte: 'now'
            }
          }
        }
      }
    }
  }
)
puts response
const response = await client.index({
  index: "index",
  id: 1,
  document: {
    my_date: "2016-05-11T16:30:55.328Z",
  },
});
console.log(response);

const response1 = await client.search({
  index: "index",
  query: {
    constant_score: {
      filter: {
        range: {
          my_date: {
            gte: "now-1h",
            lte: "now",
          },
        },
      },
    },
  },
});
console.log(response1);
PUT index/_doc/1
{
  "my_date": "2016-05-11T16:30:55.328Z"
}

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h",
            "lte": "now"
          }
        }
      }
    }
  }
}

может быть заменен следующим запросом:

resp = client.search(
    index="index",
    query={
        "constant_score": {
            "filter": {
                "range": {
                    "my_date": {
                        "gte": "now-1h/m",
                        "lte": "now/m"
                    }
                }
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'index',
  body: {
    query: {
      constant_score: {
        filter: {
          range: {
            my_date: {
              gte: 'now-1h/m',
              lte: 'now/m'
            }
          }
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "index",
  query: {
    constant_score: {
      filter: {
        range: {
          my_date: {
            gte: "now-1h/m",
            lte: "now/m",
          },
        },
      },
    },
  },
});
console.log(response);
GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h/m",
            "lte": "now/m"
          }
        }
      }
    }
  }
}

В этом случае мы округляем до минуты, поэтому, если текущее время 16:31:29, запрос по диапазону будет соответствовать всем значениям, у которых значение поля my_date находится между 15:31:00 и 16:31:59. И если несколько пользователей выполнят запрос, который содержит этот диапазон в одной и той же минуте, кэш запросов может немного ускорить процесс. Чем больше интервал, используемый для округления, тем больше кэш запросов может помочь, но будьте осторожны, так как слишком агрессивное округление может навредить пользовательскому опыту.

Может показаться заманчивым разделить диапазоны на большую кэшируемую часть и меньшие некэшируемые части, чтобы использовать кэш запросов, как показано ниже:

resp = client.search(
    index="index",
    query={
        "constant_score": {
            "filter": {
                "bool": {
                    "should": [
                        {
                            "range": {
                                "my_date": {
                                    "gte": "now-1h",
                                    "lte": "now-1h/m"
                                }
                            }
                        },
                        {
                            "range": {
                                "my_date": {
                                    "gt": "now-1h/m",
                                    "lt": "now/m"
                                }
                            }
                        },
                        {
                            "range": {
                                "my_date": {
                                    "gte": "now/m",
                                    "lte": "now"
                                }
                            }
                        }
                    ]
                }
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'index',
  body: {
    query: {
      constant_score: {
        filter: {
          bool: {
            should: [
              {
                range: {
                  my_date: {
                    gte: 'now-1h',
                    lte: 'now-1h/m'
                  }
                }
              },
              {
                range: {
                  my_date: {
                    gt: 'now-1h/m',
                    lt: 'now/m'
                  }
                }
              },
              {
                range: {
                  my_date: {
                    gte: 'now/m',
                    lte: 'now'
                  }
                }
              }
            ]
          }
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "index",
  query: {
    constant_score: {
      filter: {
        bool: {
          should: [
            {
              range: {
                my_date: {
                  gte: "now-1h",
                  lte: "now-1h/m",
                },
              },
            },
            {
              range: {
                my_date: {
                  gt: "now-1h/m",
                  lt: "now/m",
                },
              },
            },
            {
              range: {
                my_date: {
                  gte: "now/m",
                  lte: "now",
                },
              },
            },
          ],
        },
      },
    },
  },
});
console.log(response);
GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "should": [
            {
              "range": {
                "my_date": {
                  "gte": "now-1h",
                  "lte": "now-1h/m"
                }
              }
            },
            {
              "range": {
                "my_date": {
                  "gt": "now-1h/m",
                  "lt": "now/m"
                }
              }
            },
            {
              "range": {
                "my_date": {
                  "gte": "now/m",
                  "lte": "now"
                }
              }
            }
          ]
        }
      }
    }
  }
}

Однако такая практика может сделать запрос медленнее в некоторых случаях, так как издержки, вносимые запросом bool, могут свести на нет экономию, полученную от лучшего использования кэша запросов.

Принудительное слияние индексов только для чтения

Индексы, которые находятся только для чтения, могут получить выгоду от слияния до одного сегмента. Это обычно характерно для индексов, основанных на времени: только индекс текущего временного интервала получает новые документы, а более старые индексы находятся только для чтения. Фрагменты, которые были принудительно объединены в один сегмент, могут использовать более простые и эффективные структуры данных для выполнения поиска.

Не следует принудительно объединять индексы, в которые вы всё ещё записываете или в которые будете записывать в будущем. Вместо этого полагайтесь на автоматический фоновый процесс слияния, который выполняет слияния по мере необходимости, чтобы индекс работал гладко. Если вы продолжаете записывать в принудительно объединённый индекс, его производительность может значительно ухудшиться.

Разминка глобальных порядковых номеров

Глобальные порядковые номера — это структура данных, используемая для оптимизации производительности агрегаций. Они вычисляются лениво и хранятся в куче JVM как часть кэша данных полей. Для полей, которые активно используются в агрегациях по корзинам, вы можете указать Elasticsearch на создание и кэширование глобальных порядковых номеров до получения запросов. Это следует делать осторожно, так как это увеличит использование кучи и может привести к тому, что обновления будут занимать больше времени. Параметр можно динамически обновить в существующей структуре отображения, установив параметр отображения eager global ordinals:

resp = client.indices.create(
    index="index",
    mappings={
        "properties": {
            "foo": {
                "type": "keyword",
                "eager_global_ordinals": True
            }
        }
    },
)
print(resp)
response = client.indices.create(
  index: 'index',
  body: {
    mappings: {
      properties: {
        foo: {
          type: 'keyword',
          eager_global_ordinals: true
        }
      }
    }
  }
)
puts response
const response = await client.indices.create({
  index: "index",
  mappings: {
    properties: {
      foo: {
        type: "keyword",
        eager_global_ordinals: true,
      },
    },
  },
});
console.log(response);
PUT index
{
  "mappings": {
    "properties": {
      "foo": {
        "type": "keyword",
        "eager_global_ordinals": true
      }
    }
  }
}

Разминка кэша файловой системы

Если машина, на которой работает Elasticsearch, перезагружается, кэш файловой системы будет пустым, поэтому потребуется некоторое время, чтобы операционная система загрузила горячие области индекса в память, чтобы операции поиска выполнялись быстро. Вы можете явно указать операционной системе, какие файлы следует загружать в память заранее в зависимости от расширения файла, используя параметр index.store.preload.

Загрузка данных в кэш файловой системы с опережением для слишком большого количества индексов или файлов сделает поиск медленнее, если кэш файловой системы недостаточно велик для хранения всех данных. Используйте с осторожностью.

Использование сортировки индекса для ускорения конъюнкций

Сортировка индекса может быть полезна для ускорения конъюнкций за счёт немного более медленной индексации. Дополнительную информацию можно найти в документации по сортировке индекса.

Использование preference для оптимизации использования кэша

Существует несколько кэшей, которые могут помочь повысить производительность поиска, таких как кэш файловой системы, кэш запросов или кэш запросов. Однако все эти кэши поддерживаются на уровне узла, а это значит, что если вы дважды выполняете один и тот же запрос подряд, у вас есть 1 репликация или более и используется round-robin, то алгоритм маршрутизации по умолчанию, то эти два запроса будут направлены в разные копии фрагментов, препятствуя использованию кэшей на уровне узла.

Поскольку пользователи поискового приложения часто выполняют похожие запросы один за другим, например, для анализа более узкой подвыборки индекса, использование значения предпочтения, идентифицирующего текущего пользователя или сеанс, может помочь оптимизировать использование кэшей.

Реплики могут помочь с пропускной способностью, но не всегда

Помимо повышения устойчивости, реплики могут повысить пропускную способность. Например, если у вас есть индекс с одним фрагментом и тремя узлами, вам нужно установить количество реплик в 2, чтобы общее количество копий вашего фрагмента составляло 3, и все узлы использовались.

Теперь представьте, что у вас есть индекс с 2 фрагментами и двумя узлами. В одном случае количество реплик равно 0, а это значит, что каждый узел содержит один фрагмент. Во втором случае количество реплик равно 1, а это означает, что каждый узел содержит два фрагмента. Какой из наборов лучше с точки зрения производительности поиска? Обычно лучше тот набор, в котором меньше фрагментов на узел в целом. Причина в том, что он предоставляет большую долю доступного кэша файловой системы каждому фрагменту, а кэш файловой системы, вероятно, является основным фактором производительности Elasticsearch. В то же время будьте осторожны, что конфигурация без реплик подвержена сбоям в случае сбоя одного узла, поэтому существует компромисс между пропускной способностью и доступностью.

Итак, сколько реплик нужно? Если у вас есть кластер с num_nodes узлами, num_primaries первичными фрагментами в общей сложности и если вы хотите иметь возможность справиться с max_failures отказами узлов одновременно максимум, то подходящее для вас количество реплик равно max(max_failures, ceil(num_nodes / num_primaries) - 1).

Настройка запросов с помощью профилировщика поиска

API Profile предоставляет подробную информацию о том, как каждый компонент ваших запросов и агрегаций влияет на время обработки запроса.

Профилировщик поиска в Kibana делает навигацию и анализ результатов профиля лёгкими и даёт представление о том, как настроить запросы для повышения производительности и снижения нагрузки.

Поскольку сам API профиля добавляет значительные издержки к запросу, эта информация лучше всего подходит для понимания относительной стоимости различных компонентов запроса. Он не даёт надёжной оценки фактического времени обработки.

Более быстрые запросы фраз с index_phrases

Поле text имеет параметр index_phrases, который индексирует 2-шинглы и автоматически используется парсерами запросов для выполнения запросов по фразам, в которых нет разницы в положении слов. Если ваш случай использования включает много запросов по фразам, это может значительно ускорить запросы.

Более быстрые запросы префикса с index_prefixes

Поле text имеет параметр index_prefixes, который индексирует префиксы всех терминов и автоматически используется парсерами запросов для выполнения запросов по префиксу. Если ваш случай использования включает много запросов по префиксу, это может значительно ускорить запросы.

Использование constant_keyword для ускорения фильтрации

Существует общее правило, что стоимость фильтра в основном зависит от количества совпавших документов. Представьте, что у вас есть индекс, содержащий велосипеды. Есть большое количество велосипедов, и многие запросы выполняют фильтрацию по cycle_type: bicycle. Этот очень распространённый фильтр, к сожалению, очень дорогостоящий, так как он сопоставляет большинство документов. Есть простой способ избежать выполнения этого фильтра: перенести велосипеды в отдельный индекс и фильтровать велосипеды путём поиска в этом индексе вместо добавления фильтра в запрос.

К сожалению, это может усложнить логику на стороне клиента, где constant_keyword помогает. Путем отображения cycle_type как constant_keyword со значением bicycle в индексе, содержащем велосипеды, клиенты могут продолжать выполнять те же запросы, что и раньше, на монолитном индексе, и Elasticsearch сделает правильные вещи с индексом велосипедов, проигнорировав фильтры по cycle_type, если значение равно bicycle, и вернув результат без совпадений в противном случае.

Вот как могут выглядеть структуры отображения:

resp = client.indices.create(
    index="bicycles",
    mappings={
        "properties": {
            "cycle_type": {
                "type": "constant_keyword",
                "value": "bicycle"
            },
            "name": {
                "type": "text"
            }
        }
    },
)
print(resp)

resp1 = client.indices.create(
    index="other_cycles",
    mappings={
        "properties": {
            "cycle_type": {
                "type": "keyword"
            },
            "name": {
                "type": "text"
            }
        }
    },
)
print(resp1)
response = client.indices.create(
  index: 'bicycles',
  body: {
    mappings: {
      properties: {
        cycle_type: {
          type: 'constant_keyword',
          value: 'bicycle'
        },
        name: {
          type: 'text'
        }
      }
    }
  }
)
puts response

response = client.indices.create(
  index: 'other_cycles',
  body: {
    mappings: {
      properties: {
        cycle_type: {
          type: 'keyword'
        },
        name: {
          type: 'text'
        }
      }
    }
  }
)
puts response
const response = await client.indices.create({
  index: "bicycles",
  mappings: {
    properties: {
      cycle_type: {
        type: "constant_keyword",
        value: "bicycle",
      },
      name: {
        type: "text",
      },
    },
  },
});
console.log(response);

const response1 = await client.indices.create({
  index: "other_cycles",
  mappings: {
    properties: {
      cycle_type: {
        type: "keyword",
      },
      name: {
        type: "text",
      },
    },
  },
});
console.log(response1);
PUT bicycles
{
  "mappings": {
    "properties": {
      "cycle_type": {
        "type": "constant_keyword",
        "value": "bicycle"
      },
      "name": {
        "type": "text"
      }
    }
  }
}

PUT other_cycles
{
  "mappings": {
    "properties": {
      "cycle_type": {
        "type": "keyword"
      },
      "name": {
        "type": "text"
      }
    }
  }
}

Мы делим наш индекс на два: один, который будет содержать только велосипеды, и другой, который содержит другие типы велосипедов: одноколесные, трёхколесные и т. д.

resp = client.search(
    index="bicycles,other_cycles",
    query={
        "bool": {
            "must": {
                "match": {
                    "description": "dutch"
                }
            },
            "filter": {
                "term": {
                    "cycle_type": "bicycle"
                }
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'bicycles,other_cycles',
  body: {
    query: {
      bool: {
        must: {
          match: {
            description: 'dutch'
          }
        },
        filter: {
          term: {
            cycle_type: 'bicycle'
          }
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "bicycles,other_cycles",
  query: {
    bool: {
      must: {
        match: {
          description: "dutch",
        },
      },
      filter: {
        term: {
          cycle_type: "bicycle",
        },
      },
    },
  },
});
console.log(response);
GET bicycles,other_cycles/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "description": "dutch"
        }
      },
      "filter": {
        "term": {
          "cycle_type": "bicycle"
        }
      }
    }
  }
}

В индексе bicycles Elasticsearch просто проигнорирует фильтр cycle_type и перепишет запрос поиска следующим образом:

resp = client.search(
    index="bicycles,other_cycles",
    query={
        "match": {
            "description": "dutch"
        }
    },
)
print(resp)
response = client.search(
  index: 'bicycles,other_cycles',
  body: {
    query: {
      match: {
        description: 'dutch'
      }
    }
  }
)
puts response
const response = await client.search({
  index: "bicycles,other_cycles",
  query: {
    match: {
      description: "dutch",
    },
  },
});
console.log(response);
GET bicycles,other_cycles/_search
{
  "query": {
    "match": {
      "description": "dutch"
    }
  }
}

В индексе other_cycles Elasticsearch быстро определит, что bicycle не существует в словаре терминов поля cycle_type, и вернёт ответ поиска без совпадений.

Это мощный способ сделать запросы дешевле, поместив общие значения в отдельный индекс. Эту идею также можно комбинировать по нескольким полям: например, если вы отслеживаете цвет каждого велосипеда и в индексе bicycles оказывается, что большинство чёрных велосипедов, вы можете разделить его на индексы bicycles-black и bicycles-other-colors.

Использование constant_keyword не является строго необходимым для этой оптимизации: также можно обновить клиентскую логику, чтобы направлять запросы на соответствующие индексы на основе фильтров. Однако constant_keyword делает это прозрачным и позволяет отделить запросы поиска от топологии индексов в обмен на очень небольшую нагрузку.

© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/tune-for-search-speed.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API