Nebula 数据写入流程梳理
1、总览
- Webservice
- Query
- Meta
- Storage
- Raftpart
- Kvstore

1.1、时序图

2、Query流程
query:parser → validator → optimizer → executor
Parser 完成对语句的词法语法解析并生成抽象语法树(AST),Validator 会将 AST 转化为执行计划,Optimizer 对执行计划进行优化,而 Executor 负责实际数据的计算。
上下文QueryContext内容:
- query语句(ctx->setQuery(query))
- 执行线程(ctx→setRunner(getThreadManager()))
- 登录session(ctx→setSessionMgr(sessionManager_.get()))
- 执行参数(ctx->setParameterMap(parameterMap))
2.1、parser
Parser 代码实现在:src/parser 目录下
包括字典文件(lexicon)和语法规则(grammar)
词法文件是 src/parser/scanner.lex,语法文件是 src/parser/parser.yy
2.2、validator
Validator 代码实现在 src/graph/validator 和 src/graph/planner 目录。
src/graph/validator 目录主要包括各种子句的 Validator 实现,比如 OrderByValidator、LimitValidator、GoValidator 等
src/graph/planner/plan 目录定义了所有 PlanNode 的数据结构,用于生成最终的执行计划
src/graph/planner 目录还定义了 nGQL 和 MATCH 语句的 planner 实现,用于生成 nGQL 和 MATCH 语句执行计划
入口函数:
// Entry of validating sentence.
// Check session, switch space of validator context, create validators and validate.
// static
Status Validator::validate(Sentence* sentence, QueryContext* qctx) {
DCHECK(sentence != nullptr);
DCHECK(qctx != nullptr);
// Check if space chosen from session. if chosen, add it to context.
auto session = qctx->rctx()->session();
if (session->space().id > kInvalidSpaceID) {
auto spaceInfo = session->space();
qctx->vctx()->switchToSpace(std::move(spaceInfo));
}
auto validator = makeValidator(sentence, qctx);
NG_RETURN_IF_ERROR(validator->validate());
auto root = validator->root();
if (!root) {
return Status::SemanticError("Get null plan from sequential validator");
}
qctx->plan()->setRoot(root);
return Status::OK();
}
2.3、optimizer
优化器的源码实现都在 src/graph/optimizer 目录下
优化器
数据库的优化器通常分为两类,一类是基于规则的优化器 RBO(Rule-basd optimizer),一类是基于代价的优化 CBO(Cost-based optimizer),
前者完全基于预设的优化规则进行优化,匹配的条件和优化的结果都比较固定;
后者则会根据收集的数据统计信息计算不同执行计划的执行代价,尽量选择代价最小的执行计划。
目前 Nebula Graph 主要实现的是 RBO
src/graph/optimizer/rule 目录则是预设的规则集,其他的源文件则是优化器的具体实现。
优化器优化查询的入口则在 src/graph/service/QueryInstance.cpp 文件中
Status QueryInstance::validateAndOptimize() {
auto *rctx = qctx()->rctx();
auto &spaceName = rctx->session()->space().name;
VLOG(1) << "Parsing query: " << rctx->query();
// Result of parsing, get the parsing tree
auto result = GQLParser(qctx()).parse(rctx->query());
NG_RETURN_IF_ERROR(result);
sentence_ = std::move(result).value();
if (sentence_->kind() == Sentence::Kind::kSequential) {
size_t num = static_cast<const SequentialSentences *>(sentence_.get())->numSentences();
stats::StatsManager::addValue(kNumSentences, num);
if (FLAGS_enable_space_level_metrics && spaceName != "") {
stats::StatsManager::addValue(
stats::StatsManager::counterWithLabels(kNumSentences, {{"space", spaceName}}), num);
}
} else {
stats::StatsManager::addValue(kNumSentences);
if (FLAGS_enable_space_level_metrics && spaceName != "") {
stats::StatsManager::addValue(
stats::StatsManager::counterWithLabels(kNumSentences, {{"space", spaceName}}));
}
}
// Validate the query, if failed, return
NG_RETURN_IF_ERROR(Validator::validate(sentence_.get(), qctx()));
// Optimize the query, and get the execution plan
NG_RETURN_IF_ERROR(findBestPlan());
stats::StatsManager::addValue(kOptimizerLatencyUs, *(qctx_->plan()->optimizeTimeInUs()));
if (FLAGS_enable_space_level_metrics && spaceName != "") {
stats::StatsManager::addValue(
stats::StatsManager::histoWithLabels(kOptimizerLatencyUs, {{"space", spaceName}}));
}
return Status::OK();
}
2.4、executor
调度器的源码在 src/graph/scheduler 目录下
在执行阶段,执行引擎通过 Scheduler(调度器)将 Planner 生成的物理执行计划转换为一系列 Executor,驱动 Executor 的执行。
Scheduler 抽象类定义了调度器的公共接口,可以继承该类实现多种调度器。
目前实现了 AsyncMsgNotifyBasedScheduler 调度器,它基于异步消息通信与广度优先搜索避免栈溢出。
执行器的源码在 src/graph/executor 目录下
Executor,即执行器,物理执行计划中的每个 PlanNode 都会对应一个 Executor。
首先,调度器从执行计划的根节点开始通过使用广度优先搜索算法遍历整个执行计划并根据节点间的执行依赖关系,构建它们的消息通知机制。
执行时,每个节点收到它的所依赖的节点全部执行完毕的消息后,会被调度执行。一旦自身执行完成,又会发送消息给依赖自己的节点,直至整个计划执行完毕。
每个 Executor 会经历 create-open-execute-close 四个阶段:
- create: 根据节点类型生成对应的 Executor
- open: 在 Executor 正式执行前会做一些初始化操作,以及慢查询终止和内存水位的判断
- execute: Query 类型的 Executor 的输入和输出都是一张表(DataSet)。Executor 的执行基于迭代器模型:每次计算时,调用输入表的迭代器的 next() 方法,获取一行数据,进行计算,直至输入表被遍历完毕。计算的结果构成一张新表,输出给后续的 Executor 作为输出。
- close: Executor 执行完毕后,将收集到的一些执行信息如执行时间,输出表的行数等添加到 profiling stats 中。 用户可以在 profile 一个语句后显示的执行计划中查看这些统计信息。

3、Meta流程
3.1、对外接口
MetaService:
service MetaService {
ExecResp createSpace(1: CreateSpaceReq req);
ExecResp dropSpace(1: DropSpaceReq req);
ExecResp clearSpace(1: ClearSpaceReq req);
GetSpaceResp getSpace(1: GetSpaceReq req);
ListSpacesResp listSpaces(1: ListSpacesReq req);
ExecResp alterSpace(1: AlterSpaceReq req);
ExecResp createSpaceAs(1: CreateSpaceAsReq req);
ExecResp createTag(1: CreateTagReq req);
ExecResp alterTag(1: AlterTagReq req);
ExecResp dropTag(1: DropTagReq req);
GetTagResp getTag(1: GetTagReq req);
ListTagsResp listTags(1: ListTagsReq req);
ExecResp createEdge(1: CreateEdgeReq req);
ExecResp alterEdge(1: AlterEdgeReq req);
ExecResp dropEdge(1: DropEdgeReq req);
GetEdgeResp getEdge(1: GetEdgeReq req);
ListEdgesResp listEdges(1: ListEdgesReq req);
ExecResp addHosts(1: AddHostsReq req);
ExecResp addHostsIntoZone(1: AddHostsIntoZoneReq req);
ExecResp dropHosts(1: DropHostsReq req);
ListHostsResp listHosts(1: ListHostsReq req);
GetPartsAllocResp getPartsAlloc(1: GetPartsAllocReq req);
ListPartsResp listParts(1: ListPartsReq req);
GetWorkerIdResp getWorkerId(1: GetWorkerIdReq req);
ExecResp createTagIndex(1: CreateTagIndexReq req);
ExecResp dropTagIndex(1: DropTagIndexReq req );
GetTagIndexResp getTagIndex(1: GetTagIndexReq req);
ListTagIndexesResp listTagIndexes(1:ListTagIndexesReq req);
ExecResp rebuildTagIndex(1: RebuildIndexReq req);
ListIndexStatusResp listTagIndexStatus(1: ListIndexStatusReq req);
ExecResp createEdgeIndex(1: CreateEdgeIndexReq req);
ExecResp dropEdgeIndex(1: DropEdgeIndexReq req );
GetEdgeIndexResp getEdgeIndex(1: GetEdgeIndexReq req);
ListEdgeIndexesResp listEdgeIndexes(1: ListEdgeIndexesReq req);
ExecResp rebuildEdgeIndex(1: RebuildIndexReq req);
ListIndexStatusResp listEdgeIndexStatus(1: ListIndexStatusReq req);
ExecResp createUser(1: CreateUserReq req);
ExecResp dropUser(1: DropUserReq req);
ExecResp alterUser(1: AlterUserReq req);
ExecResp grantRole(1: GrantRoleReq req);
ExecResp revokeRole(1: RevokeRoleReq req);
ListUsersResp listUsers(1: ListUsersReq req);
ListRolesResp listRoles(1: ListRolesReq req);
ListRolesResp getUserRoles(1: GetUserRolesReq req);
ExecResp changePassword(1: ChangePasswordReq req);
HBResp heartBeat(1: HBReq req);
AgentHBResp agentHeartbeat(1: AgentHBReq req);
ExecResp regConfig(1: RegConfigReq req);
GetConfigResp getConfig(1: GetConfigReq req);
ExecResp setConfig(1: SetConfigReq req);
ListConfigsResp listConfigs(1: ListConfigsReq req);
ExecResp createSnapshot(1: CreateSnapshotReq req);
ExecResp dropSnapshot(1: DropSnapshotReq req);
ListSnapshotsResp listSnapshots(1: ListSnapshotsReq req);
AdminJobResp runAdminJob(1: AdminJobReq req);
ExecResp mergeZone(1: MergeZoneReq req);
ExecResp dropZone(1: DropZoneReq req);
ExecResp divideZone(1: DivideZoneReq req);
ExecResp renameZone(1: RenameZoneReq req);
GetZoneResp getZone(1: GetZoneReq req);
ListZonesResp listZones(1: ListZonesReq req);
ExecResp addListener(1: AddListenerReq req);
ExecResp removeListener(1: RemoveListenerReq req);
ListListenerResp listListener(1: ListListenerReq req);
GetStatsResp getStats(1: GetStatsReq req);
ExecResp signInService(1: SignInServiceReq req);
ExecResp signOutService(1: SignOutServiceReq req);
ListServiceClientsResp listServiceClients(1: ListServiceClientsReq req);
ExecResp createFTIndex(1: CreateFTIndexReq req);
ExecResp dropFTIndex(1: DropFTIndexReq req);
ListFTIndexesResp listFTIndexes(1: ListFTIndexesReq req);
CreateSessionResp createSession(1: CreateSessionReq req);
UpdateSessionsResp updateSessions(1: UpdateSessionsReq req);
ListSessionsResp listSessions(1: ListSessionsReq req);
GetSessionResp getSession(1: GetSessionReq req);
ExecResp removeSession(1: RemoveSessionReq req);
ExecResp killQuery(1: KillQueryReq req);
ExecResp reportTaskFinish(1: ReportTaskReq req);
// Interfaces for backup and restore
CreateBackupResp createBackup(1: CreateBackupReq req);
ExecResp restoreMeta(1: RestoreMetaReq req);
ListClusterInfoResp listCluster(1: ListClusterInfoReq req);
GetMetaDirInfoResp getMetaDirInfo(1: GetMetaDirInfoReq req);
VerifyClientVersionResp verifyClientVersion(1: VerifyClientVersionReq req)
SaveGraphVersionResp saveGraphVersion(1: SaveGraphVersionReq req)
GetSegmentIdResp getSegmentId(1: GetSegmentIdReq req);
}
4、Storage流程
storaged分三层:
- Storage interface:Storage 服务的最上层,定义了一系列和图相关的 API。API 请求会在这一层被翻译成一组针对分片的 KV 操作,例如:getNeighbors :查询一批点的出边或者入边,返回边以及对应的属性,并且支持条件过滤。insert vertex/edge :插入一条点或者边及其属性。
- Consensus:Storage 服务的中间层,实现了 Multi Group Raft
- Store Engine :是一个单机版本地存储引擎,提供对本地数据的 get 、 put 、 scan 等操作。相关接口存储在 KVStore.h 和 KVEngine.h 文件

4.1、对外接口
storage.thrift:
service GraphStorageService {
GetNeighborsResponse getNeighbors(1: GetNeighborsRequest req)
// Get vertex or edge properties
GetPropResponse getProps(1: GetPropRequest req);
ExecResponse addVertices(1: AddVerticesRequest req);
ExecResponse addEdges(1: AddEdgesRequest req);
ExecResponse deleteEdges(1: DeleteEdgesRequest req);
ExecResponse deleteVertices(1: DeleteVerticesRequest req);
ExecResponse deleteTags(1: DeleteTagsRequest req);
UpdateResponse updateVertex(1: UpdateVertexRequest req);
UpdateResponse updateEdge(1: UpdateEdgeRequest req);
ScanResponse scanVertex(1: ScanVertexRequest req)
ScanResponse scanEdge(1: ScanEdgeRequest req)
GetUUIDResp getUUID(1: GetUUIDReq req);
// Interfaces for edge and vertex index scan
LookupIndexResp lookupIndex(1: LookupIndexRequest req);
GetNeighborsResponse lookupAndTraverse(1: LookupAndTraverseRequest req);
UpdateResponse chainUpdateEdge(1: UpdateEdgeRequest req);
ExecResponse chainAddEdges(1: AddEdgesRequest req);
ExecResponse chainDeleteEdges(1: DeleteEdgesRequest req);
KVGetResponse get(1: KVGetRequest req);
ExecResponse put(1: KVPutRequest req);
ExecResponse remove(1: KVRemoveRequest req);
}

浙公网安备 33010602011771号