🚀 Попробуйте Zilliz Cloud, полностью управляемый Milvus, бесплатно — ощутите 10-кратное увеличение производительности! Попробовать сейчас>

milvus-logo
LFAI
Главная
  • Концепции
  • Home
  • Docs
  • Концепции

  • Векторный индекс

  • Индекс в памяти

Индекс в памяти

В этой теме перечислены различные типы индексов in-memory, которые поддерживает Milvus, сценарии, для которых каждый из них подходит лучше всего, и параметры, которые пользователи могут настроить для достижения лучшей производительности поиска. Об индексах на диске см. в разделе Индекс на диске.

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

Чтобы повысить производительность запросов, можно указать тип индекса для каждого векторного поля.

В настоящее время векторное поле поддерживает только один тип индекса. При переключении типа индекса Milvus автоматически удаляет старый индекс.

Векторные индексы ANNS

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

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

Индексы, поддерживаемые в Milvus

Milvus поддерживает различные типы индексов, которые делятся по типу векторных вложений, с которыми они работают: вложения с плавающей точкой (также известные как векторы с плавающей точкой или плотные векторы), двоичные вложения (также известные как двоичные векторы) и разреженные вложения (также известные как разреженные векторы).

Индексы для вкраплений с плавающей точкой

Для 128-мерных вкраплений (векторов) с плавающей точкой объем памяти, который они занимают, составляет 128 * размер float = 512 байт. А метрики расстояния, используемые для вкраплений с плавающей точкой, - это евклидово расстояние (L2) и внутреннее произведение (IP).

К таким типам индексов относятся FLAT, IVF_FLAT, IVF_PQ, IVF_SQ8, HNSW, HNSW_SQ, HNSW_PQ, HNSW_PRQ, и SCANN для поиска ANN на базе процессора.

Индексы для бинарных вкраплений

Для 128-мерных бинарных вкраплений объем памяти, который они занимают, составляет 128 / 8 = 16 байт. В качестве метрики расстояния для бинарных вкраплений используются JACCARD и HAMMING.

К этому типу индексов относятся BIN_FLAT и BIN_IVF_FLAT.

Индексы для разреженных вкраплений

Индексы для разреженных вкраплений поддерживают только метрики IP и BM25 (для полнотекстового поиска).

Тип индекса, поддерживаемого для разреженных вкраплений: SPARSE_INVERTED_INDEX.

Начиная с Milvus 2.5.4 и далее, SPARSE_WAND устаревает. Вместо него рекомендуется использовать "inverted_index_algo": "DAAT_WAND" для эквивалентности и сохранения совместимости. Для получения дополнительной информации обратитесь к разделу "Разреженный вектор".

Поддерживаемый индекс Классификация Сценарий
ПЛОСКИЙ N/A
  • Относительно небольшой набор данных
  • Требуется 100-процентный коэффициент отзыва
ЭКО_FLAT N/A
  • Высокоскоростной запрос
  • Требуется как можно более высокий коэффициент запоминания
IVF_SQ8 Индекс на основе квантования
  • Очень высокоскоростной запрос
  • Ограниченные ресурсы памяти
  • Допускает незначительный компромисс в скорости запоминания
IVF_PQ Индекс на основе квантования
  • Высокоскоростной запрос
  • Ограниченные ресурсы памяти
  • Допускает незначительный компромисс в скорости запоминания
HNSW Индекс на основе графов
  • Очень высокоскоростной запрос
  • Требуется как можно более высокая скорость запоминания
  • Большие ресурсы памяти
HNSW_SQ Индекс на основе квантования
  • Очень высокоскоростной запрос
  • Ограниченные ресурсы памяти
  • Допускает незначительный компромисс в скорости запоминания
HNSW_PQ Индекс на основе квантования
  • Среднескоростной запрос
  • Очень ограниченные ресурсы памяти
  • Допускает незначительный компромисс в скорости запоминания
HNSW_PRQ Индекс на основе квантования
  • Запрос со средней скоростью
  • Очень ограниченные ресурсы памяти
  • Допускает незначительный компромисс в скорости запоминания
SCANN Индекс на основе квантования
  • Очень высокоскоростной запрос
  • Требуется как можно более высокая скорость запоминания
  • Большие ресурсы памяти
Поддерживаемый индекс Классификация Сценарий
BIN_FLAT Индекс на основе квантования
  • Зависит от относительно небольших наборов данных.
  • Требуется идеальная точность.
  • Сжатие не применяется.
  • Гарантирует точные результаты поиска.
BIN_IVF_FLAT Индекс на основе квантования
  • Высокоскоростной запрос
  • Требуется как можно более высокий коэффициент отзыва
Поддерживаемый индекс Классификация Сценарий
РАЗРЕЖЕННЫЙ_ИНВЕРТИРОВАННЫЙ_ИНДЕКС Инвертированный индекс
  • Зависит от относительно небольших наборов данных.
  • Требуется 100-процентный коэффициент отзыва.

FLAT

Для приложений поиска векторного сходства, требующих идеальной точности и зависящих от относительно небольших (миллионных) наборов данных, индекс FLAT является хорошим выбором. FLAT не сжимает векторы и является единственным индексом, который может гарантировать точные результаты поиска. Результаты, полученные с помощью FLAT, можно также использовать для сравнения с результатами, полученными с помощью других индексов, которые имеют менее чем 100-процентный отзыв.

Точность FLAT объясняется тем, что он использует исчерпывающий подход к поиску, то есть для каждого запроса целевой входной сигнал сравнивается с каждым набором векторов в наборе данных. Это делает FLAT самым медленным индексом в нашем списке и плохо подходит для запросов к массивным векторным данным. Для индекса FLAT в Milvus не требуется никаких параметров, и его использование не требует обучения данных.

  • Параметры поиска

    ПараметрОписаниеДиапазон
    metric_type[Необязательно] Выбранная метрика расстояния.См. раздел Поддерживаемые метрики.

IVF_FLAT

IVF_FLAT делит векторные данные на кластеры nlist, а затем сравнивает расстояния между целевым входным вектором и центром каждого кластера. В зависимости от количества кластеров, к которым система настроена на запрос (nprobe), результаты поиска сходства возвращаются на основе сравнений между целевым входным вектором и векторами только в наиболее похожих кластерах, что значительно сокращает время запроса.

Настраивая nprobe, можно найти идеальный баланс между точностью и скоростью для конкретного сценария. Результаты тестирования производительности IVF_FLAT показывают, что время выполнения запроса резко возрастает при увеличении как количества векторов целевого входа (nq), так и количества кластеров для поиска (nprobe).

IVF_FLAT является самым базовым индексом ЭКО, и закодированные данные, хранящиеся в каждом блоке, соответствуют исходным данным.

  • Параметры построения индекса

    ПараметрОписаниеДиапазонЗначение по умолчанию
    nlistКоличество единиц кластера[1, 65536]128
  • Параметры поиска

    • Общий поиск

      ПараметрОписаниеДиапазонЗначение по умолчанию
      nprobeКоличество единиц для запроса[1, nlist]8
    • Поиск диапазона

      ПараметрОписаниеДиапазонЗначение по умолчанию
      max_empty_result_bucketsМаксимальное количество ведер, не дающих результатов поиска.
      Это параметр поиска по диапазону, который прекращает процесс поиска, если количество последовательных пустых ведер достигает указанного значения.
      Увеличение этого значения может улучшить скорость запоминания за счет увеличения времени поиска.
      [1, 65535]2

IVF_SQ8

IVF_FLAT не выполняет никакого сжатия, поэтому создаваемые им индексные файлы имеют примерно тот же размер, что и исходные, необработанные векторные данные без индексации. Например, если исходный набор данных 1B SIFT имеет размер 476 ГБ, то индексные файлы IVF_FLAT будут немного меньше (~470 ГБ). Загрузка всех индексных файлов в память займет 470 ГБ.

Если ресурсы памяти диска, CPU или GPU ограничены, IVF_SQ8 будет лучшим вариантом, чем IVF_FLAT. Этот тип индекса может преобразовывать каждое FLOAT (4 байта) в UINT8 (1 байт), выполняя скалярное квантование (SQ). Это позволяет сократить потребление памяти на диске, CPU и GPU на 70-75 %. Для набора данных 1B SIFT индексные файлы IVF_SQ8 требуют всего 140 ГБ памяти.

  • Параметры построения индекса

    ПараметрОписаниеДиапазон
    nlistКоличество кластеров[1, 65536]
  • Параметры поиска

    • Общий поиск

      ПараметрОписаниеДиапазонЗначение по умолчанию
      nprobeКоличество единиц для запроса[1, nlist]8
    • Поиск диапазона

      ПараметрОписаниеДиапазонЗначение по умолчанию
      max_empty_result_bucketsМаксимальное количество ведер, не дающих результатов поиска.
      Это параметр поиска по диапазону, который прекращает процесс поиска, если количество последовательных пустых ведер достигает указанного значения.
      Увеличение этого значения может улучшить скорость отзыва за счет увеличения времени поиска.
      [1, 65535]2

IVF_PQ

PQ (Product Quantization) равномерно разлагает исходное высокоразмерное векторное пространство на декартово произведение m низкоразмерных векторных пространств, а затем квантует разложенные низкоразмерные векторные пространства. Вместо вычисления расстояний между целевым вектором и центром всех единиц, квантование по продуктам позволяет вычислять расстояния между целевым вектором и центром кластеризации каждого низкоразмерного пространства и значительно сокращает временную и пространственную сложность алгоритма.

IVF_PQ выполняет кластеризацию индекса ЭКО перед квантованием произведения векторов. Его индексный файл еще меньше, чем у IVF_SQ8, но это также приводит к потере точности при поиске векторов.

Параметры построения индекса и параметры поиска зависят от дистрибутива Milvus. Сначала выберите свой дистрибутив Milvus.

  • Параметры построения индекса

    ПараметрОписаниеДиапазон
    nlistКоличество единиц кластера[1, 65536]
    mКоличество коэффициентов квантования произведенияdim mod m == 0
    nbits[Необязательно] Количество бит, в которых хранится каждый низкоразмерный вектор.[1, 64] (по умолчанию 8)
  • Параметры поиска

    • Общий поиск

      ПараметрОписаниеДиапазонЗначение по умолчанию
      nprobeКоличество единиц для запроса[1, nlist]8
    • Поиск диапазона

      ПараметрОписаниеДиапазонЗначение по умолчанию
      max_empty_result_bucketsМаксимальное количество ведер, не дающих результатов поиска.
      Это параметр поиска по диапазону, который прекращает процесс поиска, если количество последовательных пустых ведер достигает указанного значения.
      Увеличение этого значения может улучшить скорость запоминания за счет увеличения времени поиска.
      [1, 65535]2

SCANN

ScaNN (Scalable Nearest Neighbors) похож на IVF_PQ с точки зрения кластеризации векторов и квантования произведений. Их отличия заключаются в деталях реализации квантования произведения и использовании SIMD (Single-Instruction / Multi-data) для эффективного вычисления.

  • Параметры построения индекса

    ПараметрОписаниеДиапазон
    nlistКоличество кластеров[1, 65536]
    with_raw_dataВключать ли исходные данные в индексTrue или False. По умолчанию True.

    В отличие от IVF_PQ, значения по умолчанию применяются к m и nbits для оптимизации производительности.

  • Параметры поиска

    • Общий поиск

      ПараметрОписаниеДиапазонЗначение по умолчанию
      nprobeКоличество единиц для запроса[1, nlist]
      reorder_kКоличество единиц-кандидатов для запроса[top_k, ∞]top_k
    • Поиск в диапазоне

      ПараметрОписаниеДиапазонЗначение по умолчанию
      max_empty_result_bucketsМаксимальное количество ведер, не дающих результатов поиска.
      Это параметр поиска по диапазону, который прекращает процесс поиска, если количество последовательных пустых ведер достигает указанного значения.
      Увеличение этого значения может улучшить скорость запоминания за счет увеличения времени поиска.
      [1, 65535]2

HNSW

HNSW (Hierarchical Navigable Small World Graph) - это алгоритм индексирования на основе графов. Он строит многослойную навигационную структуру для изображения в соответствии с определенными правилами. В этой структуре верхние слои более разрежены и расстояния между узлами больше, а нижние слои более плотные и расстояния между узлами ближе. Поиск начинается с самого верхнего слоя, в нем находится ближайший к цели узел, а затем переходят к следующему слою, чтобы начать новый поиск. После нескольких итераций поиск быстро приближается к целевой позиции.

Для повышения производительности HNSW ограничивает максимальную степень узлов на каждом слое графа значением M. Кроме того, вы можете использовать efConstruction (при построении индекса) или ef (при поиске целей), чтобы указать диапазон поиска.

  • Параметры построения индекса

    ПараметрОписаниеДиапазонЗначение по умолчанию
    MM определяет максимальное количество исходящих соединений в графе. Большее значение M приводит к увеличению точности/времени работы при фиксированном ef/efConstruction.[2, 2048]Нет
    efConstructionef_construction управляет компромиссом между скоростью поиска индекса и скоростью сборки. Увеличение параметра efConstruction может повысить качество индекса, но также имеет тенденцию к увеличению времени индексирования.[1, int_max]None
  • Параметры поиска

    ПараметрОписаниеДиапазонЗначение по умолчанию
    efПараметр, управляющий компромиссом между временем запроса и точностью. Более высокий ef приводит к более точному, но более медленному поиску.[top_k, int_max]Нет

HNSW_SQ

Скалярное квантование (SQ) - это техника, используемая для дискретизации данных с плавающей точкой на конечный набор значений, основанный на их величине. Например, SQ6 представляет собой квантование в (2^6 = 64) дискретных значений, где каждое число с плавающей точкой кодируется 6 битами. Аналогично, SQ8 квантует данные в (2^8 = 256) дискретных значений, где каждое число с плавающей точкой представлено 8 битами. Такое квантование уменьшает занимаемую память, сохраняя при этом важную структуру данных для эффективной обработки.

В сочетании с SQ, HNSW_SQ обеспечивает контролируемый компромисс между размером индекса и точностью, сохраняя при этом высокую производительность запросов в секунду (QPS). По сравнению со стандартным HNSW, это приводит к незначительному увеличению времени построения индекса.

  • Параметры построения индекса

    ПараметрОписаниеДиапазонЗначение по умолчанию
    MM определяет максимальное количество исходящих соединений в графе. Большее значение M приводит к увеличению точности/времени работы при фиксированном ef/efConstruction.[2, 2048]Нет
    efConstructionef_construction управляет компромиссом между скоростью поиска индекса и скоростью сборки. Увеличение параметра efConstruction может повысить качество индекса, но также имеет тенденцию к увеличению времени индексирования.[1, int_max]None
    sq_typeТип скалярного квантователя.SQ6,SQ8, BF16, FP16SQ8
    refineРезервируются ли уточненные данные при построении индекса.true, falsefalse
    refine_typeТип данных уточняемого индекса.SQ6, SQ8, BF16, FP16, FP32Нет
  • Параметры поиска

    ПараметрОписаниеДиапазонЗначение по умолчанию
    efПараметр, управляющий компромиссом между временем запроса и точностью. Более высокое значение ef приводит к более точному, но более медленному поиску.[top_k, int_max]Нет
    refine_kКоэффициент увеличения уточнения по сравнению с k.[1, float_max]1

HNSW_PQ

Основная идея PQ заключается в том, чтобы разбить вектор на m подвекторов, каждый из которых найдет 2^{nbits} центроидов на основе kmeans, и каждый подвектор выберет ближайший центроид в качестве своего приближенного подвектора. Затем мы записываем все центроиды, поэтому каждый подвектор можно закодировать как nbits, а плавающий вектор длины dim можно закодировать как m ⋅ nbits бит.

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

  • Параметры построения индекса

    ПараметрОписаниеДиапазонЗначение по умолчанию
    MM определяет максимальное количество исходящих соединений в графе. Большее значение M приводит к увеличению точности/времени работы при фиксированном ef/efConstruction.[2, 2048]Нет
    efConstructionef_construction управляет компромиссом между скоростью поиска индекса и скоростью сборки. Увеличение параметра efConstruction может повысить качество индекса, но также имеет тенденцию к увеличению времени индексирования.[1, int_max]None
    mКоличество групп субвекторов, на которые нужно разбить вектор.[1, 65536]32
    nbitsКоличество бит, на которые квантуется каждая группа субвекторов.[1, 24]8
    refineРезервируются ли уточненные данные при построении индекса.true, falsefalse
    refine_typeТип данных уточненного индекса.SQ6, SQ8, BF16, FP16, FP32Нет
  • Параметры поиска

    ПараметрОписаниеДиапазонЗначение по умолчанию
    efПараметр, управляющий компромиссом между временем запроса и точностью. Более высокое значение ef приводит к более точному, но более медленному поиску.[top_k, int_max]Нет
    refine_kКоэффициент увеличения уточнения по сравнению с k.[1, float_max]1

HNSW_PRQ

PRQ аналогичен PQ и также делит вектор на группы m. Каждый субвектор будет закодирован как nbits. После завершения pq-квантования вычисляется остаток между вектором и pq-квантованным вектором, и к остаточному вектору применяется pq-квантование. Всего будет выполнено nrq полных pq-квантований, поэтому плавающий вектор длины dim будет закодирован как m ⋅ nbits ⋅ nrq bits.

В сочетании с квантователем остаточного продукта (PRQ) HNSW_PRQ обеспечивает еще более высокий контролируемый компромисс между размером индекса и точностью. Он имеет почти эквивалентное значение QPS и более высокий коэффициент запоминания, чем HNSW_PQ, при той же степени сжатия. По сравнению с HNSW_PQ время построения индекса может увеличиться в несколько раз.

  • Параметры построения индекса

    ПараметрОписаниеДиапазонЗначение по умолчанию
    MM определяет максимальное количество исходящих соединений в графе. Большее значение M приводит к увеличению точности/времени работы при фиксированном ef/efConstruction.[2, 2048]Нет
    efConstructionef_construction управляет компромиссом между скоростью поиска индекса и скоростью сборки. Увеличение параметра efConstruction может повысить качество индекса, но также имеет тенденцию к увеличению времени индексирования.[1, int_max]None
    mКоличество групп субвекторов, на которые нужно разбить вектор.[1, 65536]32
    nbitsКоличество бит, на которые квантуется каждая группа субвекторов.[1, 24]8
    nrqКоличество остаточных субквантователей.[1, 16]2
    refineРезервируются ли уточненные данные при построении индекса.true, falsefalse
    refine_typeТип данных уточняемого индекса.SQ6, SQ8, BF16, FP16, FP32Нет
  • Параметры поиска

    ПараметрОписаниеДиапазонЗначение по умолчанию
    efПараметр, определяющий компромисс между временем запроса и точностью. Более высокий ef приводит к более точному, но более медленному поиску.[top_k, int_max]Нет
    refine_kКоэффициент увеличения уточнения по сравнению с k.[1, float_max]1

BIN_FLAT

Этот индекс точно такой же, как FLAT, за исключением того, что он может использоваться только для бинарных вкраплений.

Для приложений поиска векторного сходства, требующих идеальной точности и зависящих от относительно небольших (миллионных) наборов данных, индекс BIN_FLAT является хорошим выбором. BIN_FLAT не сжимает векторы и является единственным индексом, который может гарантировать точные результаты поиска. Результаты BIN_FLAT также можно использовать в качестве точки сравнения для результатов, выдаваемых другими индексами, которые имеют менее чем 100 % отзыв.

Точность BIN_FLAT объясняется тем, что он использует исчерпывающий подход к поиску, то есть для каждого запроса целевой входной сигнал сравнивается с векторами в наборе данных. Это делает BIN_FLAT самым медленным индексом в нашем списке и плохо подходит для запросов к массивным векторным данным. В Milvus нет параметров для индекса BIN_FLAT, и его использование не требует подготовки данных или дополнительного хранения.

  • Параметры поиска

    ПараметрОписаниеДиапазон
    metric_type[Необязательно] Выбранная метрика расстояния.См. раздел Поддерживаемые метрики.

BIN_IVF_FLAT

Этот индекс точно такой же, как IVF_FLAT, за исключением того, что он может использоваться только для бинарных вкраплений.

BIN_IVF_FLAT делит векторные данные на кластеры nlist, а затем сравнивает расстояния между целевым входным вектором и центром каждого кластера. В зависимости от количества кластеров, к которым система настроена на запрос (nprobe), результаты поиска сходства возвращаются на основе сравнений между целевым входным вектором и векторами только в наиболее похожих кластерах, что значительно сокращает время запроса.

Настраивая nprobe, можно найти идеальный баланс между точностью и скоростью для конкретного сценария. Время выполнения запроса резко возрастает при увеличении как количества векторов целевого входа (nq), так и количества кластеров для поиска (nprobe).

BIN_IVF_FLAT - это самый базовый индекс BIN_IVF, и закодированные данные, хранящиеся в каждом блоке, соответствуют исходным данным.

  • Параметры построения индекса

    ПараметрОписаниеДиапазон
    nlistКоличество единиц кластера[1, 65536]
  • Параметры поиска

    • Общий поиск

      ПараметрОписаниеДиапазонЗначение по умолчанию
      nprobeКоличество единиц для запроса[1, nlist]8
    • Поиск диапазона

      ПараметрОписаниеДиапазонЗначение по умолчанию
      max_empty_result_bucketsМаксимальное количество ведер, не дающих результатов поиска.
      Это параметр поиска по диапазону, который прекращает процесс поиска, если количество последовательных пустых ведер достигает указанного значения.
      Увеличение этого значения может улучшить скорость отзыва за счет увеличения времени поиска.
      [1, 65535]2

SPARSE_INVERTED_INDEX

В каждом измерении хранится список векторов, имеющих ненулевое значение в этом измерении. Во время поиска Milvus итерируется по каждому измерению вектора запроса и вычисляет оценки для векторов, имеющих ненулевые значения в этих измерениях.

  • Параметры построения индекса

    ПараметрОписаниеДиапазон
    inverted_index_algoАлгоритм, используемый для построения и запроса индекса. Подробнее см. в разделе "Разреженный вектор".DAAT_MAXSCORE (по умолчанию), DAAT_WAND, TAAT_NAIVE

    Параметр drop_ratio_build устарел с версии Milvus v2.5.4. Он все еще может быть принят при построении индекса, но больше не будет иметь фактического влияния на индекс.

  • Параметры поиска

    ПараметрОписаниеДиапазон
    drop_ratio_searchДоля малых значений вектора, которые исключаются в процессе поиска. Этот параметр позволяет тонко настроить процесс поиска, указав долю наименьших значений в векторе запроса, которые следует игнорировать. Это помогает сбалансировать точность и производительность поиска. Чем меньше значение drop_ratio_search, тем меньший вклад эти малые значения вносят в итоговую оценку. Игнорирование некоторых малых значений позволяет повысить производительность поиска при минимальном влиянии на точность.[0, 1]

FAQ

В чем разница между индексом FLAT и индексом IVF_FLAT?

Индекс IVF_FLAT делит векторное пространство на кластеры nlist. Если значение nlist по умолчанию равно 16384, Milvus сравнивает расстояния между целевым вектором и центрами всех 16384 кластеров, чтобы получить nprobe ближайших кластеров. Затем Milvus сравнивает расстояния между целевым вектором и векторами в выбранных кластерах, чтобы получить ближайшие векторы. В отличие от IVF_FLAT, FLAT напрямую сравнивает расстояния между целевым вектором и каждым вектором.

Поэтому, когда общее количество векторов приблизительно равно nlist, IVF_FLAT и FLAT мало отличаются по количеству необходимых вычислений и производительности поиска. Но при увеличении числа векторов в два, три или n раз от nlist индекс IVF_FLAT начинает демонстрировать все большие преимущества.

Дополнительную информацию см. в разделе Как выбрать индекс в Milvus.

Что дальше

Попробуйте Managed Milvus бесплатно

Zilliz Cloud работает без проблем, поддерживается Milvus и в 10 раз быстрее.

Начать
Обратная связь

Была ли эта страница полезной?