Category: Замітки

Деякі замітки із робочих питаннь.

  • ELK-стек для чого?

    ELK-стек для чого?

    В цій статті ви знайдете інформацію щодо ELK-стеку (Elasticsearch, Logstash, Kibana).
    Ця добірка фактично стала стандартом для централізованого збереження та аналізу логів.
    Але насправді це значно потужніший інструмент, аніж деякі його представляють. Розберімося що до чого.

    Elasticsearch – це пошукова система, яка є спадкоємиця проєкту Compass, який своєю чергою побудований на базі Apache Lucene.
    Першочергова ціль була: спрощення масштабування системи. Забігаючи наперед скажу що це дійсно вийшло.
    Зараз позиціонується як розподілений, масштабуємий, RESTful пошуковий та аналітичний механізм.
    Який дозволяє побудувати не тільки систему аналізу логів, а і повноцінний повнотекстовий пошук для сайту, або бізнес орієнтовану аналітичну систему.
    Пошукові індекси у складі багатоНОДової системи, легко масштабуються, балансування підтримується “із коробки”, та не потребує додаткової складної конфігурації.
    Починаючи із версії 5.1.1 має підтримку аналізу української мови, що стане в нагоді для побудови повнотекстового пошуку, наприклад у інтернет-магазині.

    Logstash – це система обробки збору та обробки даних для подальшого збереження цих даних, наприклад в Elasticsearch.
    Та за допомогою багатьох плагінів, дозволяє зберігати данні у різні системи, наприклад: AWS CloudWatch, DataDogHQ, Google BigQuery, Graphite, InfluxDB.
    Повний список можна подивитись: https://www.elastic.co/guide/en/logstash/current/output-plugins.html
    “Із коробки” підтримує збір та парсинг типових системних логів Linux, Nginx, MySQL, Redis.
    Повний модулів можна подивитись: https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html
    Також за допомогою плагіну Grok, можливо описати фільтри специфічних для проєкту логів, парсити їх та зберігати до Elasticsearch.
    Парсинг специфічних логів це окрема тема, яку я додам трошки пізніше.

    Kibana – система візуалізації та аналізу даних збережених в Elasticsearch. З коробки має налаштовані інтерфейси для візуалізації логів Linux, Nginx, MySQL, Redis.
    Що дозволяє у дуже короткий час отримати готову систему аналізу логів, типової конфігурації, наприклад LAMP(Linux, Apache2, MySQL, PHP).
    Також за допомогою вбудованих інструментів дозволяє побудувати свій інтерфейс, будувати графіки та таблиці.
    Має два типи можливих видів побудови запитів: Kibana Query Language(KQL) та Lucene query syntax(LQS).
    Насправді KQL використовується виключно для фільтрації даних, та не несе ролі агрегації та сортування. Не вміє працювати із регулярними виразами, або нечіткими запитами.
    Для таких цілей необхідно використовувати Lucene query, але він не підтримує автозаповнення.
    Kibana із вебінтерфейсу також дозволяє описати та сконфігурувати індекси у самому Elasticsearch: описати типи полів, життєвий цикл, кількість реплік.

    Яка практична цінність цієї системи?
    Як ми бачимо кожен з окремо взятих інструментів може жити своїм життям навіть поза ELK-стеком.
    Тому якщо нам необхідно побудувати повнотекстовий пошук: Гнучкість пошукових фільтрів, включаючи нечіткий пошук та мультиарендність, коли в рамках одного об’єкта Elasticsearch дозволяє динамічно організувати декілька різних пошукових систем.

    Або у BigData проєктах – автоматична індексація нових JSON-об’єктів, які завантажуються в базу та одразу стають доступними для пошуку, за рахунок відсутності схеми згідно з типовою NoSQL-концепцією. Це дозволяє прискорити прототипування пошукових рішень Big Data.

    Наявність вбудованих аналізаторів тексту дозволяє Elasticsearch автоматично виконувати токенізацію, лематизацію, стемінг та інші перетворення для вирішення NLP-завдань, пов’язаних з пошуком даних.


    Але ми тут розглядаємо саме ELK-стек, тому основна цінність цієї системи, це збір, візуалізація та пошук по логам, який можливо побудувати при незначних витратах часу.

    Як почати використовувати?
    Тут є декілька варіантів, або із використанням репозиторію, або за допомогою контейнерів Docker.
    Хоча розглянемо обидва варіанти, я не буду розписувати покроково, процес встановлення він дуже простий. Тому одразу дам Bash-скрипт:

    #!/bin/bash
    wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
    sudo apt-get install apt-transport-https
    echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list
    sudo apt-get update && sudo apt-get install elasticsearch logstash kibana
    # secure http connect to services 9200 - elastic search, 5601 - kibana port, 5044 - logstash
    iptables -I INPUT ! -s 127.0.0.1/32 -p tcp -m tcp --dport 9200 -j ACCEPT
    iptables -I INPUT ! -s 127.0.0.1/32 -p tcp -m tcp --dport 5601 -j ACCEPT
    iptables -I INPUT ! -s 127.0.0.1/32 -p tcp -m tcp --dport 5044 -j ACCEPT
    
    #Enable services
    sudo systemctl enable elasticsearch && sudo systemctl start elasticsearch
    sudo systemctl enable logstash && sudo systemctl start logstash
    sudo systemctl enable kibana && sudo systemctl start kibana
    
    
    ## Echo key for add kibana to elasticstack
    echo "YOUR KEY FOR ENROLLMENT:"
    /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana --url "https://localhost:9200"

    Таким чином ми отримаємо однонодову інсталяцію ELK-стеку, у базовій комплектації готову для роботи.

    Або мультинодовий кластер на Docker-compose, але тут треба буде мати встановлений Docker engine і docker-compose.

    У будь-якій директорії створюємо файли .env та docker-compose.yml
    Вміст файлу ./.env:

    STACK_VERSION=8.2.2
    ES_PORT=127.0.0.1:9200
    KIBANA_PORT=127.0.0.1:5601
    LOGSTASH_PORT=127.0.0.1:5044
    
    ELASTIC_PASSWORD=<set strong password>
    KIBANA_PASSWORD=<set strong password>

    та файлу docker-compose.yml:

    version: "2.2"
    services:
      setup:
        image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
        volumes:
          - certs:/usr/share/elasticsearch/config/certs
        user: "0"
        command: >
          bash -c '
            if [ x${ELASTIC_PASSWORD} == x ]; then
              echo "Set the ELASTIC_PASSWORD environment variable in the .env file";
              exit 1;
            elif [ x${KIBANA_PASSWORD} == x ]; then
              echo "Set the KIBANA_PASSWORD environment variable in the .env file";
              exit 1;
            fi;
            if [ ! -f config/certs/ca.zip ]; then
              echo "Creating CA";
              bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip;
              unzip config/certs/ca.zip -d config/certs;
            fi;
            if [ ! -f config/certs/certs.zip ]; then
              echo "Creating certs";
              echo -ne \
              "instances:\n"\
              "  - name: es01\n"\
              "    dns:\n"\
              "      - es01\n"\
              "      - localhost\n"\
              "    ip:\n"\
              "      - 127.0.0.1\n"\
              "  - name: es02\n"\
              "    dns:\n"\
              "      - es02\n"\
              "      - localhost\n"\
              "    ip:\n"\
              "      - 127.0.0.1\n"\
              "  - name: es03\n"\
              "    dns:\n"\
              "      - es03\n"\
              "      - localhost\n"\
              "    ip:\n"\
              "      - 127.0.0.1\n"\
              > config/certs/instances.yml;
              bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key;
              unzip config/certs/certs.zip -d config/certs;
            fi;
            echo "Setting file permissions"
            chown -R root:root config/certs;
            find . -type d -exec chmod 750 \{\} \;;
            find . -type f -exec chmod 640 \{\} \;;
            echo "Waiting for Elasticsearch availability";
            until curl -s --cacert config/certs/ca/ca.crt https://es01:9200 | grep -q "missing authentication credentials"; do sleep 30; done;
            echo "Setting kibana_system password";
            until curl -s -X POST --cacert config/certs/ca/ca.crt -u elastic:${ELASTIC_PASSWORD} -H "Content-Type: application/json" https://es01:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_PASSWORD}\"}" | grep -q "^{}"; do sleep 10; done;
            echo "All done!";
          '
        healthcheck:
          test: ["CMD-SHELL", "[ -f config/certs/es01/es01.crt ]"]
          interval: 1s
          timeout: 5s
          retries: 120
    
      es01:
        depends_on:
          setup:
            condition: service_healthy
        image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
        volumes:
          - certs:/usr/share/elasticsearch/config/certs
          - esdata01:/usr/share/elasticsearch/data
        ports:
          - ${ES_PORT}:9200
        environment:
          - node.name=es01
          - cluster.name=${CLUSTER_NAME}
          - cluster.initial_master_nodes=es01,es02,es03
          - discovery.seed_hosts=es02,es03
          - ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
          - bootstrap.memory_lock=true
          - xpack.security.enabled=true
          - xpack.security.http.ssl.enabled=true
          - xpack.security.http.ssl.key=certs/es01/es01.key
          - xpack.security.http.ssl.certificate=certs/es01/es01.crt
          - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
          - xpack.security.http.ssl.verification_mode=certificate
          - xpack.security.transport.ssl.enabled=true
          - xpack.security.transport.ssl.key=certs/es01/es01.key
          - xpack.security.transport.ssl.certificate=certs/es01/es01.crt
          - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt
          - xpack.security.transport.ssl.verification_mode=certificate
          - xpack.license.self_generated.type=${LICENSE}
        mem_limit: ${MEM_LIMIT}
        ulimits:
          memlock:
            soft: -1
            hard: -1
        healthcheck:
          test:
            [
              "CMD-SHELL",
              "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'",
            ]
          interval: 10s
          timeout: 10s
          retries: 120
    
      es02:
        depends_on:
          - es01
        image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
        volumes:
          - certs:/usr/share/elasticsearch/config/certs
          - esdata02:/usr/share/elasticsearch/data
        environment:
          - node.name=es02
          - cluster.name=${CLUSTER_NAME}
          - cluster.initial_master_nodes=es01,es02,es03
          - discovery.seed_hosts=es01,es03
          - bootstrap.memory_lock=true
          - xpack.security.enabled=true
          - xpack.security.http.ssl.enabled=true
          - xpack.security.http.ssl.key=certs/es02/es02.key
          - xpack.security.http.ssl.certificate=certs/es02/es02.crt
          - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
          - xpack.security.http.ssl.verification_mode=certificate
          - xpack.security.transport.ssl.enabled=true
          - xpack.security.transport.ssl.key=certs/es02/es02.key
          - xpack.security.transport.ssl.certificate=certs/es02/es02.crt
          - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt
          - xpack.security.transport.ssl.verification_mode=certificate
          - xpack.license.self_generated.type=${LICENSE}
        mem_limit: ${MEM_LIMIT}
        ulimits:
          memlock:
            soft: -1
            hard: -1
        healthcheck:
          test:
            [
              "CMD-SHELL",
              "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'",
            ]
          interval: 10s
          timeout: 10s
          retries: 120
    
      es03:
        depends_on:
          - es02
        image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
        volumes:
          - certs:/usr/share/elasticsearch/config/certs
          - esdata03:/usr/share/elasticsearch/data
        environment:
          - node.name=es03
          - cluster.name=${CLUSTER_NAME}
          - cluster.initial_master_nodes=es01,es02,es03
          - discovery.seed_hosts=es01,es02
          - bootstrap.memory_lock=true
          - xpack.security.enabled=true
          - xpack.security.http.ssl.enabled=true
          - xpack.security.http.ssl.key=certs/es03/es03.key
          - xpack.security.http.ssl.certificate=certs/es03/es03.crt
          - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
          - xpack.security.http.ssl.verification_mode=certificate
          - xpack.security.transport.ssl.enabled=true
          - xpack.security.transport.ssl.key=certs/es03/es03.key
          - xpack.security.transport.ssl.certificate=certs/es03/es03.crt
          - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt
          - xpack.security.transport.ssl.verification_mode=certificate
          - xpack.license.self_generated.type=${LICENSE}
        mem_limit: ${MEM_LIMIT}
        ulimits:
          memlock:
            soft: -1
            hard: -1
        healthcheck:
          test:
            [
              "CMD-SHELL",
              "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'",
            ]
          interval: 10s
          timeout: 10s
          retries: 120
    
      kibana:
        depends_on:
          es01:
            condition: service_healthy
          es02:
            condition: service_healthy
          es03:
            condition: service_healthy
        image: docker.elastic.co/kibana/kibana:${STACK_VERSION}
        volumes:
          - certs:/usr/share/kibana/config/certs
          - kibanadata:/usr/share/kibana/data
        ports:
          - ${KIBANA_PORT}:5601
        environment:
          - SERVERNAME=kibana
          - ELASTICSEARCH_HOSTS=https://es01:9200
          - ELASTICSEARCH_USERNAME=kibana_system
          - ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD}
          - ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES=config/certs/ca/ca.crt
        mem_limit: ${MEM_LIMIT}
        healthcheck:
          test:
            [
              "CMD-SHELL",
              "curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'",
            ]
          interval: 10s
          timeout: 10s
          retries: 120
    
      logstash:
        image: docker.elastic.co/logstash/logstash:${STACK_VERSION}
        ports:
          - ${LOGSTASH_PORT}:5044
          - "5000:5000/tcp"
          - "5000:5000/udp"
          - "9600:9600"
        environment:
          LS_JAVA_OPTS: -Xms256m -Xmx256m
    	depends_on:
          es01:
            condition: service_healthy
          es02:
            condition: service_healthy
          es03:
            condition: service_healthy
    	
    volumes:
      certs:
        driver: local
      esdata01:
        driver: local
      esdata02:
        driver: local
      esdata03:
        driver: local
      kibanadata:
        driver: local

    І запустити для чого виконати команду:

    docker-compose up -d

    Після запуску кластера можна під’єднатися до Kibana http://localhost:5601
    використавши логін elastic та пароль зазначений в ELASTIC_PASSWORD

    Далі вже необхідно буде встановити збір логів із серверів. Якщо ми хочемо збирати стандартні логи, то для цього навіть не потрібен Logstash, замість нього достатньо встановити на сервері із якого необхідно збирати логи, Filebeat.


    Filebeat – це легкий агент для збору та надсилання файлів журналювання, який може відправляти данні, або напряму в Elasticsearch, або для подальшого опрацювання до Logstash.

    Для встановлення Filebeat необхідно виконати декілька дій на сервері із Elasticsearch та сервері із якого збираємо логи.
    На сервері звідки беремо логи:

    #!/bin/bash
    curl -L -O --output-dir /tmp https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.2.2-amd64.deb 
    sudo dpkg -i /tmp/filebeat-8.2.2-amd64.deb
    sudo rm /tmp/filebeat-8.2.2-amd64.deb
    filebeat modules enable nginx

    Після чого змінюємо конфігурацію модуля /etc/filebeat/modules.d/nginx.yml:

    - module: nginx
      access:
        enabled: true
        var.paths: ["/var/log/nginx/access.log*"]

    Необхідно створити юзера для запису, на сервері з Elasticsearch виконуємо:

    ./bin/elasticsearch-users useradd filebeat

    Вигадуємо пароль, та вводимо його, changeme!23

    Створюємо групу із достатніми правами для інсталяції згідно з офіційною документацією

    curl -k -u "elastic:<ELASTIC_PASSWORD>" -X PUT "https://localhost:9200/_security/role/filebeat_write?pretty" -H 'Content-Type: application/json' -d'
    {
      "cluster": [
        "monitor",
        "read_pipeline",
        "read_ilm",
        "manage"
      ],
      "indices": [
        {
          "names": [
            "filebeat-*"
          ],
          "privileges": [
            "create_doc",
            "view_index_metadata",
            "create_index"
          ]
        },
        {
          "names": [
            "filebeat-*",
            ".ds-filebeat-*"
          ],
          "privileges": [
            "manage",
            "manage_follow_index",
            "all"
          ]
        }
      ]
    }
    '

    Додаємо до груп:

    ./bin/elasticsearch-users roles filebeat -a kibana_admin
    ./bin/elasticsearch-users roles filebeat -a filebeat_writer

    У файлі конфігурації на сервері із Filebeat змінити рядки:

    output.elasticsearch:
      hosts: ["https://myEShost:9200"]
      username: "filebeat" 
      password: "changeme!23"
      
    setup.kibana:
        host: "myKIBANAhostIP:5601" 
        username: "filebeat"  
        password: "changeme!23"

    Причому у блоці із setup.kibana, юзер і пароль, опціональні, Якщо їх не вказати будуть використані із блоку output.elasticsearch.

    Після всіх підготовчих робіт виконуємо команду, яка створить робочі столи і візуалізації для вибраних модулів(в нашому випадку Nginx).

    filebeat setup -e

    Також хочу зазначити що група kibana_admin, необхідна одноразово для створення базових робочих столів із візуалізацією. Можна потім відізвати цю роль:

    ./bin/elasticsearch-users roles filebeat -r kibana_admin

    На цьому базова конфігурація закінчена і можна перейти до Kibana. Та подивитись логи Nginx.


    Додаткові сервери додаються так само, але вже без всіх цих маніпуляцій зі створенням групи й додаванням юзера до неї.
    Або із використанням Ansible розгорнути типові конфігурації Filebeat, на всіх необхідних серверах.

  • Як пришвидшити роботу із plain index у Manticore Search?

    Як пришвидшити роботу із plain index у Manticore Search?

    Стикнулися із проблемою: повнотекстовий пошук дуже довго проходив через великий індекс.

    Проблема пов’язана із тим, що “проходження”, по одному “простому індексу”(plain index) виконується із використанням одного ядра, у той час, коли інші ядра “простоюють”.
    У офіційній документації знаходимо що існує “розподілений індекс”(distributed index), який утилізує стільки ядер, скільки вказано у директиві dist_threads.
    UPD. Директива dist_threads від 2020 року застаріла, на заміну пришла директива threads, яка за замоченням відповідає кількості ядер процесора.

    Перейдемо до демонстрації, як це працює.
    Наприклад ви маєте “простий індекс” такого плану:

    source src_products:connect_database
    {
            sql_query_pre = SET CHARACTER_SET_RESULTS=utf8
            sql_query_pre   = SET GROUP_CONCAT_MAX_LEN = 2000
            sql_range_step      = 1000;
    		sql_query_range     = SELECT min(id), max(id) 
    		
    		sql_query               = \
                    SELECT t1.id AS product_id, \
                t2.id AS supplier_id, \
                count(distinct t1.id) as product_count, \
                t1.product_count as total_product_count, \
                unix_timestamp(t2.supplier_start_date) AS date_sale_start, \
                unix_timestamp(t2.date_modified) AS date_modified, \
                unix_timestamp(t2.sale_end_date) AS date_end_sale, \
                unix_timestamp(t2.created_at) AS created_at, \
                t1.title AS product_title, \
                t1.description AS product_description, \
                t1.title_en AS product_title_en, \
                t1.description_en AS product_description_en, \
                t2.title, \
                t2.description, \
                t2.title_en, \
                t2.description_en, \
                t2.status_id as status_id, \
                t2.identifier AS supplier_identifier, \
                t2.name AS supplier_name, \
                t2.id AS supplier_id, \
                    FROM products t1 \
                inner join supplier t2 on t2.id = t1.supplier_id \
            WHERE t1.id >= \$start AND t1.id <= \$end AND t2.enabled = 1 \
            GROUP BY t1.id
    		
    		sql_field_string = product_title
    		sql_field_string = product_description
    		sql_attr_uint = product_id
    		sql_attr_uint = supplier_id
    		sql_attr_multi = uint code_id from query; \
    			select t1.id as id, t3.codes_id as code_id \
    			from products t1 \
    			inner join items t2 on t1.product_id = t2.product_id \
    			inner join codes_item t3 on t2.id = t3.item_id
    }
    
    index products
    {
        source   = src_products
        path   = /var/lib/manticore/data/src_products
        min_stemming_len    = 3
        min_word_len        = 3
        phrase_boundary     = ., ?, !, ^, U+2026
        min_infix_len = 3
    }

    Рішення проблеми:

    Нам необхідно розбити один великий “простий індекс” на декілька(бажано за кількістю ядер) та об’єднати їх у “розподілений індекс”.
    Можемо взяти сумарну кількість записів розділити порівну й описати декілька сорсів із якимось офсетом.
    Але що робити якщо таблиця постійно наповнюється та кількість записів змінюється? А ще записи можуть видалятися.
    Тому цю проблему ми віддамо на рішення самої БД, щоб вона завжди нам віддавала рівні частини звідки починати й де закінчувати для кожного шматочка “простого індексу”.
    Тут на допомогу приходить модифікація нашого sql_query_range, який вказує звідки починати й де закінчувати вибірку для побудови індексу.

    Наприклад, щоб поділити на 3 рівні частини:

    sql_query_range     = SELECT min(S.ID), max(S.ID) FROM \
    						  ( \
    							SELECT L.ID, \
    							  @RN:=@RN+1 ROWNUMBER, \
    							  (SELECT ROUND(COUNT(*)*0) FROM products) startN, \          # вказуємо звідки починаемо, для другого індексу буде: COUNT(*)*0.334. Для останнього: COUNT(*)*0.668
    							  (SELECT ROUND(COUNT(*)*0.334) FROM products) stopN \ 		  # де закінчуємо, для другого індексу буде: COUNT(*)*0.668. Для останнього: COUNT(*)
    							FROM products L \
    							  CROSS JOIN (SELECT @RN:=0) R \
    							ORDER BY L.ID \
    						  ) S \
    						WHERE S.ROWNUMBER >= S.startN and S.ROWNUMBER <= S.stopN

    Далі розмножуємо опис джерела(source), де змінюємо назву, та змінюємо sql_query_range.

    source src_products2:connect_database
    {
            sql_query_pre = SET CHARACTER_SET_RESULTS=utf8
            sql_query_pre   = SET GROUP_CONCAT_MAX_LEN = 2000
            sql_range_step      = 1000;
            sql_query_range     = SELECT min(S.ID), max(S.ID) FROM \
    								  ( \
    									SELECT L.ID, \
    									  @RN:=@RN+1 ROWNUMBER, \
    									  (SELECT ROUND(COUNT(*)*0.334) FROM products) startN, \
    									  (SELECT ROUND(COUNT(*)*0.668) FROM products) stopN \
    									FROM products L \
    									  CROSS JOIN (SELECT @RN:=0) R \
    									ORDER BY L.ID \
    								  ) S \
    								WHERE S.ROWNUMBER >= S.startN and S.ROWNUMBER <= S.stopN
    		...
        ...
        ...		
    }

    Розмножуємо опис index:

    index products2 
    {
    	source   = src_products2
        path   = /var/lib/manticore/data/src_products2
        min_stemming_len    = 3
        min_word_len        = 3
        phrase_boundary     = ., ?, !, ^, U+2026
        min_infix_len = 3
    }

    І накінець описуємо “розподілений індекс”(distributed index):

    index products_distributed
    {
        type = distributed
        local = products
        local = products2
        local = products3
    }

    Перезапускаємо індексацію, перезагружаємо сервіс Manticore Search, перевіряємо чи всі данні проіндексувались вірно.
    Ну і також необхідно буде змінити у конфігах вашого аплікейшену назву індексу: products -> products_distributed.

    Післямова

    Таким чином ми розділили один звичайний індекс, на декілька, та об’єднали їх до розподіленого індексу.
    Що дозволить ядру Manticore Search, використовувати більше ядер для пошуку по індексу.

    Безумовно ця історія створює одну неприємну річ, це обслуговування джерел(source) для індексів, при зміні одного, необхідно виконати зміни у кожному джерелі.
    Але приріст продуктивності коштує цього. Також для зміни конфігурації можливо використовувати різні інструменти, наприклад Ansible.
    Або створити Docker образ, конфігурацію manticore закинути до Git, та перебудовувати образ в процесі вашого CI/CD пайплайну.