На чемпионате США по воздушной гитаре участники используют свои таланты, чтобы раздражать «невидимую» гитару, чтобы раскачивать живую толпу и демонстрировать исполнение, которое выходит за рамки имитации настоящей гитары и само по себе становится формой искусства. Ключевым фактором, который определяет победителя, является неуловимое качество « Воздушность ». При рассмотрении использования Neo4j в проекте одним из ключевых соображений является наличие доменной модели, которая поддается представлению графа. Другими словами, есть ли у ваших данных « графичность»«. Тем не менее, до недавнего времени до меня не дошло, что, когда вы начинаете доказательство концепции, у вас, вероятно, нет этих данных (или их достаточно), или, возможно, ваши сотрудники службы безопасности не позволят вам в пределах 100 миль от производства компании. данные с этим новомодным nosql thingamajig.
Таким образом, чтобы проверить наши идеи и создать доказательство концепции, нам нужно будет сгенерировать пример данных и протестировать наши алгоритмы (иначе как запросы Cypher и Gremlin). Я покажу вам, как построить генератор элементарных графов, вам придется настроить его в соответствии с вашим доменом, но это только начало. Мы также будем использовать Batch Importer для быстрой загрузки наших данных в Neo4j.
Если вы помните, у меня было три сообщения в блоге о Batch Importer. В первом я показал вам, как установить Batch Importer , во втором я показал вам, как использовать данные в вашей реляционной базе данных для генерации CSV-файлов для создания вашего графика , и совсем недавно я показал вам, как быстро индексировать ваши данные .
Пакетный импортер ожидает серию разделенных табуляцией файлов для ввода. Итак, давайте сгенерируем эти файлы. Мы создадим граф с 6 типами узлов. Вот они с количеством каждого, которое мы собираемся создать:
# Nodes # Users 21,000 # Companies 4,000 # Activity 1.2M # Item 1.3M # Entity 3.5M # Tags 20,000
Мы свяжем их вместе с набором отношений:
# Relationships # Users -[:belongs_to]-> Companies # User -[:performs]-> Activity # Activity -[:belongs_to]-> Item # Item -[:references]-> Entity # Item -[:tagged]-> Tags #
Чтобы упростить этот пример, мы создадим всего два индекса. Полнотекстовый индекс узла, называемый «вершинами», и точный индекс отношения, называемый «ребрами». Возможно, вы захотите создать несколько индексов для каждого типа узла или отношения.
Я хочу сделать это прямо сейчас, поэтому мы сделаем это с помощью ряда команд rake:
rake neo4j:install rake neo4j:create rake neo4j:load rake neo4j:start
Если вы следили за моим блогом, вы знаете, что делают установка и запуск, но нам нужно создать метод, который будет обрабатывать создание и загрузку. Мы можем быстро создать Rakefile для них:
require 'neography/tasks' require './neo_generate.rb' namespace :neo4j do task :create do create_graph end task :load do load_graph end end
Теперь мы можем начать с create_graph. Если вы помните, пакетный импортер ищет серию разделенных табуляцией файлов. Один, который содержит узлы, другой для отношений и, необязательно, другие файлы для каждого индекса, который вы хотите создать. Каждый файл имеет заголовок с некоторыми свойствами, поэтому наш метод create_graph будет выглядеть так:
def create_graph create_node_properties create_nodes create_nodes_index create_relationship_properties create_relationships create_relationships_index end
Я собираюсь здесь произвольно решить, что каждый из моих узлов будет иметь два свойства, и мы назовем их property1 и property2, потому что я супер креативен, когда дело доходит до именования вещей.
def create_node_properties @node_properties = ["type", "property1", "property2"] generate_node_properties(@node_properties) end
Я сказал два? Я имел в виду три. Просто для собственного здравого смысла я хотел бы дать узлам свойство типа и указать тип узла, которым они являются, поэтому мы включим «тип» в качестве первого свойства.
# Recreate nodes.csv and set the node properties # def generate_node_properties(args) File.open("nodes.csv", "w") do |file| file.puts properties.join("\t") end end
С нашим заголовком мы можем обратить наше внимание на фактическое создание этих узлов. Мы будем использовать хеш, который будет иметь тип узла, начальный и конечный идентификаторы узлов и некоторые свойства. Но какими свойствами должны обладать наши узлы? Какими должны быть их ценности? Это немного сложно. Самое простое решение состоит в том, чтобы просто сгенерировать gobbledygook со случайными строками:
# Generate random lowercase text of a given length # # Args # length - Integer (default = 8) # def generate_text(length=8) chars = 'abcdefghjkmnpqrstuvwxyz' key = '' length.times { |i| key << chars[rand(chars.length)] } key end
Другой возможностью является использование одного из драгоценных камней генератора случайных данных, таких как подделка, для создания более интеллектуальных и конкретных случайных данных (например, имен женщин ). В этот раз мы выберем легкий путь и просто дадим каждому узлу два случайных свойства, за исключением пользователя и компании, которые получат свойства от Подделки.
def create_nodes # Define Node Property Values node_values = [lambda { generate_text }, lambda { generate_text }] user_values = [lambda { Forgery::Name.full_name }, lambda { Forgery::Personal.language }] company_values = [lambda { Forgery::Name.company_name }, lambda { Forgery::Name.industry }] @nodes = {"user" => { "start" => 1, "end" => 21000, "props" => user_values}, "company" => { "start" => 21001, "end" => 25000, "props" => company_values}, "activity" => { "start" => 25001, "end" => 1225000, "props" => node_values}, "item" => { "start" => 1225001, "end" => 2525000, "props" => node_values}, "entity" => { "start" => 2525001, "end" => 6025000, "props" => node_values}, "tag" => { "start" => 6025001, "end" => 6045000, "props" => node_values} } # Write nodes to file @nodes.each{ |node| generate_nodes(node[0], node[1])} end
Отлично, теперь, чтобы наконец сгенерировать эти узлы, мы напишем в node.csv тип узла и будем называть нашу лямбду, чтобы каждый узел получал различную случайную строку.
# Generate nodes given a type and hash # def generate_nodes(type, hash) puts "Generating #{(1 + hash["end"] - hash["start"])} #{type} nodes..." nodes = File.open("nodes.csv", "a") (1 + hash["end"] - hash["start"]).times do |t| properties = [type] + hash["props"].collect{|l| l.call} nodes.puts properties.join("\t") end nodes.close end
Наш файл node.csv будет выглядеть следующим образом:
type property1 property2 user Helen Harvey Kashmiri user Sean Matthews Afrikaans user William Harper Haitian Creole user Bruce Hill Macedonian user Chris Riley Swahili
С узлами в стороне, пришло время для отношений. Мы будем простыми и скажем, что у каждого отношения также есть два свойства.
def create_relationship_properties @rel_properties = ["property1", "property2"] generate_rel_properties(@rel_properties) end
Я имел в виду три свойства. Еще раз я добавляю тип, но это отличается от типа узла выше, поскольку каждое отношение в Neo4j ДОЛЖНО иметь тип, это не необязательное свойство. «\ T», который вы видите ниже, помещает вкладки между каждым полем, извините, если я не упомянул об этом раньше, и вы были как, черт возьми, это?
# Recreate rels.csv and set the relationship properties # def generate_rel_properties(properties) File.open("rels.csv", "w") do |file| header = ["start", "end", "type"] + properties file.puts header.join("\t") end end
Я показал вам, как создавать хорошие поддельные данные для узлов, поэтому мы будем делать это просто и просто делать случайные 8-символьные строки. Я использую числовое поле, чтобы установить, сколько из этих отношений будет создано, их тип обязателен и некоторые свойства. Вы также заметите, что у меня есть ключ «подключения»: последовательный или случайный. Я объясню это немного позже.
def create_relationships # Define Relationsihp Property Values rel_values = [lambda { generate_text }, lambda { generate_text }] rels = {"user_to_company" => { "from" => @nodes["user"], "to" => @nodes["company"], "number" => 21000, "type" => "belongs_to", "props" => rel_values, "connection" => :sequential }, "user_to_activity" => { "from" => @nodes["user"], "to" => @nodes["activity"], "number" => 1200000, "type" => "performs", "props" => rel_values, "connection" => :random }, "activity_to_item" => { "from" => @nodes["activity"], "to" => @nodes["item"], "number" => 3000000, "type" => "belongs", "props" => rel_values, "connection" => :random }, "item_to_entity" => { "from" => @nodes["item"], "to" => @nodes["entity"], "number" => 6000000, "type" => "references", "props" => rel_values, "connection" => :random }, "item_to_tag" => { "from" => @nodes["item"], "to" => @nodes["tag"], "number" => 250000, "type" => "tagged", "props" => rel_values, "connection" => :random } } # Write relationships to file rels.each{ |rel| generate_rels(rel[1])} end
Я использую «соединение», чтобы решить, как соединить эти узлы вместе. Я генерирую либо случайные соединения между узлами, либо генерирую последовательные соединения (так как в каждом «от узла» подключается к одному «к узлу», пока не будет больше соединений, и если будет больше соединений, чем узлов, мы зациклимся).
Не стесняйтесь объединять их или создавать новые типы соединений (например, кластеризованные)
def generate_rels(hash) puts "Generating #{hash["number"]} #{hash["type"]} relationships..." File.open("rels.csv", "a") do |file| case hash["connection"] when :random hash["number"].times do |t| file.puts "#{rand(hash["from"]["start"]..hash["from"]["end"])}\t#{rand(hash["to"]["start"]..hash["to"]["end"])}\t#{hash["type"]}\t#{hash["props"].collect{|l| l.call}.join("\t")}" end when :sequential from_size = hash["from"]["end"] - hash["from"]["start"] to_size = hash["to"]["end"] - hash["to"]["start"] hash["number"].times do |t| file.puts "#{hash["from"]["start"] + (t % from_size)}\t#{hash["to"]["start"] + (t % to_size)}\t#{hash["type"]}\t#{hash["props"].collect{|l| l.call}.join("\t")}" end end end end
Наш файл rels.csv будет выглядеть следующим образом:
start end type property1 property2 1 21001 belongs_to sjqwkvag vpxahvcr 2 21002 belongs_to pfxnxznu vrprnpky 3 21003 belongs_to gcyxumgy nrxepdzb 4 21004 belongs_to aayyejkw xpenqebd 5 21005 belongs_to hvhjexas kmyqucmn
Чтобы создать наш индекс узла, мы просто откроем node.csv и выведем его, добавив идентификатор узла в качестве первого столбца. Майкл работает над использованием заголовков node.csv, чтобы сказать Batch Importer индексировать узлы, но пока эта работа не будет выполнена, это будет работать.
def create_nodes_index puts "Generating Node Index..." nodes = File.open("nodes.csv", "r") nodes_index = File.open("nodes_index.csv","w") counter = 0 while (line = nodes.gets) nodes_index.puts "#{counter}\t#{line}" counter += 1 end nodes.close nodes_index.close end
Следовательно, node_index.csv будет выглядеть так:
0 type property1 property2 1 user Helen Harvey Kashmiri 2 user Sean Matthews Afrikaans 3 user William Harper Haitian Creole 4 user Bruce Hill Macedonian 5 user Chris Riley Swahili
Мы сделаем нечто подобное со связями, но пропустим начальный и конечный узлы, а также тип связи.
def create_relationships_index puts "Generating Relationship Index..." rels = File.open("rels.csv", "r") rels_index = File.open("rels_index.csv","w") counter = -1 while (line = rels.gets) size ||= line.split("\t").size rels_index.puts "#{counter}\t#{line.split("\t")[3..size].join("\t")}" counter += 1 end rels.close rels_index.close end
Наш файл rels_index.csv будет выглядеть так:
-1 property1 property2 0 nwjsbmgg gnsnefrf 1 szqqygra maumqtnp 2 pdtamztw uvcserrp 3 wewdtztx bkezsmva 4 gynprabv eszjgmfs 5 drcaxsse ungxbzzm
Давайте запустим neo4j: create для генерации этих файлов. Сейчас самое подходящее время для быстрого растягивания, биоразрыва и т. Д., Так как это может занять пару минут.
Generating 21000 user nodes... Generating 4000 company nodes... Generating 1200000 activity nodes... Generating 1300000 item nodes... Generating 3500000 entity nodes... Generating 20000 tag nodes... Generating Node Index... Generating 21000 belongs_to relationships... Generating 1200000 performs relationships... Generating 3000000 belongs relationships... Generating 6000000 references relationships... Generating 250000 tagged relationships... Generating Relationship Index...
С возвращением, теперь у нас сгенерированы эти четыре CSV-файла, нам нужно запустить пакетный импортер, чтобы поместить их в Neo4j. Поэтому мы запустим rake neo4j: load, чтобы это произошло, что, как вы помните, вызывает метод load_graph. Это выглядит так:
# Execute the command needed to import the generated files # def load_graph puts "Running the following:" command ="java -server -Xmx4G -jar ../batch-import/target/batch-import-jar-with-dependencies.jar neo4j/data/graph.db nodes.csv rels.csv node_index vertices fulltext nodes_index.csv rel_index edges exact rels_index.csv" puts command exec command end </pre></p>
Пакетный импортер теперь сделает свое дело:
............................................................ Importing 6045000 Nodes took 39 seconds ....................................................................................................377369 ms for 10000000 .... Importing 10471000 Relationships took 476 seconds ............................................................ Importing 6045000 Nodes into vertices Index took 226 seconds ....................................................................................................261031 ms for 10000000 .... Importing 10471000 Relationships into edges Index took 266 seconds 1153 seconds
Теперь мы можем запустить rake neo4j: начать видеть наш график в Neo4j.
Давайте перейдем к консоли и убедимся, что наши данные там есть:
START me = node:vertices(type="user") RETURN me LIMIT 5
Успех!
==> +-------------------------------------------------------------------------------+ ==> | me | ==> +-------------------------------------------------------------------------------+ ==> | Node[1]{property2->"Kashmiri",property1->"Helen Harvey",type->"user"} | ==> | Node[2]{property2->"Afrikaans",property1->"Sean Matthews",type->"user"} | ==> | Node[3]{property2->"Haitian Creole",property1->"William Harper",type->"user"} | ==> | Node[4]{property2->"Macedonian",property1->"Bruce Hill",type->"user"} | ==> | Node[5]{property2->"Swahili",property1->"Chris Riley",type->"user"} | ==> +-------------------------------------------------------------------------------+ ==> 5 rows, 111 ms