DoctrineSolrBundle — поиск по Doctrine entity на базе Solr в Symfony2/3

    DoctrineSolrBundle


    Добрый день, хочу представить свой symfony 2 бандл для автоматической синхронизации Doctrine entity в Solr и последующим поиском. Бандл предназначен для работы с Solr на уровне Doctrine entity и позволяет избежать написания низкоуровневых запросов в solr. Процесс установки и подробную документацию можно посмотреть на github.


    Возможности


    Реализованы основные (не все) возможности поиска стандартного парсера запросов Solr :



    Также реализована поддержка SuggestComponent


    Пример конфигурации


    После установки для начала работы требуется настроить бандл в config.yml. Пример минимальной конфигурации бандла:


    mdiyakov_doctrine_solr:
        indexed_entities:
            page:
                class:  AppBundle\Entity\Page
                schema: page
                config:
                    - { name: type, value: page }
        schemes:
            page:
                document_unique_field: { name: 'uid' }
                config_entity_fields:
                    - {  config_field_name: 'type', document_field_name: 'type', discriminator: true }
                fields:
                    - {  entity_field_name: 'id', document_field_name: 'entity_id', field_type: int, entity_primary_key: true }
                    - {  entity_field_name: 'textField', document_field_name: 'text', priority: 100, suggester: true }

    Как результат после каждого создания, обновления "AppBundle\Entity\Page" сущности, будут прондексированы поля "id" и "textField", а также конфигурационное поле "type". В случае удаления экземпляра сущности соответствущий solr документ будет удален.


    В "schemes" секции описываютс схемы индексации. В схему индексации входит описание полей сущности (fields), конфигурационных полей (config_entity_fields) которые должны быть проиндексированы в solr. А также "document_unique_field" указывающее уникальное поле для schemes.xml в solr. Помимо этого есть необязательное поле client в случае если надо использовать несколько solr core для разных схем индексации (подробнее об этом тут). По сути каждая схема в schemes отражает конкретное solr core.


    Конфигурационное поле это поле которое задается в секции "indexed_entities" в рамках конфига сущности. Его можно использовать для индексации параметров заданных в parameters.yml, например:


    ....            
    class:  AppBundle\Entity\Page
    schema: page
    config:
            - { name: app_version, value: %app_version% }
            - { name: host, value: %host% }
           ...
    ....

    Для каждой индексируемой сущности должно быть задано как минимум одно конфигурационное поле с уникальным значением относительно всех индексируемых сущностей обозначенное как discriminator: true в schemes. Например:


    mdiyakov_doctrine_solr:
        indexed_entities:
            page:
                class:  AppBundle\Entity\Article
                schema: page
                config:
                    - { name: type, value: article }
        news:
                class:  AppBundle\Entity\News
                schema: page
                config:
                    - { name: type, value: news }
      schemes:         
            page:
                ...
                config_entity_fields:
                    - {  config_field_name: 'type', document_field_name: 'discriminator', discriminator: true }

    Т.к. и AppBundle\Entity\Article и AppBundle\Entity\News использует одну и ту же схему "page" то соотв. уникальность их primary key теряется т.к. могут существовать News и Article с одинаковым id. Чтобы избежать неопределенности задается конфигурационное поле используемое как дискриминатор значение которого добавляется к primary key сущности и результат записывается в уникальное поле документа.


    Также есть возможность задать фильтры для индексируемых сущностей применяемых перед тем как сущность будет проиндексирована в solr. В зависимости от результата фильтра сущность может быть проиндексирована, удалена или пропущена во время индексации. Пример:


    indexed_entities:
        page:
            class:  AppBundle\Entity\Page
            ...
            filters: [ big_id, published, ... ]
        news:
            class:  AppBundle\Entity\News
            ...
            filters: [ published, ... ]        
    schemes:
           ....
    filters:    
        fields:
            big_id: { entity_field_name: "id", entity_field_value: 3, operator: ">=" }
            published: { entity_field_name: "published", entity_field_value: true, operator: "=" }

    Соотв. если после того как Page или News были созданы и например поле "published" = false то индексация будет пропущена и в solr ничего не будет записано.


    Если же Page или News были обновлены и например поле "published" = false то solr документ соответствующий этому экземпляру сущности будет удален в solr


    То же самое и для фильтра big_id в случае если значение поле id < 3. "big_id" и "published" это произвольные названия для фильтров, могут быть какими угодно. Также есть возможность задачть symfony service как фильтр который применяется к экземпляру сущности а не к отдельному полю, подробности тут
    Для успешной индексации должны быть выполнены условия для всех фильтров.


    Индексация


    Индексация сущности запускается каждый раз когда выполняется $em->flush и реализуется через https://symfony.com/doc/current/bundles/DoctrineBundle/entity-listeners.html


    В случае когда надо выполнить первичную индексацию для всех сущностей в базе, реализована консольная команда:


    app/console doctrine-solr:index

    Подробности ее работы и аргументы можно найти на github.


    Пример поиска:


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


    // MyController
    //...
    // @var \Mdiyakov\DoctrineSolrBundle\Finder\ClassFinder $finder 
    $finder = $this->get('ds.finder')->getClassFinder(Article::class);
    
    /** @var Article[] $searchResults */
    $searchResults = $finder->findSearchTermByFields($searchTerm, ['title']);

    Результатом будет массив состоящий только из Article::class.


    Если схема используется несколькими сущностями, например:


      indexed_entities:
            page:
                class:  AppBundle\Entity\Article
                schema: page
               ...
        news:
                class:  AppBundle\Entity\News
                schema: page
                ...
    ...

    то можно искать по всем сущностям использующим эту схему:


    $schemaFinder = $this->get('ds.finder')->getSchemaFinder('page');
    $schemaFinder->addSelectClass(Article::class);
    $schemaFinder->addSelectClass(News::class);
    
    /** @var object[] **/
    $result = $schemaFinder->findSearchTermByFields($searchTerm, ['title', 'category']);

    Результатом будет массив состоящий из Article и News экземпляров отсортированных в соотв. с релевантностью.


    Подробнее про методы поиска тут


    Заключение


    Это вводная статься не описывающая полностью все возможности. Если вас заинтересовал бандл привожу пару ссылок для быстрого перехода по остальным возможностям бандла:



    UPD: Добавлена поддержка Symfony 3 и PHP 7

    Поделиться публикацией

    Похожие публикации

    Комментарии 25

      0
      А как обеспечивается согласованность между Реляционной БД и индексом? Допустим, если транзакция в БД закоммитилась, а Solr оказался недоступен по каким-то причинам?
        0
        Это обеспечивается доктриной, тем как вызываются EntityListener. Если взглянуть на \Doctrine\ORM\UnitOfWork::commit:
                ...
                try {
         	    ...
                    $this->executeInserts($class);
        	    ...
        
                    $this->executeUpdates($class);
                    ...
        
                    $conn->commit();
                } catch (Exception $e) {
                    $this->em->close();
                    $conn->rollback();
        
                    throw $e;
                }
        

        в рамках вызова executeInserts и executeUpdates происходит вызов EntityListener который запускает индексацию. В случае если solr недоступен будет выброшен эксепшн "\Solarium\Exception\HttpException" и выполнен код в секции catch и изменения в базе откатятся через rollback
          0
          Хорошо, а если в Solr все запишется удачно, а транзакция откатится, например будет выброшено исключение в commit()?
            0
            Да это может случиться, предлагаю заглянуть в \Doctrine\DBAL\Connection::commit (у меня версия doctrine/dbal = v2.5.13):
                 /*
                 * @throws \Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or
                 *                                            because the transaction was marked for rollback only.
                 */
                public function commit()
                {
            	...
            	if ($this->_transactionNestingLevel == 0) {
            	    throw ConnectionException::noActiveTransaction();
            	}
            	if ($this->_isRollbackOnly) {
            	    throw ConnectionException::commitFailedRollbackOnly();
            	}
            	...
                 }
            


            Соотв. эксепшн может быть в ситуации когда «this->_transactionNestingLevel == 0» это возможно если не был вызван $connection->beginTransaction() перед $connection->commit(). Но т.к. индексация происходит в рамках $em->flush() то за вызов "$connection->beginTransaction()" отвечает UnitOfWork, соответственно тут мы в безопасности.

            Второй случай когда может возникнуть эксепшн это "$this->_isRollbackOnly == true". Выполнение этого условия возможно в след. ситуациях (не тестировал, исхожу из чтения кода):
            1. Когда был вызван явно \Doctrine\DBAL\Connection::setRollbackOnly для текущей транзакции до \Doctrine\DBAL\Connection::commit
            2. Либо был вызван \Doctrine\DBAL\Connection::rollBack для вложенной транзакции до \Doctrine\DBAL\Connection::commit

            В обоих случаях если работа идет стандартым способом через $em->flush() оба вышеописанных случая исключены. Имхо эти эксепшены могут возникнуть если разработчик явно использует beginTranscation и endTransaction и допускает ошибку например, забыв где то вызвать beginTranscation что в принципе отслеживается практически сразу либо использует вложенные транзакции, но тут уж он должен понимать тогда что он делает и к чему это приведет.

            Более опасный источник десинхронизации на самом деле не в \Doctrine\DBAL\Connection::commit а в других EntityListener которые могут вызываться для индексируемой сущности. Если после успешной индексации в каком-либо другом EntityListener будет неотлавливаемый эксепшн то получим кейс что индексация в солр прошла но изменения в базу не записались. Тут могу только порекомендовать разработчику иметь это ввиду и не злоупотреблять EntityListener либо корректно обрабатывать ошибки в других EntityListener.

            В итоге рекомендую на каждую измененную сущность делать свой $em->flush($entity) чтобы избежать десинхронизации и не злоупотреблять EntityListener. Как и любой фреймворк/библиотека DoctrineSolrBundle может использоваться неправильно или неоптимально что может привести к ошибке. Если у вас реализуется сложная логика в EntityListener или вы используется вложенные, «ручные» или еще как то усложненные транзакции то возможно DoctrineSolrBundle не будет вам удобен. Возможно в будущей версии я сделаю чтобы можно было отключить автоиндексацию при каждом flush и возможность вызвать индексацию вручную.

              0
              Еще рассинхронизация может произойти при останове/перезапуске веб-сервера, отключении питания, сбоях в работе сети (плдключения к БД, не удалось запрос COMMIT отравить). Во всеъ этих случаях подход, используемый в бандле может привести к рассинхронизации. И дело даже не в том, что что-то не так запрограммировано — дело в принципе, положенном в основу бандла. Для отказоустойчивой синхронизации между БД и индексом нужен двухфаный коммит.
                0
                Как вы себе представляете двухфазный коммит между postgresql и solr? Т.е. вы можете сделать например двухфазный коммит между двумя postgresql базами данных. Солр тут надо рассматривать как third-party api. Между такими разными продуктами это имхо надо городить такой свой огород что неизвестно что дешевле и лучше, пофиксить неконсистентность раз в полгода (что по сути просто пересохранить сущность) или поддерживать свой двухфазный велосипед для солр и рсубд. К тому же это разные продукты с разными целями и внутренним устройством.

                Помимо того коммит в солр это не коммит в смысле реляционной субд, коммит в солр пушит набор изменений в индексе с последнего коммита на диск и эти изменения могут включать в себя запросы от разных пользователей по разным документам. Т.е. rollback в solr откатит все изменения сделанные с последнего коммита, а т.к. это процедура выполняемая (в зависимости от настроек) раз в какой то период (в случае если это не hard commit), то мы можем откатить и измения сделанные другими пользователями, но еще не закомиченные в индекс (поправьте меня если я неправ). Соотв. вместо роллбэка нам остается только переиндексировать сущность в исходном состоянии до того как она была изменена.

                Это можно сделать след образом (протестировано):
                $entityManagerName = 'doctrine.orm.default_entity_manager';
                $page = $this->get($entityManagerName)->getRepository('AppBundle\Entity\Page')
                            ->find(3);
                
                $page->setText($someText);
                
                try {
                    $this->get($entityManagerName)->flush($page);
                } catch(\Exception $e) {
                    if (!$em->isOpen()) {
                        $this->container->get('doctrine')->resetManager($entityManagerName);
                    }
                    $page = $em->getRepository(get_class($page))->find($page->getId());
                    $this->get('mdiyakov_doctrine_solr.manager.index_process_manager')->reindex($page);
                }
                

                При таком подходе даже если в солр были сделаны изменения они откатятся к прежнему состянию.

                И соотв. если сущность создается:
                $entityManagerName = 'doctrine.orm.default_entity_manager';
                $page = new AppBundle\Entity\Page();
                ...
                $page->setText($someText);
                
                try {
                    $this->get($entityManagerName)->flush($page);
                } catch(\Exception $e) {
                    (как в примере выше)...
                    $this->get('mdiyakov_doctrine_solr.manager.index_process_manager')->remove($page);
                }
                


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

                Как вариант вынести индексацию в очередь, но опять же это не гарант того что изменения будут приняты в солр например и не придется руками обрабатывать ошибку. В DoctrineSolrBundle предусмотрена валидация конфига индексируемых сущностей во время инициализации основных сервисов это поможет снизить возможные ошибки во время индексации.
                  0
                  Прекрасно представляю. Вместо того, чтобы сразу писать в Solr пишем необходимые для индексирования данные в отдельную таблицу, назовем ее log в той же РСУБД в той же транзакции. Получится что-то вроде журнала событий. Можно в json или xml положить, будет гибко. В другом процессе (cron, например) читаем необработанные события из log индексируем в solr. Если при обработке события произойдет сбой, событие не отметится как обработанное и процедура повторится. Изменения в индексах типа Solr как правило идемпотентны и могут быть повторены (если например фейл произошел при пометке события обработанным). Вот вам и 2PC.
                  А с подходом в бандле сколько не смотри на исключения, не читай код доктрины, не пиши тестов — все равно может произойти рассинхронизация. Его нельзя назвать отказоустойчивым. Аварийный останов веб-сервера тут никак не обработать, а его нужно учитывать.
                  То что вы с SO привели — я с этим не совсем согласен. Eventual Consistency можно сделать. В MySQL можно вообще бинлог читать и на его основе выполнять индексирование. В Postgres тоже вроде что-то добавили в последних версиях.
                    0
                    Вот вы уже начали изобретать «двухфазный» велосипед и это привело к переусложнению. По вашей логике отправка емейла по крону это двухфазный коммит? Во-первых то что вы описали это не двухфазный коммит, во-вторых даже несмотря на это ваше решение переусложнено и содержит лишнюю логику, в третьих покажу как можно эмулировать «двухфазный» коммит при индексации через cron и чем это грозит.

                    Запуск по крону выполнения операций записи в солр это не двухфазный коммит, вы не получаете от солр никакого потдверждения о том возможна транзакция или нет во время записи в базу. А соответственно и вся суть двухфазного коммита теряется. При двухфазном коммите либо все участники выполняют транзакцию либо нет(делается rollback). У вас же база выполняет транзакцию а солр нет (читаем теорию en.wikipedia.org/wiki/Two-phase_commit_protocol). Solr не реализует ACID (https://ru.wikipedia.org/wiki/ACID) да ему это и не надо соотв. какой вообще может быть разговор о двухфазном коммите скажите мне?

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

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

                    Теперь как можно эмулировать «двухфазный» коммит при индексации через крон. Для каждой индексируемой сущности заводите поле updated_at с DateTime когда был послдений апдейт и реализуете версионность, можно хранить всего две версии — актуальную и предыдущую (причем это касается только индексируемых в солр полей сущности). Скрипт синхронизации с солр по крону достает из базы все сущности у которых updated_at больше временной метки последней синхронизации. И для актуальной версии каждой сущности делает запрос в солр на обновление данных, если солр выкидывает эксепшн и говорит что, например, данные каким то образом не валидны то вы пишите в актуальную версию данные из предыдущей версии для сущности и удаляете предыдущую. Т.о. мы реализуем механизм роллбэка в базе в случае фейла при записи в солр.

                    Чем это грозит ? Представьте что менеджер через какую-нибудь cms тулзовину отредактировала страницу и сохранила. Она видит что сохранение успешно прошло и забывает про эту страницу. Спустя секунду запускается скрипт синхронизации с солр. Он пытается индексировать актуальную версию страницы в солр но солр говорит например что данные не валидны, превышено кол-во символов или еще что нибудь. Тогда происходит откат в базе на пред. версию. И по итогу все изменения теряются, менеджер не в курсе что это произошло и данные не обновлены. Соотв. надо реализовать уведомление о такой ситуации и тд… По итогу это все перерастает в снежний ком всякого говна. Занавес.

                    Да мое решение не отказустойчиво в случае внезапного отказа оборудования. Но этого и не требуется. В случае отказа оборудования вы можете просто запустить переиндексацию и все синхронизируется. В 99% случаев синхронной синхронизации реализованной в бандле достаточно.
                      0
                      Чем это грозит? Представьте что менеджер через какую-нибудь cms тулзовину отредактировала страницу и сохранила. Она видит что сохранение успешно прошло и забывает про эту страницу. Спустя секунду запускается скрипт синхронизации с солр. Он пытается индексировать актуальную версию страницы в солр но солр говорит например что данные не валидны, превышено кол-во символов или еще что нибудь. Тогда происходит откат в базе на пред. версию. И по итогу все изменения теряются, менеджер не в курсе что это произошло и данные не обновлены. Соотв. надо реализовать уведомление о такой ситуации и тд… По итогу это все перерастает в снежний ком всякого говна. Занавес.


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

                      Так вы можете все солровские правила учесть при записи в таблицу log, чтобы не писалось в log и транзакция откатилась.
                      Solr не реализует ACID (https://ru.wikipedia.org/wiki/ACID) да ему это и не надо соотв. какой вообще может быть разговор о двухфазном коммите скажите мне?

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

                      Да мое решение не отказустойчиво в случае внезапного отказа оборудования. Но этого и не требуется. В случае отказа оборудования вы можете просто запустить переиндексацию и все синхронизируется. В 99% случаев синхронной синхронизации реализованной в бандле достаточно.


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

                      В ваших ответах столько эмоций, зачем? Я вас пытаюсь показать лучшее решение, а вы даже слушать не хотите, сразу в штыки и оскорбления — «это велосипед и костыли». В том виде, в котором я описал, индексирование реализуется очень просто, никакого велосипедостроения нет, это необходимо для реализации отказоустойчивости. Я так делал и я не один такой. Попробуйте, оно не так страшно, как представляется на первый взгляд. А у вас «крутая библиотека», которая не является велосипедом, и которая при этом не обеспечивает отказоустойчивости?

                      В 99% случаев синхронной синхронизации реализованной в бандле достаточно.

                      Как процент посчитали?

                        0
                        Ваше решение не отказоустойчиво даже в случае обычного перезапуска веб-сервера или сервера БД, какие там отказы. После каждой перезагрузки или останова предлагаете переиндексировать?


                        Вы серьезно ?) Как насчет hot reload для перезапуска веб-сервера

                        Вы хотите сказать что это нормально просто взять и остановить сервер бд, без того чтобы вывести maintanence page предварительно? Если вы сервер бд стопанете у вас элементарно сайт работать не будет.

                        В ваших ответах столько эмоций, зачем? Я вас пытаюсь показать лучшее решение


                        У меня из эмоций только искреннее удивление вашим трактовкам двухфазного коммита) Вы не лучшее решение пытаетесь показать, вы меня убеждаете в том что запуск скрипта по крону это двухфазный коммит. Я не спорю с фактом что возможна десинхронизация только если свет вырубиться в помещении. Но тк. Solr это вторичное хранилище, вспомогательное а никак не основное то это приемлимо и решается переиндексацией.
                          0
                          Hot reload'ом во всех случаях не отделаешься. Он не всесилен и не вся конфигурация через него меняется. Это отговорки. Админ, который ребутнет сервер, может и не знать, что что-то потом надо переиндексировать.

                          Если вы сервер бд стопанете у вас элементарно сайт работать не
                          будет.


                          Тут дело в моменте остановки — именно в этот момент может много чего в базу и в индекс писаться, что приведет в вашем случае к неконсистентности. Или так по-вашему тоже не бывает?

                          Но тк. Solr это вторичное хранилище, вспомогательное а никак не основное то это приемлимо и решается переиндексацией.


                          Тгда уж лучше по крону с дельтами все индексировать и следить «не рассинхронизировался ли индекс» не надо. Кто за этим будет следить?
                        0
                        Зачем писать индексируемые данные в отдельную таблицу log.


                        Это эмуляция журнала на уровне приложения. Все транзакционные БД имеют внизу такой-же журнал, но нескоолько в другом виде. Такие журналы — основа обеспечения консистентности и воссстановления после сбоев. Вот полезная книга об этом
                          0
                          то что бд имеют журнал это понятно и для этого есть вполне определенные причины. Зачем это дублировать на уровне приложения вот это непонятно, думаю по ссылке в книге там не учат эмулировать этот журнал на уровне приложения)
                            0
                            Ну назвали бы эти «причины». Основная причина наличия журнала — возможность восттановления после сбоя до определенной точки — обеспечение согласованности. Зачем дублировать? Затем чтобы обеспечить согласованность в конечном счете между солром и данными в БД. И вы сами признали то, что решение с доп. таблицей обепечивает надежность даже в случае аварийных остановов. Понятно зачем дублирование?
                          0
                          Теперь как можно эмулировать «двухфазный» коммит при индексации через крон. Для каждой индексируемой сущности заводите поле updated_at с DateTime когда был послдений апдейт и реализуете версионность, можно хранить всего две версии — актуальную и предыдущую (причем это касается только индексируемых в солр полей сущности). Скрипт синхронизации с солр по крону достает из базы все сущности у которых updated_at больше временной метки последней синхронизации. И для актуальной версии каждой сущности делает запрос в солр на обновление данных, если солр выкидывает эксепшн и говорит что, например, данные каким то образом не валидны то вы пишите в актуальную версию данные из предыдущей версии для сущности и удаляете предыдущую. Т.о. мы реализуем механизм роллбэка в базе в случае фейла при записи в солр.


                          И теряете при этом данные.
                            0
                            так да, в этом и суть моего примера) Это пример как можно эмулировать двухфазный коммит с возможностью отката в базе и что из этого может получиться. Это пример как НЕ надо делать
                              0
                              А я это и так знаю, зачем написали?
                            0
                            Ваша интерпретация и реализация 2PC никуда не годится. Вы неправильно его понимаете.
                              0
                              Еще раз, я единственное что пытаюсь донести что двухфазный коммит состоит из двух фаз.

                              При двухфазном коммите либо все участники выполняют транзакцию либо нет(делается rollback). У вас же база выполняет транзакцию а солр нет (читаем теорию en.wikipedia.org/wiki/Two-phase_commit_protocol). Solr не реализует ACID (https://ru.wikipedia.org/wiki/ACID) да ему это и не надо соотв. какой вообще может быть разговор о двухфазном коммите скажите мне?


                              Реализация ACID подразумевает изоляцию и возможно отката транзакции, т.к. Solr это не реализует то и соотв. у него нет соотв. механизмов роллбэка.

                              А у вас «крутая библиотека», которая не является велосипедом, и которая при этом не обеспечивает отказоустойчивости?


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

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

                              И это не двухфазный коммит, тут нет никаких фаз, нет возможности откатить транзакцию всем участникам процесса.
                                0
                                У нас 2PC между таблицей log в РСУБД и индексом solr. Фаза подготовки — вытаскиваем событие из таблицы log, индексируем в solr. Фаза фиксации — записываем в РСУБД, что событие обработано. Если фейл произойдет на фазе подготовки по причине отказа недоступности solr — операция повторится. Если на фазе фиксации — тоже повторится. Консистентность гарантирована. Да роллбека в solr нет, но он не обязателен для 2PC — там главное обеспечить согласованность и мы ее сохраняем при таком подходе. Индекс Solr идемпотентен — можно записать второй раз, ничего плохого не случится.
                                  0
                                  Таким образом, я вижу два отказоустойчивых решения. Первое — полностью индексировать все данные по крону, можно с дельтами. Второе решение — с доп. таблицей с событиями и другим процессом. Второе можно не обязательно по крону — можно и демона написать, который будет поллить таблицу, будет near real time. Ну или cron раз в минуту). И первое и второе реализуется легко. Попробуйте.

                                  Но как часто это случается и даже если это случилось идете и запускаете переиндексацию и всё.

                                  Вот он, авось). Это можно если вы на одном проекте сидите. А если не вы будете поддерживать? За каждым следить? Нет уж, пусть лучше само восстанавливается. А в чем конкретно сложность приведенного мной решения?
                                    0
                                    Ну вот вы уже и демона писать начали) Демон на пыхе не самое тривиальное решение. В общем я еще раз объясняю при вашем подходе может возникнуть ошибка записи в солр, ну например из-за невалидных данных в солр (при записи date например кривой формат даты или пустое значение в required поле ), и повторно пытаться запускать запись смысла нет. При вашем подходе ваш «двухфазный коммит» будет ждать когда руками отредактируют данные, либо схему solr. И по итогу какое то время опять будет неконсистентность. А если еще логирование плохо настроено то вообще неизвестно когда вы узнаете что у вас там что то неиндексируется. Опять же если индексация реализована так что при таком эксепшне стопать выполняющийся скрипт, то у вас и след изменения зависнут, пока вы не почините битый документ.

                                    Мой подход предпологает синхронную запись в солр которая запускается как правило в потоке пользователя. Открыл страницу, отредактировал, сохранил, на фоне происходит инексация в солр. Если что то не сохранилось/грохнулось это видно сразу, как правило в таких случаях идешь обратно на страницу и пересохраняешь. Это уже дело разработчика обработать эксепшн и указать пользователю что делать. Это не вопрос моего бандла. Я привел выше как можно откатить индексацию в солр если flush вызвал exception.

                                    Я выбрал свой подход — это синхронная индексация через EntityListener. Тут я вижу проблему только если внезапно был вырублен свет, да в этом случае можно просто сделать переиндексацию (вопрос что об этом надо знать админу, ну уж это компетенция тех лидера команды). Во всех остальных ситуациях фейл можно отследить и обработать оперативно. Подход с поллингом и демоном имеет место быть, но имхо это не необходимость и может вызвать потерю во времени индексации и в зависимости от реализации сприта синхронизации, усложнение поддержки.

                                    Я указал в статье что индексация реализована через EntityListener. Вы как разработчик вольны выбрать устраивает вас такой подход или нет. Если вас лично не устраиват или вы собираетесь проводить платежные транзакции через солр — ради бога не пользуйтесь моим бандлом, пишите поллинги/демоны тем более для вас это похоже плёвое дело. Но я не собираюсь городить демонов и сприты поллинга в моейм случае. Спасибо за дискуссию
                0
                deleted

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

                Самое читаемое