Инженерия Data Science: 3 лучшие практики по драйверам Neo4j

Автор Категория ,
Инженерия Data Science: 3 лучшие практики по драйверам Neo4j

Зачем проверять подключение к Neo4j, какую URI-схему выбрать, чем плохи транзакции с автофиксацией и как передавать переменные в Cypher-запросы: рекомендации по использованию драйверов графовой СУБД в реальных приложениях аналитики больших данных.

Драйверы и особенности подключения к базе данных

Напомним, драйвер – это сущность, которая реализует определённые API-интерфейсы для взаимодействия с сервером базы данных. По сути, драйвер базы данных является прослойкой между пользовательским кодом и базой данных, обеспечивая переносимость приложения на разные базы без привязки к движку СУБД. Для драйверов на основе СУБД обычно требуется сложный набор сведений о соединении, например, сетевой адрес, протокол, имя базы данных и пр.

Как и в любой СУБД, драйверы позволяют интегрировать Neo4j с нужным языком программирования. Neo4j предоставляет драйверы, которые позволяют разработчику подключаться к базе данных, создавая приложения, которые записывают, считывают, обновляют и удаляют информацию из графа. Neo4j официально поддерживает драйверы для .Net, Java, JavaScript, Go и Python для бинарного протокола Bolt. Драйверы представляют собой тяжеловесные объекты, которые предполагается многократное повторное использование. После создания экземпляра драйвера следует проверить подключение, например:

from neo4j import GraphDatabase
uri = "neo4j+s://my-aura-instance.databases.neo4j.io"
driver = GraphDatabase.driver(uri, auth=("neo4j", "password"))
driver.verify_connectivity()

Обычно драйвер Neo4j создает соединения по мере необходимости и управляет ими в пуле. Проверка подключения в самом начале работы, заставляет драйвер создать подключение в этот момент. Таким образом можно предупредить ошибки, связанные с неверным адресом или учетными записями. Для версий Neo4j после 4.0 лучше всего подойдет URI-схема подключения neo4j+s://. Она работает с защищенными базами данных с одним экземпляром, а также с кластерами, выполняет проверку сертификата и обеспечивает безопасное соединение. Схема подключения Bolt:// подключается только к одному компьютеру и не очень подходит для кластерного режима.

Объекты-драйверы в Neo4j содержат пулы соединений, и их создание может длиться долго, порядка нескольких секунд, т.к. помимо самого подключения, производится установка всех необходимых соединений. В результате приложение должно создать только один экземпляр драйвера для каждой СУБД Neo4j, сохранить его и использовать для всего. Это особенно важно в таких средах, как AWS Lambda и других типах бессерверных облачных функциях, где создание экземпляра драйвера каждый раз при запуске кода снижает производительность.

драйверы Neo4j
Драйверы Neo4j

Cypher-транзакции в Neo4j

Напомним, в Neo4j транзакции — это атомарные единицы работы, состоящие из одного или нескольких исполнений оператора языка запросов Cypher. Транзакция выполняется в рамках сеанса. Для выполнения оператора Cypher требуются две части информации: шаблон оператора и набор параметров с ключом. Шаблон представляет собой строку, которая наполняется значениями параметров во время выполнения. Несмотря на то, что можно запускать Cypher-код без параметров, хорошей практикой считается использование параметров в операторах. Это позволяет кэшировать операторы в движке Cypher, что полезно для производительности. API драйвера Neo4j предусматривает три формы транзакций:

  • транзакции с автоматической фиксацией, которые отправляются в сеть и немедленно подтверждаются. Несколько транзакций не могут совместно использовать сетевые пакеты, что снижает эффективность использования сети. Auto-commit не предназначены для применения в production, когда важно обеспечить производительность или отказоустойчивость приложения. Однако, автофиксация транзакций — единственный способ выполнить Cypher-оператор USING PERIODIC COMMIT, который может предотвратить нехватку памяти при импорте больших объемов данных с помощью функции LOAD CSV, нарушив изоляцию транзакций.
  • транзакционные функции (Transaction functions), которые могут автоматически воспроизводится после сбоя, требуют минимум шаблонного кода, а также позволяют четко разделить запросы к базе данных и логику приложения. Функции транзакций также могут обрабатывать проблемы с подключением и временные ошибки, используя механизм автоматического повтора, который можно настроить в конструкции драйвера. Любые результаты запроса, полученные внутри функции транзакции, должны использоваться внутри нее. Функции транзакций могут возвращать значения, но это должны быть производные значения, а не необработанные результаты. Именно эту форму рекомендуется использовать в реальных проектах, т.е. production-среде.
  • явные транзакции (Explicit transactions) – сокращенная форма функций транзакций, обеспечивающая доступ к явным операциям BEGIN, COMMIT и ROLLBACK.

Про тонкости обработки транзакций с помощью механизма закладок мы рассказываем в следующей статье, а пока рассмотрим, почему форма Transaction functions считается лучшей практикой. Драйверы Neo4j не анализируют пользовательский Cypher-запрос, а кластеры графовой СУБД используют маршрутизируемые драйверы. Поэтому в случае транзакций с автоматической фиксацией все запросы на чтение и на запись всегда будут отправляться лидеру кластера. Таким образом, к примеру, 2/3 узлов кластера из 3 машин будут простаивать, поскольку все запросы направились на один и тот же компьютер. Другой недостаток использования транзакций session.run и Auto-commit заключается в потере контроля над поведением фиксации: становится невозможным запустить несколько Cypher-запросов, которые все фиксируются один за другим по отдельности, а не одновременно.

Таким образом, вместо следующего кода с авто-фиксацией транзакции:

with driver.session() as session:
session.run("CREATE (p:Person { name: $name })", name="Ann")

рекомендуется использовать транзакционную функцию:

def add_person(self, name):
with driver.session() as session:
session.write_transaction(create_person_node, name)

def create_person_node(tx, name):
return tx.run("CREATE (a:Person {name: $name})", name=name)

В этом участке кода задание Cypher инкапсулирован в create_person_node. А вызов session.write_transaction сообщает драйверу, что это транзакция, содержащая операции записи. При использовании session.read_transaction для чтения драйвер может распределить работу между узлами кластера.

Этот же участок кода демонстрирует другую лучшую практику применения параметров с именем $name для передачи переменной в запрос Cypher. Рекомендуется использовать эти параметры везде, где данные подставляются в запросы без всякой конкатенации строк. Использование параметров запроса дает следующие преимущества:

  • повышение безопасности, включая защиту от атак с внедрением (Cypher Injection Attacks);
  • уменьшение форм запросов, позволяя базе данных выполнять их быстрее, вместо компиляции нового запроса в виде ранее неизвестной строки.

Больше реальных примеров применения Neo4j и других инструментов графовой аналитики больших данных для практических бизнес-задач вы узнаете на специализированных курсах в нашем лицензированном учебном центре обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков больших данных в Москве:

Источники

  1. https://medium.com/neo4j/neo4j-driver-best-practices-dfa70cf5a763
  2. https://neo4j.com/developer/language-guides/
  3. https://neo4j.com/docs/driver-manual/1.7/sessions-transactions/