Першыя ўражанні ад 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

Дадаць каментар