Pierwsze wrażenia z Amazon Neptune

Pozdrawiam mieszkańców Chabrowska. W oczekiwaniu na rozpoczęcie kursu „AWS dla programistów” Przygotowaliśmy tłumaczenie ciekawych materiałów.

Pierwsze wrażenia z Amazon Neptune

W wielu przypadkach użycia, które lubimy bakdataJak widzimy na stronach naszych klientów, istotne informacje kryją się w powiązaniach pomiędzy podmiotami, np. analizując relacje pomiędzy użytkownikami, zależności pomiędzy elementami, czy też połączenia pomiędzy sensorami. Takie przypadki użycia są zwykle modelowane na wykresie. Na początku tego roku Amazon udostępnił swoją nową grafową bazę danych, Neptun. W tym poście chcemy podzielić się naszymi pierwszymi pomysłami, dobrymi praktykami i tym, co z czasem można ulepszyć.

Dlaczego potrzebowaliśmy Amazon Neptune

Grafowe bazy danych obiecują lepiej obsługiwać wysoce powiązane zbiory danych niż ich relacyjne odpowiedniki. W takich zbiorach danych odpowiednie informacje są zwykle przechowywane w relacjach między obiektami. Do przetestowania Neptuna wykorzystaliśmy niesamowity projekt otwartych danych MusicBrainz. MusicBrainz gromadzi wszelkiego rodzaju metadane muzyczne, jakie można sobie wyobrazić, takie jak informacje o artystach, utworach, wydaniach albumów lub koncertach, a także o tym, z kim współpracował twórca utworu lub kiedy album został wydany w jakim kraju. MusicBrainz można postrzegać jako ogromną sieć podmiotów powiązanych w jakiś sposób z przemysłem muzycznym.

Zbiór danych MusicBrainz jest dostarczany w postaci zrzutu CSV relacyjnej bazy danych. W sumie zrzut zawiera około 93 milionów wierszy w 157 tabelach. Chociaż niektóre z tych tabel zawierają podstawowe dane, takie jak artyści, wydarzenia, nagrania, wydania lub utwory, inne tabele linków — przechowują relacje między artystami i nagraniami, innymi artystami lub wydawnictwami itp. Pokazują strukturę graficzną zbioru danych. Konwertując zbiór danych na trójki RDF, uzyskaliśmy około 500 milionów instancji.

Bazując na doświadczeniach i wrażeniach partnerów projektu, z którymi współpracujemy, przedstawiamy scenerię, w której ta baza wiedzy wykorzystywana jest do pozyskiwania nowych informacji. Ponadto oczekujemy, że będzie on regularnie aktualizowany, na przykład poprzez dodawanie nowych wersji lub aktualizację członków grupy.

regulacja

Zgodnie z oczekiwaniami instalacja Amazon Neptune jest prosta. Jest dość szczegółowa udokumentowane. Bazę wykresów możesz uruchomić kilkoma kliknięciami. Jeśli jednak chodzi o bardziej szczegółową konfigurację, niezbędne informacje trudne do znalezienia. Dlatego chcemy wskazać na jeden parametr konfiguracyjny.

Pierwsze wrażenia z Amazon Neptune
Zrzut ekranu konfiguracji dla grup parametrów

Amazon twierdzi, że Neptun koncentruje się na obciążeniach transakcyjnych o małych opóźnieniach, dlatego domyślny limit czasu żądania wynosi 120 sekund. Przetestowaliśmy jednak wiele przypadków użycia analitycznego, w których regularnie osiągaliśmy ten limit. Ten limit czasu można zmienić, tworząc nową grupę parametrów dla Neptuna i ustawiając neptune_query_timeout odpowiednie ograniczenie.

Ładowanie danych

Poniżej omówimy szczegółowo, w jaki sposób załadowaliśmy dane MusicBrainz do Neptuna.

Relacje w trójkę

Najpierw przekonwertowaliśmy dane MusicBrainz na trójki RDF. Dlatego dla każdej tabeli zdefiniowaliśmy szablon, który określa, w jaki sposób każda kolumna jest reprezentowana w trójce. W tym przykładzie każdy wiersz tabeli wykonawców jest odwzorowany na dwanaście trójek 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> .

Przesyłanie zbiorcze

Sugerowanym sposobem ładowania dużych ilości danych do Neptuna jest proces zbiorczego przesyłania za pośrednictwem S3. Po przesłaniu plików triples do S3 rozpoczynasz przesyłanie za pomocą żądania POST. W naszym przypadku 24 milionów trojaczków zajęło około 500 godzin. Spodziewaliśmy się, że będzie szybciej.

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"
 
}'

Aby uniknąć tego długiego procesu za każdym razem, gdy uruchamiamy Neptuna, zdecydowaliśmy się przywrócić instancję z migawki, w której te trojaczki były już załadowane. Uruchamianie z migawki jest znacznie szybsze, ale nadal zajmuje około godziny, zanim Neptun będzie dostępny dla żądań.

Podczas początkowego ładowania trojaczków do Neptuna napotkaliśmy różne błędy.

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

Niektóre z nich zawierały błędy analizy, jak pokazano powyżej. Do chwili obecnej nie udało nam się ustalić, co dokładnie poszło nie tak. Trochę więcej szczegółów na pewno by się tutaj przydało. Ten błąd wystąpił dla około 1% wstawionych trójek. Ale jeśli chodzi o testowanie Neptuna, zaakceptowaliśmy fakt, że pracujemy tylko z 99% informacji z MusicBrainz.

Chociaż jest to łatwe dla osób znających SPARQL, należy pamiętać, że trójki RDF muszą być opatrzone adnotacjami jawnych typów danych, co z kolei może powodować błędy.

Pobieranie strumieniowe

Jak wspomniano powyżej, nie chcemy używać Neptuna jako statycznego magazynu danych, ale raczej jako elastycznej i rozwijającej się bazy wiedzy. Musieliśmy zatem znaleźć sposoby na wprowadzenie nowych trójek, gdy zmienia się baza wiedzy, na przykład po opublikowaniu nowego albumu lub gdy chcemy zmaterializować uzyskaną wiedzę.

Neptun obsługuje operatory wejściowe poprzez zapytania SPARQL, zarówno surowe, jak i oparte na próbkach. Poniżej omówimy oba podejścia.

Jednym z naszych celów było wprowadzanie danych w sposób strumieniowy. Rozważ wydanie albumu w nowym kraju. Z punktu widzenia MusicBrainz oznacza to, że w przypadku wydania zawierającego albumy, single, EPki itp. do tabeli dodawany jest nowy wpis kraj wydania. W RDF łączymy tę informację z dwiema nowymi trójkami.

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> };

Kolejnym celem było uzyskanie nowej wiedzy z wykresu. Załóżmy, że chcemy uzyskać liczbę wydawnictw opublikowanych przez każdego artystę w swojej karierze. Takie zapytanie jest dość złożone i zajmuje na Neptunie ponad 20 minut, dlatego musimy zmaterializować wynik, aby ponownie wykorzystać tę nową wiedzę w jakimś innym zapytaniu. Dodajemy więc trójki z tą informacją z powrotem do wykresu, wpisując wynik podzapytania.

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
 
}

Dodanie pojedynczych trójek do wykresu zajmuje kilka milisekund, natomiast czas wykonania wstawienia wyniku podzapytania zależy od czasu wykonania samego podzapytania.

Chociaż nie używaliśmy go często, Neptun pozwala również na usuwanie trójek na podstawie próbek lub jawnych danych, które można wykorzystać do aktualizacji informacji.

Zapytania SPARQL

Wprowadzając poprzednią podpróbkę, która zwraca liczbę wydań każdego artysty, wprowadziliśmy już pierwszy typ zapytania, na które chcemy odpowiedzieć za pomocą Neptuna. Budowanie zapytania w Neptunie jest łatwe — wyślij żądanie POST do punktu końcowego SPARQL, jak pokazano poniżej:

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

Dodatkowo zaimplementowaliśmy zapytanie, które zwraca profil artysty zawierający informacje o jego imieniu, wieku czy kraju pochodzenia. Należy pamiętać, że wykonawcami mogą być pojedyncze osoby, zespoły lub orkiestry. Dodatkowo uzupełniamy te dane informacją o liczbie wydawnictw wydanych przez artystów w ciągu roku. W przypadku artystów solowych dodajemy także informacje o zespołach, w których dany artysta brał udział w każdym roku.

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)
 
   }

Ze względu na złożoność takiego zapytania mogliśmy wykonać zapytania punktowe jedynie dla konkretnego artysty, np. Eltona Johna, ale nie dla wszystkich artystów. Wydaje się, że Neptun nie optymalizuje takiego zapytania, upuszczając filtry do podselekcji. Dlatego każdy wybór musi zostać ręcznie przefiltrowany według nazwy artysty.

Neptun pobiera opłaty zarówno za godzinę, jak i za wejścia/wyjścia. Do naszych testów użyliśmy minimalnej instancji Neptune, która kosztuje 0,384 USD za godzinę. W przypadku powyższego zapytania, które oblicza profil dla pojedynczego pracownika, Amazon obciąża nas dziesiątkami tysięcy operacji we/wy, co oznacza koszt 0.02 USD.

Wniosek

Po pierwsze, Amazon Neptune dotrzymuje większości swoich obietnic. Jako usługa zarządzana jest to grafowa baza danych, którą można niezwykle łatwo zainstalować i uruchomić bez konieczności wykonywania wielu czynności konfiguracyjnych. Oto pięć naszych kluczowych ustaleń:

  • Przesyłanie zbiorcze jest łatwe, ale powolne. Ale może się to skomplikować w przypadku komunikatów o błędach, które nie są zbyt pomocne.
  • Pobieranie strumieniowe obsługuje wszystko, czego się spodziewaliśmy i było dość szybkie
  • Zapytania są proste, ale nie na tyle interaktywne, aby można było uruchamiać zapytania analityczne
  • Zapytania SPARQL należy optymalizować ręcznie
  • Płatności Amazon są trudne do oszacowania, ponieważ trudno oszacować ilość danych skanowanych przez zapytanie SPARQL.

To wszystko. Zapisać się do bezpłatne webinarium na temat „Load Balancing”.


Źródło: www.habr.com

Dodaj komentarz