1.1. 大多数现实生活中的应用程序(如推荐引擎或个性化应用程序)所需的模型比社交网络示例的单顶点、单边数据模型复杂得多
1.2. 三种高级数据建模技术
1.2.1. 使用通用标签提高性能
1.2.2. 将属性移动到边,以简化遍历
1.2.3. 对数据进行反规范化处理,以实现更高效的图遍历
2. 数据模型2.1. 四步骤
2.1.1. 定义问题
2.1.2. 创建概念数据模型
2.1.3. 创建逻辑数据模型
2.1.4. 测试模型
2.2. 将概念模型转换为逻辑模型的过程
2.2.1. 将实体转换为顶点
2.2.2. 将关系转换为边
2.2.3. 为这些顶点和边指定属性
2.2.4. 测试模型
2.3. 扩展逻辑数据模型
2.3.1. 只有一个顶点和一条边的模型对于复杂的工作是不够的
2.3.2. 微观优化加起来就会显著地提高性能
2.4. 很少有应用程序或用例只遍历单个实体和关系
2.4.1. 很多时候,需要遍历多种顶点和边才能获得所需的结果
2.4.2. 为社交网络开发的寻路和递归遍历不需要遍历不同的实体类型,但这在复杂的领域中实际上是非常罕见的
2.5. 熟路
2.5.1. 在图论中,线路(walk)是由一系列边和顶点组成的序列
2.5.2. 路径(path)是一种特殊类型的线路,只包含不同的顶点
2.5.3. 熟路(known path)是图应用程序中的一种模式,在这种模式中,需要事先知道要遍历的顶点和边的确切序列才能获得答案
2.5.4. 在寻路问题中,我们知道要遍历的顶点和边的序列,但不知道遍历这些顶点和边的次数
2.5.5. 在熟路问题中,我们知道要遍历的顶点和边的序列以及需要遍历的次数
2.5.5.1. 既知道路径又知道该路径的重复次数,因此能够优化数据模型和遍历
2.5.5.2. 熟路优于递归遍历,因为熟路遍历图所需的操作数是已知的
2.5.5.3. 通常会使熟路比迭代次数未知的递归遍历有更稳定的执行时间
2.5.6. 关于遍历深度的先验知识是区分寻路和熟路这两种模式的因素之一
2.5.6.1. 是否知道从开始顶点到结束顶点所需操作(例如实体和关系)的确切定义?
2.5.6.2. 是否知道要获得结果所需的遍历次数?
2.5.6.3. 如果这两个问题的答案都是“是”,那么这个遍历就是熟路
3. 将实体转换为顶点3.1. 使用通用标签
3.1.1. 顶点或边上的标签主要旨在为相似项组成的类别提供一个名称
3.1.2. 最佳实践通常是使用更通用的术语(如user或person),而不是使用具体的术语(如reviewer或restaurant_patron)
3.1.3. 通用标签能够将相关的项分为一组,从而减少实体的类型,简化模型和遍历的编写
3.1.4. 有利于创建更高性能的遍历
3.1.5. 通用标签不仅允许将类似的实体归入最广泛的类别,而且仍然能够区分它们
3.1.6. 所有的遍历都是功能性的
3.1.7. 通用数据标签使我们能够复用标签,通过对类似的实体进行分组来简化遍历,从而创建性能更高、更可扩展的遍历
3.2. 为了实现更多功能(如在街道地图上渲染地址)和更高的复杂性,通常会使用地理空间坐标
3.2.1. 采取了将地理位置反规范化为属性的方法,而不是将地理数据作为单独的实体
3.3. 反规范化图数据
3.3.1. 图数据模型中的反规范化类似于关系数据模型中的反规范化
3.3.2. 两者都在写入时将数据复制到多个位置,以提高读取时的性能
3.3.3. 缺点
3.3.3.1. 增加了磁盘使用量
3.3.3.1.1. 因为数据被写入多个位置,所以增加了存储数据所需的空间
3.3.3.1.2. 磁盘空间的成本已经不算什么问题了,但这些成本还是需要考虑的,特别是对于大型项目或易受成本影响的项目而言
3.3.3.2. 数据同步问题
3.3.3.2.1. 当将数据写入多个位置时,一旦进行更改就必须对每个位置进行更新
3.3.3.2.2. 如果这些位置中的任何一个没有同步,那么不同的遍历可能会返回不同的结果
3.3.3.3. 降低了写入性能
3.3.3.3.1. 因为必须在多个位置更新值,所以需要更多的写入操作
3.3.3.3.2. 写入放大(write amplification)
3.3.3.4. 反规范化不应该是解决性能问题的首选
3.3.3.4.1. 只有在进行了适当的数据建模和横向优化之后都未能达到预期性能时,才应该考虑使用反规范化
3.3.4. 读取性能差通常是由于检索信息所需的操作次数过多
3.3.4.1. 在所有类型的分布式系统中还会被放大,因为读取数据时除了访问内存和磁盘之外,还有额外的网络访问
3.3.5. 在图数据库中,反规范化只是为了减少从起始点到结束数据所需的遍历长度
3.3.6. 索引也是反规范化的一种形式,尽管我们并不总是这样认为
3.3.6.1. 无论使用哪种数据库引擎,只要添加了索引,就在使用一种反规范化形式
3.3.6.2. 数据库引擎针对索引专门设计的简单语法并不能改变这样一个事实:索引是为了提高读取性能而对现有数据的复制
3.3.7. 通过预计算字段或重复数据来对数据进行反规范化,可以在遍历过程中更早地使用数据,从而降低遍历的复杂性
3.3.8. 预计算字段
3.3.8.1. 预计算字段是顶点或边的属性,存储了写入时就计算出的结果,以便在读取时能够快速检索数据
3.3.8.2. 热门电影的检索时间比冷门电影的检索时间长
3.3.8.2.1. 这种类型的性能问题在图数据库中很常见,因为被连接最多的顶点通常是遍历中最常接触的顶点
3.3.8.2.2. 通过预先计算该值,根据watched_count查找答案的时间是恒定的,不管这部电影有多热门
3.3.8.3. 随着时间的推移,数据库记录越来越多,但是预计算字段并不会受到影响
3.3.8.4. 当有聚合(sum、average和count)值并且执行遍历的频率明显高于更新顶点的频率时,预计算字段是一个很好的选择
3.3.8.5. 当待计算字段的读取频率比写入频率高得多时,预计算字段是一个很好的选择
3.3.9. 使用重复数据
3.3.9.1. 将属性从一个顶点或边复制到另一个顶点或边
3.3.9.2. 将属性复制到图中的多个位置允许我们以保持数据同步为代价来优化多个不同的遍历路径
3.3.9.3. 重复数据(通过将数据写入多个位置)也是牺牲写入性能来优化读取性能的一个例子
3.3.9.4. 当有多个遍历模式并且需要通过移动遍历操作之前的所需数据来优化它们时,将数据复制到图中的多个位置是一个很好的选择
3.3.9.5. 重复数据涉及将属性复制到图中的多个位置,以优化多个不同的遍历路径,但要以保持数据同步为代价
3.3.10. 在量化了应用程序的性能之后,确定是否值得对数据反规范化是一件很容易的事情
3.4. 将关系转换为边
3.4.1. 需要决定每一类边的唯一性
3.5. 查找和分配属性
3.5.1. 随着模型的结构元素的就位,是时候开始添加属性了
3.5.2. 常见的方法是将属性从顶点移到相邻边
3.6. 将属性移到边
3.6.1. 将属性从顶点移动到相邻边,以减少遍历需要处理的步数
3.6.2. 越早筛选遍历,遍历的性能就越好
3.6.3. Neo4j和Amazon Neptune,已经优化了其底层数据模型,以在磁盘表示中把边信息配置在相关顶点上。这意味着在这些系统中,将属性移边并不是一种优化
3.6.4. 这种单操作优化在单个遍历器上看起来微不足道,但在运行多个遍历器时,它会带来巨大的性能提升
3.6.5. 这是提高整体遍历性能的最佳方法之一,就像关系数据库中,如果在join操作之前筛选数据,那么性能就会提高
3.6.6. 将属性从顶点移动到边可以减少遍历必须执行的操作数来降低遍历的复杂性
3.7. 检查模型
3.7.1. 验证其构造
4. 比较结果4.1. 顶点和边的名称形成了人类可读的句子,并且实体之间的关系是可以理解的
4.1.1. 这是图数据建模的最大好处之一
4.2. 与关系数据模型不同,技术用户和非技术用户都可以理解图数据模型