亚马逊海王星第一印象

敬礼,哈布罗夫斯克。预期AWS for Developers课程的开始,我们准备了一些有趣的材料的翻译。




bakdata等许多用户案例中,我们在客户的网站上看到的相关信息隐藏在实体之间的关系中,例如,在分析用户之间的关系,元素之间的依存关系或传感器之间的连接时。这种用例通常在图表上建模。今年早些时候,亚马逊发布了新的海王星图数据库。在这篇文章中,我们希望分享我们的第一个想法,良好做法以及随着时间的推移可以改进的内容。

我们为什么需要亚马逊海王星


图数据库有望比其关系等效数据库更好地处理高度连接的数据集。在这样的数据集中,相关信息通常存储在对象之间的关系中。为了测试海王星,我们使用了一个令人惊叹的开放数据MusicBrainz项目。 MusicBrainz收集有关音乐的任何可能的元数据,例如有关艺术家,歌曲,专辑发行或音乐会的信息,以及与该歌曲合作的艺术家与谁合作,或专辑在哪个国家发行。 MusicBrainz可以看作是一个庞大的实体网络,与音乐行业有某种联系。

MusicBrainz数据集作为CSV关系数据库的转储提供。总共,转储包含157个表中的约9,300万行。这些表中的某些包含主数据,例如艺术家,事件,记录,发行或曲目,而其他 - 链接表 -存储艺术家与记录,其他艺术家或发行之间的关系等...它们展示了集合的图形结构数据。将数据集转换为RDF三元组时,我们获得了约5亿份副本。

根据我们与之合作的项目合作伙伴的经验和印象,我们介绍了一种使用该知识库获取新信息的环境。此外,我们假设将定期进行更新,例如,通过添加新版本或更新组成员。

客制化


正如预期的那样,安装Amazon Neptune很容易。记载在一些细节上只需单击几下即可启动图形数据库。但是,当涉及到更详细的配置时,很难找到所需的信息因此,我们要指向一个配置参数。 Amazon


参数组的配置屏幕截图

声称,Neptune专注于低延迟事务性工作负载,因此默认超时为120秒。但是,我们测试了许多定期达到此限制的分析用例。您可以通过为海王星创建一个新的参数组并将其设置为来更改此超时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请求开始下载。在我们的案例中,花了3亿个三重花了大约24小时。我们期望它会更快。

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中加载三元组时,我们遇到了各种错误。

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

如上所示,其中一些解析错误。迄今为止,我们仍未弄清楚目前到底出了什么问题。多一点细节肯定会在这里有所帮助。大约1%的插入三元组发生此错误。但是对于测试海王星,我们接受了这样一个事实,即我们仅处理MusicBrainz提供的99%的信息。

即使熟悉SPARQL的人很容易,也要记住,必须使用显式数据类型对RDF三元组进行注释,这又会导致错误。

流下载


如上所述,我们不想将Neptune用作静态数据仓库,而是将其用作灵活且不断发展的知识库。因此,当改变知识库时,例如,当发行新专辑或当我们要实现衍生的知识时,我们需要找到引入新的三元组的方法。

Neptune通过SPARQL查询支持输入运算符,既可以使用原始数据,也可以基于样本。我们将在下面讨论这两种方法。

我们的目标之一是以流模式输入数据。考虑在新国家发行专辑。从MusicBrainz的角度来看,这意味着对于包含专辑,单曲,EP等等的发行版,新记录将添加到发行版国家/地区表中在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> };

另一个目标是从图表中获取新知识。假设我们想要获得每个艺术家在其职业生涯中发行的发行数量。这样的查询相当复杂,在Neptune中花费超过20分钟,因此我们需要具体化结果,以便在其他查询中重用此新知识。因此,我们将带有此信息的三元组添加回图形中,从而介绍了子查询的结果。

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中构建查询很容易-向SPARQL端点发送POST请求,如下所示:

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

由于此类请求的复杂性,我们只能对特定艺术家(例如Elton John)执行积分查询,而不能对所有艺术家进行积分查询。海王星似乎没有通过省略子样本中的过滤器来优化这种查询。因此,每个样本都必须按艺术家的名字手动过滤。

海王星既按小时收费,又为每个I / O操作付费。在我们的测试中,我们使用了最小的海王星实例,其成本为0.384美元/小时。在上述请求(为一位艺术家计算个人资料)的情况下,亚马逊将向我们收取数万美元的I / O费用,这意味着0.02美元。

结论


首先,亚马逊海王星信守诺言。作为一项托管服务,它是一个图形数据库,非常易于安装,无需进行大量设置即可启动。这是我们的五个主要发现:

  • 批量上传很简单但是很慢。但是由于错误消息的帮助不大,它可能会变得复杂
  • 流加载支持我们期望的一切,并且足够快
  • 查询很简单,但交互性不足以执行分析查询
  • 必须手动优化SPARQL查询
  • 亚马逊付款难以评估,因为很难估算SPARQL查询扫描的数据量。

就这样。注册有关“负载平衡”主题免费网络研讨会

All Articles