Анализ на големи данни с Neo4j и Java, част 1

Релационните бази данни доминират в управлението на данни от десетилетия, но наскоро те загубиха позициите си спрямо алтернативите на NoSQL. Въпреки че NoSQL хранилищата данни не са подходящи за всеки случай на употреба, те обикновено са по-добри за големи данни , което е стенография за системи, които обработват огромни обеми данни. Четири типа хранилище за данни се използват за големи данни:

  • Магазини за ключ / стойност като Memcached и Redis
  • Ориентирани към документи бази данни като MongoDB, CouchDB и DynamoDB
  • Ориентирани към колони хранилища за данни като Cassandra и HBase
  • Графирайте бази данни като Neo4j и OrientDB

Този урок въвежда Neo4j, който е база данни с графики, използвана за взаимодействие със силно свързани данни . Докато релационните бази данни са добри в управлението на връзките между данните, графичните бази данни са по-добри в управлението на n-тастепенни отношения. Като пример вземете социална мрежа, където искате да анализирате модели, включващи приятели, приятели на приятели и т.н. Базата данни с графики би улеснила отговора на въпрос като: „Като се имат предвид пет степени на разделяне, кои са пет филма, популярни в моята социална мрежа, които все още не съм гледал?“ Такива въпроси са често срещани за препоръчителния софтуер, а графичните бази данни са идеални за решаването им. Освен това графичните бази данни са добри в представянето на йерархични данни, като контрол на достъпа, продуктови каталози, бази данни за филми или дори мрежови топологии и организационни диаграми. Когато имате обекти с множество връзки, бързо ще откриете, че графичните бази данни предлагат елегантна, обектно-ориентирана парадигма за управление на тези обекти.

Случаят за графични бази данни

Както подсказва името, базите данни с графики са добри в представянето на графики с данни. Това е особено полезно за социалния софтуер, където всеки път, когато се свързвате с някого, се определя връзка между вас. Вероятно при последното ви търсене на работа сте избрали няколко компании, които са ви интересували, и след това сте търсили в социалните си мрежи връзки с тях. Въпреки че може да не познавате някой, работещ за някоя от тези компании, някой във вашата социална мрежа вероятно знае. Решаването на такъв проблем е лесно при една или две степени на разделяне (ваш приятел или приятел на приятел), но какво се случва, когато започнете да разширявате търсенето в мрежата си?

В своята книга, Neo4j в действие, Aleksa Vukotic и Nicki Watt изследват разликите между релационните бази данни и графичните бази данни за решаване на проблеми в социалната мрежа. Ще се опирам на тяхната работа за следващите няколко примера, за да ви покажа защо графичните бази данни стават все по-популярна алтернатива на релационните бази данни.

Моделиране на сложни взаимоотношения: Neo4j срещу MySQL

От гледна точка на компютърните науки, когато мислим за моделиране на взаимоотношения между потребители в социална мрежа, можем да начертаем графика като тази на фигура 1.

Стивън Хейнс

Потребителят има IS_FRIEND_OFвзаимоотношения с други потребители и тези потребители имат IS_FRIEND_OFвзаимоотношения с други потребители и т.н. Фигура 2 показва как бихме представили това в релационна база данни.

Стивън Хейнс

На USERмасата има един-към-много с USER_FRIENDтаблицата, която модели на "приятел" връзка между двама потребители. Сега, след като сме моделирали връзките, как бихме искали данните си? Вукотич и Уат измериха ефективността на заявката за преброяване на броя на различни приятели, излизащи на дълбочина от пет нива (приятели на приятели, приятели на приятели на приятели). В релационна база данни заявките ще изглеждат както следва:

 # Depth 1 select count(distinct uf.*) from user_friend uf where uf.user_1 = ? # Depth 2 select count(distinct uf2.*) from user_friend uf1 inner join user_friend uf2 on uf1.user_1 = uf2.user_2 where uf1.user_1 = ? # Depth 3 select count(distinct uf3.*) from t_user_friend uf1 inner join t_user_friend uf2 on uf1.user_1 = uf2.user_2 inner join t_user_friend uf3 on uf2.user_1 = uf3.user_2 where uf1.user_1 = ? # And so on... 

Интересното при тези заявки е, че всеки път, когато излезем на още едно ниво, от нас се изисква да се присъединим към USER_FRIENDмасата със себе си. Таблица 1 показва какво са открили изследователите Вукотич и Уат, когато са добавили 1000 потребители с приблизително 50 връзки всеки (50 000 връзки) и са изпълнили заявките.

Таблица 1. Време за отговор на заявката на MySQL за различни дълбочини на връзките

Дълбочина Време за изпълнение (секунди) Брой резултат

2 0,028 ~ 900
3 0,213 ~ 999
4 10.273 ~ 999
5 92,613 ~ 999

MySQL се справя чудесно с обединяването на данни на разстояние до три нива, но производителността бързо се влошава след това. Причината е, че всеки път, когато USER_FRIENDтаблицата се присъедини към себе си, MySQL трябва да изчисли декартовия продукт на таблицата, въпреки че по-голямата част от данните ще бъдат изхвърлени. Например, когато изпълнявате това съединение пет пъти, декартовият продукт води до 50 000 ^ 5 реда или 102,4 * 10 ^ 21 реда. Това е загуба, когато се интересуваме само от 1000 от тях!

След това Вукотич и Уат се опитаха да изпълнят същия тип заявки срещу Neo4j. Тези напълно различни резултати са показани в таблица 2.

Таблица 2. Време за реакция на Neo4j за различни дълбочини на взаимоотношения

Дълбочина Време за изпълнение (секунди) Брой резултат

2 0,04 ~ 900
3 0,06 ~ 999
4 0,07 ~ 999
5 0,07 ~ 999

The takeaway from these execution comparisons is not that Neo4j is better than MySQL. Rather, when traversing these types of relationships, Neo4j's performance is dependent on the number of records retrieved, whereas MySQL's performance is dependent on the number of records in the USER_FRIEND table. Thus, as the number of relationships increases, the response times for MySQL queries will likewise increase, whereas the response times for Neo4j queries will remain the same. This is because Neo4j's response time is dependent on the number of relationships for a specific query, and not on the total number of relationships.

Scaling Neo4j for big data

Extending this thought project one step further, Vukotic and Watt next created a million users with 50 million relationships between them. Table 3 shows results for that data set.

Table 3. Neo4j response time for 50 million relationships

DepthExecution time (seconds)Count result

2 0.01 ~2,500
3 0.168 ~110,000
4 1.359 ~600,000
5 2.132 ~800,000

Needless to say, I am indebted to Aleksa Vukotic and Nicki Watt and highly recommend checking out their work. I extracted all the tests in this section from the first chapter of their book, Neo4j in Action.

Getting started with Neo4j

You've seen that Neo4j is capable of executing massive amounts of highly related data very quickly, and there's no doubt it's a better fit than MySQL (or any relational database) for certain kinds of problems. If you want to understand more about how Neo4j works, the easiest way is to interact with it through the web console.

Start by downloading Neo4j. For this article, you'll want the Community Edition, which as of this writing is at version 3.2.3.

  • On a Mac, download a DMG file and install it as you would any other application.
  • On Windows, either download an EXE and walk through an installation wizard or download a ZIP file and decompress it on your hard drive.
  • On Linux, download a TAR file and decompress it on your hard drive.
  • Alternatively, use a Docker image on any operating system.

След като инсталирате Neo4j, стартирайте го и отворете прозореца на браузъра до следния URL:

//127.0.0.1:7474/browser/

Влезте с потребителското име по подразбиране на neo4jи паролата по подразбиране на neo4j. Трябва да видите екран, подобен на Фигура 3.

Стивън Хейнс

Възли и взаимоотношения в Neo4j

Neo4j е проектиран около концепцията за възли и взаимоотношения:

  • А възел представлява нещо като потребител, филм или книга.
  • Възелът съдържа набор от двойки ключ / стойност , като име, заглавие или издател.
  • Етикетът на възел определя какъв тип е това - отново потребител, филм или книга.
  • Взаимоотношенията определят асоциации между възлите и са от специфични типове.

Като пример бихме могли да определим символни възли като Iron Man и Captain America; дефинирайте възел на филм, наречен "Отмъстители"; и след това определете APPEARS_INвръзка между Железния човек и Отмъстителите и Капитан Америка и Отмъстителите. Всичко това е показано на Фигура 4.

Стивън Хейнс

Фигура 4 показва три възела (два символни възела и един филмов възел) и две връзки (и двете от тип APPEARS_IN).

Моделиране и заявки за възли и връзки

Подобно на това как релационната база данни използва структуриран език за заявки (SQL) за взаимодействие с данни, Neo4j използва Cypher Query Language за взаимодействие с възли и връзки.

Нека използваме Cypher, за да създадем просто представяне на семейство. В горната част на уеб интерфейса потърсете знака за долар. Това показва поле, което ви позволява да изпълнявате заявки на Cypher директно срещу Neo4j. Въведете следната заявка на Cypher в това поле (използвам семейството си като пример, но не се колебайте да промените подробностите, за да моделирате собственото си семейство, ако искате):

CREATE (person:Person {name: "Steven", age: 45}) RETURN person

Резултатът е показан на фигура 5.

Стивън Хейнс

На Фигура 5 можете да видите нов възел с етикет Person и името Steven. Ако задържите мишката върху възела във вашата уеб конзола, ще видите свойствата му в долната част. В този случай свойствата са ID: 19, име: Steven и възраст: 45. Сега нека разделим заявката на Cypher:

  • CREATE: The CREATE keyword is used to create nodes and relationships. In this case, we pass it a single argument, which is a Person enclosed in parentheses, so it is meant to create a single node.
  • (person: Person {...}): The lower case "person" is a variable name through which we can access the person being created, while the capital "Person" is the label. Note that a colon separates the variable name from the label.
  • {name: "Steven, age: 45}: These are the key/value properties that we're defining for the node we're creating. Neo4j does not require you to define a schema before creating nodes and each node can have a unique set of elements. (Most of the time you define nodes with the same label to have the same properties, but it is not required.)
  • RETURN person: After the node is created, we ask Neo4j to return it back to us. This is why we saw the node appear in the user interface.

The CREATE command (which is case insensitive) is used to create nodes and can be read as follows: create a new node with the Person label that contains name and age properties; assign it to the person variable and return it back to the caller.

Querying with Cypher Query Language

Next we want to try some querying with Cypher. First, we'll need to create a few more people, so that we can define relationships between them.

 CREATE (person:Person {name: "Michael", age: 16}) RETURN person CREATE (person:Person {name: "Rebecca", age: 7}) RETURN person CREATE (person:Person {name: "Linda"}) RETURN person 

Once you've created your four people, you can either click on the Person button under the Node Labels (visible if you click on the database icon in the upper left corner of the web page) or execute the following Cypher query:

MATCH (person: Person) RETURN person

Cypher uses the MATCH keyword to find things in Neo4j. In this example, we are asking Cypher to match all nodes that have a label of Person, assign those nodes to the person variable, and return the value that is associated with that variable. As a result you should see the four nodes that you've created. If you hover over each node in your web console, you will see each person's properties. (You might note that I excluded my wife's age from her node, illustrating that properties do not need to be consistent across nodes, even of the same label. I am also not foolish enough to publish my wife's age.)

We can extends this MATCH example a little further by adding conditions to the nodes we want returned. For example, if we wanted just the "Steven" node, we could retrieve it by matching on the name property:

MATCH (person: Person {name: "Steven"}) RETURN person

Or, if we wanted to return all of the children we could request all people having an age under 18:

MATCH (person: Person) WHERE person.age < 18 RETURN person

In this example we added the WHERE clause to the query to narrow our results. WHERE works very similarly to its SQL equivalent: MATCH (person: Person) finds all nodes with the Person label, and then the WHERE clause filters values out of the result set.

Modeling direction in relationships

We have four nodes, so let's create some relationships. First of all, let's create the IS_MARRIED_TO relationship between Steven and Linda:

MATCH (steven:Person {name: "Steven"}), (linda:Person {name: "Linda"}) CREATE (steven)-[:IS_MARRIED_TO]->(linda) return steven, linda

В този пример ние съвпадаме с два възела на Person, обозначени като Steven и Linda, и създаваме връзка от тип IS_MARRIED_TOот Steven до Linda. Форматът за създаване на връзката е както следва:

(node1)-[relationshipVariable:RELATIONSHIP_TYPE->(node2)