Перші враження від Amazon Neptune

Салют, хабрівчани. Напередодні старту курсу «AWS для розробників» підготували переведення цікавого матеріалу.

Перші враження від Amazon Neptune

У багатьох юзкейсах, які ми, як bakdata, бачимо на сайтах наших клієнтів, релевантна інформація прихована у зв'язках між сутностями, наприклад, під час аналізу відносин між користувачами, залежностей між елементами чи з'єднань між датчиками. Такі юзкейс зазвичай моделюються на графі. Раніше цього року Amazon випустила нову графову базу даних Neptune. У цьому пості ми хочемо поділитися нашими першими ідеями, хорошими практиками та тим, що може бути покращено з часом.

Навіщо нам знадобився Amazon Neptune

Графові бази даних обіцяють обробляти набори даних із високим зв'язком краще, ніж їх реляційні еквіваленти. У таких наборах даних релевантна інформація зазвичай зберігається у зв'язках між об'єктами. Для тестування Neptune ми використали дивовижний проект із відкритими даними MusicBrainz. MusicBrainz збирає будь-які мислимі метадані про музику, наприклад, інформацію про виконавців, пісні, випуски альбомів чи концерти, а також про те, з ким співпрацював артист, який створив пісню, або коли в якій країні був випущений альбом. MusicBrainz можна як величезну мережу сутностей, які однак пов'язані з музичної індустрією.

Набір даних MusicBrainz надається як дампа CSV реляційної бази даних. Усього дамп містить близько 93 мільйонів рядків у 157 таблицях. У той час як деякі з цих таблиць містять основні дані, такі як виконавці, події, записи, релізи або треки, інші таблиці зв'язків — зберігають відносини між виконавцями та записами, іншими виконавцями чи релізами тощо… Вони демонструють графову структуру набору даних. При перетворенні набору даних на RDF-трійки ми отримали приблизно 500 мільйонів екземплярів.

На основі досвіду та вражень від партнерів проекту, з якими ми працюємо, ми представляємо сеттинг, в якій ця база знань використовується для отримання нової інформації. Крім того, ми припускаємо, що вона регулярно оновлюватиметься, наприклад, шляхом додавання нових релізів або оновлення учасників груп.

Налаштування

Як і очікувалося, установка Amazon Neptune проста. Вона досить докладно задокументована. Ви можете запустити графову базу даних лише за кілька кліків. Тим не менш, коли справа доходить до більш детальної конфігурації, потрібну інформацію знайти важко. Отже, хочемо вказати однією параметр конфігурації.

Перші враження від Amazon Neptune
Скриншот конфігурації для груп параметрів

Amazon стверджує, що Neptune фокусується на транзакційних робочих навантаженнях із низькою затримкою, тому за умовчанням час очікування запиту становить 120 секунд. Ми, проте, протестували безліч аналітичних юзкейсів, у яких регулярно досягали цього ліміту. Цей час очікування можна змінити, створивши нову групу параметрів для Neptune та встановивши для neptune_query_timeout відповідне обмеження.

завантаження даних

Нижче ми докладно обговоримо, як ми завантажували дані MusicBrainz у Neptune.

Відносини у трійки

По-перше, ми перетворили дані MusicBrainz на RDF-трійки. Тому для кожної таблиці ми визначили шаблон, який визначає, як кожен стовпець представлений у трійці. У цьому прикладі кожен рядок таблиці виконавців зіставлено з дванадцятьма RDF-трійками.

<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/gid> "${gid}"^^<http://www.w3.org/2001/XMLSchema#string> .
 
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/name> "${name}"^^<http://www.w3.org/2001/XMLSchema#string> .
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/sort-name> "${sort_name}"^^<http://www.w3.org/2001/XMLSchema#string> .
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/begin-date> "${begin_date_year}-${begin_date_month}-${begin_date_day}"^^xsd:<http://www.w3.org/2001/XMLSchema#date> .
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/end-date> "${end_date_year}-${end_date_month}-${end_date_day}"^^xsd:<http://www.w3.org/2001/XMLSchema#date> .
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/type> <http://musicbrainz.foo/artist-type/${type}> .
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/area> <http://musicbrainz.foo/area/${area}> .
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/gender> <http://musicbrainz.foo/gender/${gender}> .
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/comment> "${comment}"^^<http://www.w3.org/2001/XMLSchema#string> .
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/edits-pending> "${edits_pending}"^^<http://www.w3.org/2001/XMLSchema#int> .
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/last-updated> "${last_updated}"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
 
<http://musicbrainz.foo/artist/${id}> <http://musicbrainz.foo/ended> "${ended}"^^<http://www.w3.org/2001/XMLSchema#boolean> .

Масове завантаження

Запропонований спосіб завантаження великих обсягів даних у Neptune - це процес масового завантаження через S3. Після завантаження файлів трійок на S3 ви починаєте завантаження за допомогою POST-запиту. У нашому випадку це зайняло близько 24 години для 500 мільйонів трійок. Ми очікували, що буде швидшим.

curl -X POST -H 'Content-Type: application/json' http://your-neptune-cluster:8182/loader -d '{
 
 
 "source" : "s3://your-s3-bucket",
 
 "format" : "ntriples",
 
 "iamRoleArn" : "arn:aws:iam::your-iam-user:role/NeptuneLoadFromS3",
 
 "region" : "eu-west-1",
 
 "failOnError" : "FALSE"
 
}'

Щоб уникнути цього тривалого процесу щоразу, коли ми запускаємо Neptune, ми вирішили відновлювати інстанс із снапшота, в якому ці трійки вже завантажені. Запуск зі снапшота відбувається значно швидше, але все ще займає близько години, доки Neptune не стане доступним для запитів.

При початковому завантаженні трійок у Neptune ми зіткнулися з різними помилками.

{
 
 
 "errorCode" : "PARSING_ERROR",
 
 "errorMessage" : "Content after '.' is not allowed",
 
 "fileName" : [...],
 
 "recordNum" : 25
 
}

Деякі були помилками парсингу, як показано вище. На сьогоднішній день ми досі не з'ясували, що саме пішло негаразд у цей момент. Трохи більше деталей допомогли б тут. Ця помилка сталася приблизно для 1% вставлених трійок. Але, щодо тестування Neptune, ми прийняли той факт, що ми працюємо тільки з 99% інформації з MusicBrainz.

Навіть якщо це не є складним для людей, знайомих з SPARQL, майте на увазі, що RDF-трійки повинні бути анотовані з явними типами даних, що знову-таки може викликати помилки.

Потокове завантаження

Як згадувалося вище, ми не хочемо використовувати Neptune в якості статичного сховища даних, а скоріше як гнучку базу знань, що розвивається. Тому нам потрібно було знайти способи введення нових трійок при зміні бази знань, наприклад, коли публікується новий альбом або ми хочемо матеріалізувати похідні знання.

Neptune підтримує оператори введення через SPARQL-запити як з необробленими даними, так і на основі вибірок. Нижче ми обговоримо обидва підходи.

Однією з наших цілей було вводити дані у потоковому режимі. Розглянемо випуск альбому у новій країні. З точки зору MusicBrainz це означає, що для випуску, який включає альбоми, сингли, EP і т. д., новий запис додається в таблицю release-country. У RDF ми зіставляємо цю інформацію із двома новими трійками.

INSERT DATA { <http://musicbrainz.foo/release-country/737041> <http://musicbrainz.foo/release> <http://musicbrainz.foo/release/435759> };INSERT DATA { <http://musicbrainz.foo/release-country/737041> <http://musicbrainz.foo/date-year> "2018"^^<http://www.w3.org/2001/XMLSchema#int> };

Іншою метою було здобути нові знання з графа. Припустимо, ми хочемо одержати кількість релізів, які кожен артист опублікував у своїй кар'єрі. Такий запит досить складний і займає більше 20 хвилин у Neptune, тому нам потрібно матеріалізувати результат, щоб повторно використати ці нові знання у якомусь іншому запиті. Тому ми додаємо трійки з цією інформацією назад до графа, вводячи результат підзапиту.

INSERT {
 
 
  ?artist_credit <http://musicbrainz.foo/number-of-releases> ?number_of_releases
 
} WHERE {
 
  SELECT ?artist_credit (COUNT(*) as ?number_of_releases)
 
  WHERE {
 
     ?artist_credit <http://musicbrainz.foo/rdftype> <http://musicbrainz.foo/artist-credit> .
 
     ?release_group <http://musicbrainz.foo/artist-credit> ?artist_credit .
 
     ?release_group <http://musicbrainz.foo/rdftype> <http://musicbrainz.foo/release-group> .
 
     ?release_group <http://musicbrainz.foo/name> ?release_group_name .
 
  }
 
  GROUP BY ?artist_credit
 
}

Додавання одиночних трійок до графа займає кілька мілісекунд, у той час як час виконання для вставки результату підзапиту залежить від часу виконання самого підзапиту.

Незважаючи на те, що ми не використовували це часто, Neptune також дозволяє видаляти трійки на основі вибірок або явних даних, які можна використовувати для оновлення інформації.

SPARQL-запити

Представляючи попередній вибір, який повертає кількість релізів для кожного виконавця, ми вже ввели перший тип запиту, на який ми хочемо відповісти, використовуючи Neptune. Побудувати запит у Neptune нескладно — надішліть POST-запит у SPARQL-ендпоінт, як показано нижче:

curl -X POST --data-binary 'query=SELECT ?artist ?p ?o where {?artist <http://musicbrainz.foo/name> "Elton John" . ?artist ?p ?o . }' http://your-neptune-cluster:8182/sparql

Крім того, ми реалізували запит, який повертає профіль виконавців, що містить інформацію про їх ім'я, вік або країну походження. Майте на увазі, що виконавцями можуть бути люди, групи чи оркестри. Крім того, ми доповнюємо дані інформацією про кількість релізів, випущених артистами протягом року. Для сольних виконавців ми також додаємо інформацію про групи, в яких ці артисти брав участь у кожному році.

SELECT
 
 
 ?artist_name ?year
 
 ?releases_in_year ?releases_up_year
 
 ?artist_type_name ?releases
 
 ?artist_gender ?artist_country_name
 
 ?artist_begin_date ?bands
 
 ?bands_in_year
 
WHERE {
 
 # Bands for each artist
 
 {
 
   SELECT
 
     ?year
 
     ?first_artist
 
     (group_concat(DISTINCT ?second_artist_name;separator=",") as ?bands)
 
     (COUNT(DISTINCT ?second_artist_name) AS ?bands_in_year)     
 
   WHERE {
 
     VALUES ?year {
 
       1960 1961 1962 1963 1964 1965 1966 1967 1968 1969
 
       1970 1971 1972 1973 1974 1975 1976 1977 1978 1979
 
       1980 1981 1982 1983 1984 1985 1986 1987 1988 1989
 
       1990 1991 1992 1993 1994 1995 1996 1997 1998 1999
 
       2000 2001 2002 2003 2004 2005 2006 2007 2008 2009
 
       2010 2011 2012 2013 2014 2015 2016 2017 2018
 
     }   
 
     ?first_artist <http://musicbrainz.foo/name> "Elton John" .
 
     ?first_artist <http://musicbrainz.foo/rdftype> <http://musicbrainz.foo/artist> .
 
     ?first_artist <http://musicbrainz.foo/type> ?first_artist_type .
 
     ?first_artist <http://musicbrainz.foo/name> ?first_artist_name .
 

 
 
     ?second_artist <http://musicbrainz.foo/rdftype> <http://musicbrainz.foo/artist> .
 
     ?second_artist <http://musicbrainz.foo/type> ?second_artist_type .
 
     ?second_artist <http://musicbrainz.foo/name> ?second_artist_name .
 
     optional { ?second_artist <http://musicbrainz.foo/begin-date-year> ?second_artist_begin_date_year . }
 
     optional { ?second_artist <http://musicbrainz.foo/end-date-year> ?second_artist_end_date_year . }
 

 
 
     ?l_artist_artist <http://musicbrainz.foo/entity0> ?first_artist .
 
     ?l_artist_artist <http://musicbrainz.foo/entity1> ?second_artist .
 
     ?l_artist_artist <http://musicbrainz.foo/link> ?link .
 

 
 
     optional { ?link <http://musicbrainz.foo/begin-date-year> ?link_begin_date_year . }
 
     optional { ?link <http://musicbrainz.foo/end-date-year> ?link_end_date_year . }
 

 
 
     FILTER (!bound(?link_begin_date_year) || ?link_begin_date_year <= ?year)
 
     FILTER (!bound(?link_end_date_year) || ?link_end_date_year >= ?year)
 
     FILTER (!bound(?second_artist_begin_date_year) || ?second_artist_begin_date_year <= ?year)
 
     FILTER (!bound(?second_artist_end_date_year) || ?second_artist_end_date_year >= ?year)
 
     FILTER (?first_artist_type NOT IN (<http://musicbrainz.foo/artist-type/2>, <http://musicbrainz.foo/artist-type/5>, <http://musicbrainz.foo/artist-type/6>))
 
     FILTER (?second_artist_type IN (<http://musicbrainz.foo/artist-type/2>, <http://musicbrainz.foo/artist-type/5>, <http://musicbrainz.foo/artist-type/6>))
 
   }
 
   GROUP BY ?first_artist ?year
 
 }
 
 # Releases up to a year
 
 {
 
   SELECT
 
     ?artist
 
     ?year
 
     (group_concat(DISTINCT ?release_name;separator=",") as ?releases)
 
     (COUNT(*) as ?releases_up_year)
 
   WHERE {
 
     VALUES ?year {
 
       1960 1961 1962 1963 1964 1965 1966 1967 1968 1969
 
       1970 1971 1972 1973 1974 1975 1976 1977 1978 1979
 
       1980 1981 1982 1983 1984 1985 1986 1987 1988 1989
 
       1990 1991 1992 1993 1994 1995 1996 1997 1998 1999
 
       2000 2001 2002 2003 2004 2005 2006 2007 2008 2009
 
       2010 2011 2012 2013 2014 2015 2016 2017 2018 
 
     }
 

 
 
     ?artist <http://musicbrainz.foo/name> "Elton John" .
 

 
 
     ?artist_credit_name <http://musicbrainz.foo/artist-credit> ?artist_credit .
 
     ?artist_credit_name <http://musicbrainz.foo/rdftype> <http://musicbrainz.foo/artist-credit-name> .
 
     ?artist_credit_name <http://musicbrainz.foo/artist> ?artist .
 
     ?artist_credit <http://musicbrainz.foo/rdftype> <http://musicbrainz.foo/artist-credit> .
 

 
 
     ?release_group <http://musicbrainz.foo/artist-credit> ?artist_credit .
 
     ?release_group <http://musicbrainz.foo/rdftype> <http://musicbrainz.foo/release-group> .
 
     ?release_group <http://musicbrainz.foo/name> ?release_group_name .
 
     ?release <http://musicbrainz.foo/release-group> ?release_group .
 
     ?release <http://musicbrainz.foo/name> ?release_name .
 
     ?release_country <http://musicbrainz.foo/release> ?release .
 
     ?release_country <http://musicbrainz.foo/date-year> ?release_country_year .
 

 
 
     FILTER (?release_country_year <= ?year)
 
   }
 
   GROUP BY ?artist ?year
 
 }
 
 # Releases in a year
 
 {
 
   SELECT ?artist ?year (COUNT(*) as ?releases_in_year)
 
   WHERE {
 
     VALUES ?year {
 
       1960 1961 1962 1963 1964 1965 1966 1967 1968 1969
 
       1970 1971 1972 1973 1974 1975 1976 1977 1978 1979
 
       1980 1981 1982 1983 1984 1985 1986 1987 1988 1989
 
       1990 1991 1992 1993 1994 1995 1996 1997 1998 1999
 
       2000 2001 2002 2003 2004 2005 2006 2007 2008 2009
 
       2010 2011 2012 2013 2014 2015 2016 2017 2018 
 
     }
 

 
 
     ?artist <http://musicbrainz.foo/name> "Elton John" .
 

 
 
     ?artist_credit_name <http://musicbrainz.foo/artist-credit> ?artist_credit .
 
     ?artist_credit_name <http://musicbrainz.foo/rdftype> <http://musicbrainz.foo/artist-credit-name> .
 
     ?artist_credit_name <http://musicbrainz.foo/artist> ?artist .
 
     ?artist_credit <http://musicbrainz.foo/rdftype> <http://musicbrainz.foo/artist-credit> .
 

 
 
     ?release_group <http://musicbrainz.foo/artist-credit> ?artist_credit .
 
     ?release_group <http://musicbrainz.foo/rdftype> <http://musicbrainz.foo/release-group> .
 
     ?release_group <http://musicbrainz.foo/name> ?release_group_name .
 
     ?release <http://musicbrainz.foo/release-group> ?release_group .
 
     ?release_country <http://musicbrainz.foo/release> ?release .
 
     ?release_country <http://musicbrainz.foo/date-year> ?release_country_year .
 

 
 
     FILTER (?release_country_year = ?year)
 
   }
 
   GROUP BY ?artist ?year
 
 }
 
 # Master data
 
 {
 
   SELECT DISTINCT ?artist ?artist_name ?artist_gender ?artist_begin_date ?artist_country_name
 
   WHERE {
 
     ?artist <http://musicbrainz.foo/name> ?artist_name .
 
     ?artist <http://musicbrainz.foo/name> "Elton John" .
 
     ?artist <http://musicbrainz.foo/gender> ?artist_gender_id .
 
     ?artist_gender_id <http://musicbrainz.foo/name> ?artist_gender .
 
     ?artist <http://musicbrainz.foo/area> ?birth_area .
 
     ?artist <http://musicbrainz.foo/begin-date-year> ?artist_begin_date.
 
     ?birth_area <http://musicbrainz.foo/name> ?artist_country_name .
 

 
 
     FILTER(datatype(?artist_begin_date) = xsd:int)
 
   }

Через складність такого запиту ми могли виконувати лише точкові запити для конкретного виконавця, наприклад, Елтона Джона, але не для всіх артистів. Neptune, схоже, не оптимізує такий запит, опускаючи фільтри у вибірки. Тому кожну вибірку доводиться фільтрувати вручну на ім'я виконавця.

Neptune має як погодинну, і оплату за кожну операцію вводу-вывода. Для нашого тестування ми використали найменшу інстанс Neptune, яка коштує $0,384/год. У разі запиту вище, який обчислює профіль для одного виконавця, Amazon стягує з нас кілька десятків тисяч операцій вводу-виводу, що передбачає витрати у розмірі $0.02.

Висновок

По-перше, Amazon Neptune тримає більшість своїх обіцянок. Як керований сервіс, це графова база даних, яка надзвичайно проста в установці і може бути запущена без великої кількості налаштувань. Ось наші п'ять ключових висновків:

  • Масове завантаження просте, але повільне. Але вона може ускладнитися через повідомлення про помилки, які не дуже допомагають
  • Поточне завантаження підтримує все, що ми очікували, і було досить швидким
  • Запити прості, але недостатньо інтерактивні для виконання аналітичних запитів
  • SPARQL-запити мають бути оптимізовані вручну
  • Платежі Amazon важко оцінити, оскільки складно оцінити обсяг даних, відсканованих за SPARQL-запитом.

На цьому все. Записуйтесь на безкоштовний вебінар на тему «Балансування навантаження».


Джерело: habr.com

Додати коментар або відгук