talis/tripod-php

用于管理Mongo数据库中RDF数据的库


README

<CircleCI>

用于管理存储在MongoDB中的RDF数据的对象图映射器。另请参阅tripod-node

功能

  • 在合理硬件上,对大于100M个三元的数据集,单次查询响应时间高性能,小于1毫秒
  • 不支持SPARQL查询,而是提供两种风格的SPARQL-like DESCRIBE/SELECT操作
    • 无需图遍历的即席查询
    • 通过Composites,这些是固定规范的物化文档,支持图遍历。目前有三种类型的复合体
      • 视图(DESCRIBE) - 这些是多主体图,可以在一个自包含的文档中检索
      • 表格(SELECT) - 表格数据集
      • 搜索 - 也适用于表格,更适合搜索用例
  • 在写入时以速度与一致性进行交易 - 视图和表格可以立即更新或在后台进行最终一致性更新
  • 在谓词上定义索引以加快查询速度
  • 分页表格数据,并计算多值单元格(太好了!)
  • (非常)简单的数据集搜索。在公共发布之前,我们支持ElasticSearch搜索提供程序,但这超出了我们的需求,因此已被移除,因为我们没有意愿维护它。
  • 通用的瑞士军刀图形对象ExtendedGraph,您的应用程序模型可以包装或扩展
  • 支持事务,用于跨多个CBD的更新
  • 命名图支持
  • 使用计数器和定时器测量系统(如graphite/carbon)的仪表

快速入门

require_once("tripod.inc.php");

// Queue worker must register these event listeners
Resque_Event::listen('beforePerform', [\Tripod\Mongo\Jobs\JobBase::class, 'beforePerform']);
Resque_Event::listen('onFailure', [\Tripod\Mongo\Jobs\JobBase::class, 'onFailure']);

\Tripod\Config::setConfig($conf); // set the config, usually read in as JSON from a file

$tripod = new Driver(
  "CBD_users", // pod (read: MongoDB collection) we're working with
  "myapp" // store (read: MongoDB database)  we're working with
);

// describe
$graph = $tripod->describe("http://example.com/user/1");
echo $graph->get_first_literal("http://example.com/user/1","http://xmlns.com/foaf/0.1/name"); 

// select
$data = $tripod->select(
  array("rdf:type.u"=>"http://xmlns.com/foaf/0.1/Person"),
  array("foaf:name"=>true);
);
if ($data['head']['count']>0) {
  foreach($data['results'] as $result) {
    echo $result['foaf:name'];
  }
}

// an expensive pre-defined graph traversal query
$graph = $tripod->getViewForResource("http://example.com/users","v_users");
$allUsers = $graph->get_subjects_of_type("http://xmlns.com/foaf/0.1/Person");

// save
$newGraph = new \Tripod\ExtendedGraph();
$newGraph->add_literal_value("http://example.com/user/2","http://xmlns.com/foaf/0.1/name","John Smith");
$tripod->saveChanges(
  new \Tripod\ExtendedGraph(), // the before state, here there was no before (new data)
  $newGraph // the desired after state
);

// save, but background all the expensive view/table/search generation
$tripod = new \Tripod\Mongo\Driver("CBD_users",  "usersdb", array(
    'async' = array(OP_VIEWS=>true,OP_TABLES=>true,OP_SEARCH=>true) // async opt says what to do later via a queue rather than as part of the save
  )
);
$tripod->saveChanges(
  new \Tripod\ExtendedGraph(), // the before state, here there was no before (new data)
  $newGraph // the desired after state
);

要求

PHP >= 5.5

Mongo 3.2.x和更高版本。

MongoDB PHP驱动程序版本。http://mongodb.github.io/mongo-php-driver/#installation

配置看起来像什么?

阅读完整文档

在您可以与tripod做任何事情之前,您需要通过Config::setConfig()方法初始化配置。这需要一个关联数组,这通常可以从JSON字符串解码。以下是一个示例

{
    "namespaces" : {
        "rdf":"http://www.w3.org/1999/02/22-rdf-syntax-ns#",
        "foaf":"http://xmlns.com/foaf/0.1/",
        "exampleapp":"http://example.com/properties/"
    },
    "defaultContext":"http://talisaspire.com/",
    "data_sources" : {
        "cluster1": {
            "type": "mongo",
            "connection": "mongodb:\/\/localhost",
            "replicaSet": ""
        },
        "cluster2": {
            "type": "mongo",
            "connection": "mongodb:\/\/othermongo.example.com",
            "replicaSet": ""
        }
    },
    "stores" : {
        "myapp" : {
            "data_source" : "cluster1",
            "pods" : {
                "CBD_users" : {
                    "cardinality" : {
                        "foaf:name" : 1
                    },
                    "indexes" : {
                        "names": {
                            "foaf:name.l":1
                        }
                    }
                }
            },
            "view_specifications" : [
                {
                    "_id": "v_users",
                    "from":"CBD_users",
                    "type": "exampleapp:AllUsers",
                    "include": ["rdf:type"],
                    "joins": {
                        "exampleapp:hasUser": {
                            "include": ["foaf:name","rdf:type"]
                            "joins": {
                                "foaf:knows" : {
                                "include": ["foaf:name","rdf:type"]
                                }
                            }
                        }
                    }
                }
            ],
            "table_specifications" : [
                {
                    "_id": "t_users",
                    "type":"foaf:Person",
                    "from":"CBD_user",
                    "to_data_source" : "cluster2",
                    "ensureIndexes":[
                        {
                            "value.name": 1
                        }
                    ],
                    "fields": [
                        {
                            "fieldName": "type",
                            "predicates": ["rdf:type"]
                        },
                        {
                            "fieldName": "name",
                            "predicates": ["foaf:name"]
                        },
                        {
                            "fieldName": "knows",
                            "predicates": ["foaf:knows"]
                        }
                    ],
                    "joins" : {
                        "foaf:knows" : {
                            "fields": [
                                {
                                    "fieldName":"knows_name",
                                    "predicates":["foaf:name"]
                                }
                            ]
                        }
                    }
                }
            ],
            "search_config":{
                "search_provider":"MongoSearchProvider",
                "search_specifications":[
                    {
                        "_id":"i_users",
                        "type":["foaf:Person"],
                        "from":"CBD_user",
                        "to_data_source" : "cluster2",
                        "filter":[
                            {
                                "condition":{
                                    "foaf:name.l":{
                                        "$exists":true
                                    }
                                }
                            }
                        ],
                        "indices":[
                            {
                                "fieldName": "name",
                                "predicates": ["foaf:name", "foaf:firstName","foaf:surname"]
                            }
                        ],
                        "fields":[
                            {
                                "fieldName":"result.name",
                                "predicates":["foaf:name"],
                                "limit" : 1
                            }
                        ]
                    }
                ]
            }
        }
    },
    "transaction_log" : {
        "database" : "testing",
        "collection" : "transaction_log",
        "data_source" : "cluster2"
    }
}

内部数据模型

数据存储在Mongo集合中,每个文档一个CBD。通常,您会选择将特定对象类型的所有数据放在一个以CBD_为前缀的单独集合中,例如CBD_users,尽管这更多是一种约定而不是要求。

这些CBD集合被认为是应用程序的读/写,并受tlog中记录的事务影响(见下文)。

CBD可能看起来像这样

{
	"_id" : {
		"r" : "http://example.com/user/2",
		"c" : "http://example.com/defaultContext"
	},
	"siocAccess:Role" : {
		"l" : "an undergraduate"
	},
	"siocAccess:has_status" : {
		"l" : "public"
	},
	"spec:email" : {
		"l" : "me@example.com"
	},
	"rdf:type" : [
		{
			"u" : "foaf:Person"
		},
		{
			"u" : "sioc:User"
		}
	],
	"foaf:name" : {
		"l" : "John Smith"
	}
}

简要指南

  • 以下划线为前缀的是特殊字段,由tripod独家管理。这里,_id是主体(资源属性r)和命名图(上下文属性c)的复合,对于此CBD。
  • 谓词是属性,总是具有命名空间,例如foaf:name
  • 谓词字段的值要么是一个对象,要么是一组对象(对于多值)。该对象恰好有一个属性,即u(URI,这些是RDF资源对象值)或l(文本,这些是RDF文本对象值)

事务

MongoDB仅在文档级别上是原子的。Tripod数据集每个文档存储一个CBD。因此,对数据集的图更新可能影响1..n个文档。

三脚架维护一个更新事务日志(tlog),以便在多文档写入的情况下进行回滚。将此运行在与主数据分离的集群中是可能的(也是推荐的)。对于灾难恢复,您可以使用tlog在已知良好的备份上重新播放事务。

在生产中,我们在EC2上运行一个小型2号集群,该集群存储最多7天的tlog,我们定期将其修剪并刷新到S3。

你用它建了些什么?

支撑Talis Aspire的大多数数据集,这是一个服务于全球50多所大学的1万名学生的企业SaaS课程管理系统,通过三脚架库使用存储在MongoDB中的图数据。

当我们需要迁移出我们自己的内部专有三元组存储(偶然地,它是基于早期版本的Apache JENA)时,我们构建了三脚架。

我们已经将它用于生产2年。我们的数据量超过70个数据库的5亿多个三元组,在中等规模的3节点集群(2个数据节点)上,使用戴尔R710中端服务器、12核心96Gb RAM、RAID-10非SSD磁盘阵列、EC2中的m1.small仲裁器。

我为什么要用这个?

  • 您的查询量很高,但您的应用程序所需的独立图遍历查询数量很少(您可能会惊讶应用程序可能需要的不同查询有多少)
  • 您的读写平衡大约为>10:1
  • 您的数据集可能每个不超过100MT(我们没有测试超过这个范围)

什么时候不应该使用这个?

  • 您不知道您数据的形式
  • 您需要运行即兴的图遍历查询
  • 您需要SPARQL支持

一些其他限制

  • 即兴的复杂查询是不可能的,因为物化视图和表是预指定的。更改这些规范需要重新生成所有物化文档。
  • 严重依赖于命名空间。在数据库中,谓词总是有命名空间的,实际上,在配置中知道并指定所有命名空间是必需的,因此处理任意未知数据不是强项。
  • 写入成本很高,因为它们触发视图和表的无效化。视图和表的规范越多,写入就越慢(或者如果后台生成,视图和表中的数据一致性变得越慢)

即将推出(也就是一个粗略路线图)

  • 更多文档
  • 节点版本 - 可能最初是只读子集 它在这里:这里
  • 改进背景队列,目前这是一个长时间运行的PHP脚本,从存储在MongoDB中的更新队列中工作。原本只打算用于PoC,但两年后它还在这里!
  • tlog持久化的替代持久化技术。内存映射数据库对于快速周转的数据集来说不好,因为即使数据集被修剪,数据文件也会增长。为tlog持久化实现一个更专业的追加数据库或甚至是RDBMS。正在进行中:[问题](https://github.com/talis/tripod-php/issues/7),[分支](https://github.com/talis/tripod-php/tree/pgsql-tlog),[PR](https://github.com/talis/tripod-php/pull/6)
  • ExtendedGraph的性能改进。该对象的内部结构是Talis自己专有三元存储时代的遗留物,以及它过去如何返回数据。我们通过使用MongoGraph对象来传输数据来启动它。这严重依赖于正则表达式,我们知道从我们在现场收集的数据中,这是一个可以减少CPU周期和内存使用的优化点。另一方面,能够采摘这样的有针对性的低垂果实是一件好事。
  • 版本化配置。这将允许视图和表在它们的规范更新时自我修复。目前,当规范发生变化时,您必须删除并重新生成所有内容。
  • 发布到Apache Storm的更新我们实现了事件钩子,这将允许您实现此功能。
  • 将配置推送到数据库内部,并对其进行版本控制。这意味着多个应用程序可以使用相同的数据库和相同的配置进行工作。
  • 能够指定类似于Doctrine等特别注解的实体。这将是一种在不担心ExtendedGraph的复杂性下实现模型的方式。大部分配置都可以从实体类本身的注解中推导出来。我们现在正在开发这个alpha版本,请参阅此问题。它可能是一个依赖于Tripod而不是合并到核心的附加库。
  • 自从四年前开始这个项目以来,我们已经学到了很多。现在库在功能上已经稳定,我们可以重新设计对象结构和职责。这是我们开源的一个原因——希望我们将被迫进行长期拖延的整理。

演示文稿

我们在MongoUK 2012上展示了更早版本的演示。自那时以来,我们已经解决了以下待办事项

  • 开始node-js端口的开发
  • 消除了对map-reduce的依赖
  • 添加了命名图支持(上下文)
  • 优化了数据模型
  • 开源了代码

致谢

我们使用了出色的ARC,Tripod的一些元素基于Moriarty库,这是Talis早期工作的成果,为Talis自己的专有云三元存储提供了PHP库(现已不再运营)。

由Talis的kiyanwangrobotrobot提出。