知识库

将显式 Lucene 索引迁移到原生模式索引

鉴于仍有一些客户使用较旧的 Neo4j 版本并采用 legacy/explicit 索引,我们将在此讨论一些要点,说明在升级到 Neo4j 4.x 版本时如何将这些索引转换为本机模式索引,因为 legacy/explicit 索引自 3.5.x 起已被弃用,并在 4.x 版本中被完全移除。

背景是,Legacy/explicit 索引是 Neo4j 3.2(发布日期 2017)之前唯一可用的索引类型。这些早期版本的索引是基于 Lucene 实现的,通常会导致显著的写入性能下降,因此需要用 3.3 及以上版本提供的本机模式索引来替代它们。

除了性能方面的考量外,显式索引在通过 Java API(以及自 3.3.x 起的存储过程)添加/删除/更新节点、关系或属性时,还必须显式/手动保持最新,这也是它们被称为显式索引的原因。

相反,本机索引会自动维护,从而无需任何手动步骤或额外代码来保持其最新。

从实现角度来看,这些 legacy 索引几乎不包含模式细节,除了索引创建时原作者选择的命名约定外,几乎没有可供使用的线索来帮助以脚本方式实现自动迁移到 Neo4j 本机模式索引的过程。

因此,任何索引迁移都必须逐个索引进行,可能需要一定程度的猜测。这意味着需要查看 API 代码,评估哪些节点或关系属性被索引,然后相应地在这些属性上实现相匹配的模式索引,或者审查 Cypher 查询并制定支持性索引以优化其执行。

总体而言,以下是推荐的高级别步骤

1) 获取当前版本中所有索引的列表

  • 示例:在 3.x 中,您可以使用 "call db.index.explicit.list();",它会显示索引名称以及其类型,类型可以是 “exact” 或 “full-text”,并指明是节点索引还是关系索引,这有助于您确定要构建何种模式索引。

2) 接下来查看您的 Cypher 或 Java API 代码,并相应地将其转换为 4.x+ 格式。

  • 例如,将 “start” 语句转换为等价的 MATCH 语句。示例

    • START n=node:myExplicitIndexYear("myid:1234567") RETURN n;

    • 上述查询应改为如下

    • MATCH (n:Person{myid:1234567}) RETURN n;

3) 实现等价的模式索引,以支持并优化步骤 2 中关联/转换后的查询的执行。

  • 示例:CREATE INDEX index_name FOR (n:Person) ON (n.myid);

  • 更具体地说

    • 检查所有 Cypher 查询,了解它们实际写入该索引的内容,并据此生成相应的 CREATE INDEX(针对整数、字符串、日期、地理空间等)或调用 db.index.fulltext.createIndexForNodes(全文检索)语句。

    • 检查所有对该索引的读取,并采用语句使用新索引(这意味着需要启用查询日志,以捕获生成的 Cypher 查询及其性能属性,如执行时间、IO、内存、CPU 等,以标记运行时间最长的查询并逐个进行排查——而且您的查询模式很可能相互类似,因此修复一个查询后,同类查询也会受益)。

4) 完成所有显式索引的处理后,关闭数据库并移动/删除相关的索引子文件夹(例如 /data/databases/graph.db/index)。

请注意,如果在将 legacy 索引转换为等价的模式索引后,使用 legacy 索引的搜索查询返回的行数与模式索引不符(出现意外的行数),这可能表明模式索引定义不正确(标签或属性错误),或者 legacy 索引未及时更新(记住 legacy 索引必须手动保持最新,例如,如果您的 Java API 代码缺乏适当的错误处理,则在因服务器重启等原因导致回滚时,事务可能没有被正确重试和提交)。以下是一个 3.x 示例,演示了此类情况。

// Create New Nodes
//
CREATE (p:Person {name:'Steve_1',year:1990});
CREATE (p:Person {name:'Steve_2',year:1990});
CREATE (p:Person {name:'Steve_3',year:1990});


// Create Two Explicit Indexes(also called Legacy/Manual/Lucene Indexes)
//
CALL db.index.explicit.forNodes('myExplicitIndexFTS', {type: 'fulltext', provider: 'lucene'});
CALL db.index.explicit.forNodes('myExplicitIndexYear', {type: 'exact', provider: 'lucene'});

// List Explicit Indexes
//
call db.index.explicit.list;

// Index nodes with name property
//
MATCH (p:Person{name:'Steve_1'}) call db.index.explicit.addNode('myExplicitIndexFTS', p, 'name', 'Steve_1') yield success return count(*);
MATCH (p:Person{name:'Steve_2'}) call db.index.explicit.addNode('myExplicitIndexFTS', p, 'name', 'Steve_2') yield success return count(*);

// Index nodes with "birth year" property
//
match (p:Person{name:'Steve_1'})
with p
call db.index.explicit.addNode("myExplicitIndexYear",p,"year",p.year) yield success return count(*);

// Search for persons with matching name VE, which will only return Steve_1, and Steve_2
//
CALL db.index.explicit.searchNodes('myExplicitIndexFTS','name:*VE*');

// Search for persons with year=1990 which will only return Steve_1
//
CALL db.index.explicit.searchNodes('myExplicitIndexYear','year:1990');

// Search for persons with year=1990 using explicit index which will only return Steve_1
//
start n=node:myExplicitIndexYear("year:1990") return n;

// Convert "start" to equivalent Match statement, and this statement returns all 3 rows corresponding to year=1990 (and of course ideally, you would want to create an index on :Person(
year) or :Person(name) for best performance when creating equivalent native schema indexes on these two properties.
match (n:Person{year:1990}) return n;

附录

© . This site is unofficial and not affiliated with Neo4j, Inc.