Gran análisis de la base de datos de series temporales openGemini: marco del motor de consultas

Este artículo analiza principalmente el marco openGemini desde el nivel del código fuente y analiza la estructura interna de uno de los operadores (StreamAggregateTransform) a través de un ejemplo de función de agregación, que es útil para el desarrollo de nuevos operadores.

Marco de motor de consultas openGemini

El marco del motor de consultas de openGemini se muestra en la figura y se divide en dos partes: sistema de compilación de declaraciones de consulta y sistema de ejecución de declaraciones de consulta.

Sistema de construcción

Interfaz Http , monitorea las solicitudes de los clientes. openGemini proporciona una interfaz HTTP RESTFULL al mundo exterior y el cliente envía la declaración de consulta

SELECT count(water_level) 
FROM h2o_feet
WHERE time > now()-1h

Convertido internamente en mensajes HTTP y enviado al servidor (ts-sql), llamando a la interfaz "/query"

curl -i -XPOST "http://localhost:8086/query" -k --insecure --data-urlencode "q=SELECT count(water_level)  FROM h2o_feet WHERE time > now()-1h"

La entrada está en open_src/influx/httpd/handler.go y la función h.serveQuery es responsable de procesar las solicitudes. El código central es el siguiente:

func NewHandler(...) {
	...
	h.AddRoutes([]Route{
   		...
   		Route {
        	"query", // Query serving route.
          	"GET", "/query", true, true, h.serveQuery,
   		},
   		Route{
          	"query", // Query serving route.
          	"POST", "/query", true, true, h.serveQuery,
   		},
   		...
	}
	...
}

Parser And Compile , realiza verificación de legalidad, análisis léxico y análisis de sintaxis en la declaración de consulta, compila y genera un árbol de sintaxis abstracta (AST). Si la función recién agregada no está registrada, la función compileFunction la detectará aquí y Se informará un error. La  ERR: undefined function xxxpila de llamadas de función específica es la siguiente:

 0  0x0000000000ecbe20 in github.com/openGemini/openGemini/open_src/influx/query.(*compiledField).compileFunction
    at ./open_src/influx/query/compile.go:454
 1  0x0000000000ecb3cb in github.com/openGemini/openGemini/open_src/influx/query.(*compiledField).compileExpr
    at ./open_src/influx/query/compile.go:369
 2  0x0000000000eca26b in github.com/openGemini/openGemini/open_src/influx/query.(*compiledStatement).compileFields
    at ./open_src/influx/query/compile.go:272
 3  0x0000000000ec9dc7 in github.com/openGemini/openGemini/open_src/influx/query.(*compiledStatement).compile
    at ./open_src/influx/query/compile.go:212
 4  0x0000000000ec9465 in github.com/openGemini/openGemini/open_src/influx/query.Compile
    at ./open_src/influx/query/compile.go:129
 5  0x0000000000edaae5 in github.com/openGemini/openGemini/open_src/influx/query.Prepare
    at ./open_src/influx/query/select.go:125
 6  0x00000000011f28f0 in github.com/openGemini/openGemini/engine/executor.Select
    at ./engine/executor/select.go:49

Logical Plan Builder , basado en los operadores lógicos y el álgebra lógica diseñados en la base de datos de series temporales, genera el plan lógico correspondiente a partir del árbol de sintaxis abstracta. Esto equivale a ensamblar operadores lógicos independientes (como LogicalAggregate, LogicalLimit, LogicalJoin) para completar conjuntamente las funciones esperadas por la declaración de consulta. Cada operador lógico corresponde a un operador físico (que puede entenderse como una entidad funcional de cálculo real). Todas las funciones agregadas, como recuento, mínimo, máximo y modo, pertenecen a operadores lógicos. Los operadores físicos correspondientes son , LogicalAggregateLuego implemente  las funciones correspondientes a contar, mínimo y máximo respectivamente.LogicalAggregateStreamAggregateTransformStreamAggregateTransform

El lugar donde los planes de consulta están relacionados con los operadores lógicos es motor/executor/logic_plan.go

func (b *LogicalPlanBuilderImpl) Aggregate() LogicalPlanBuilder {
	last := b.stack.Pop()
	plan := NewLogicalAggregate(last, b.schema)   // 在这里
	b.stack.Push(plan)
	return b
}

La pila de llamadas es la siguiente:

 0  0x00000000011a8069 in github.com/openGemini/openGemini/engine/executor.NewLogicalAggregate
    at ./engine/executor/logic_plan.go:273
 1  0x00000000011b5d55 in github.com/openGemini/openGemini/engine/executor.(*LogicalPlanBuilderImpl).Aggregate
    at ./engine/executor/logic_plan.go:2235
 2  0x00000000011f57c5 in github.com/openGemini/openGemini/engine/executor.buildAggNode
    at ./engine/executor/select.go:343
 3  0x00000000011f5a35 in github.com/openGemini/openGemini/engine/executor.buildNodes
    at ./engine/executor/select.go:355
 4  0x00000000011f5fa8 in github.com/openGemini/openGemini/engine/executor.buildQueryPlan
    at ./engine/executor/select.go:424
 5  0x00000000011f6134 in github.com/openGemini/openGemini/engine/executor.buildExtendedPlan
    at ./engine/executor/select.go:431
 6  0x00000000011f32b5 in github.com/openGemini/openGemini/engine/executor.(*preparedStatement).BuildLogicalPlan
    at ./engine/executor/select.go:140
 7  0x00000000011f37cc in github.com/openGemini/openGemini/engine/executor.(*preparedStatement).Select
    at ./engine/executor/select.go:173
 8  0x00000000011f2a0f in github.com/openGemini/openGemini/engine/executor.Select
    at ./engine/executor/select.go:63

El lugar donde los operadores lógicos se relacionan con los operadores físicos es en motor/executor/agg_transform.go

147: var _ = RegistryTransformCreator(&LogicalAggregate{}, &StreamAggregateTransformCreator{})

Este es un registro que se ejecuta cuando se inicia el sistema y vincula LogicalAggregate a StreamAggregateTransform. Otros operadores lógicos y físicos también están vinculados de la misma manera, como por ejemplo:

var _ = RegistryTransformCreator(&LogicalGroupBy{}, &GroupByTransformCreator{})

Optimizer , un optimizador heurístico, realiza optimización basada en reglas y reorganización de planes lógicos de acuerdo con las reglas algebraicas del diseño. El nuevo operador de agregación no participa por el momento.

sistema de ejecución

DAG Builder , genera un plano físico a partir del plano lógico optimizado. Puede entenderse simplemente como reemplazar los operadores lógicos con los operadores físicos correspondientes (transformadores) La dependencia entre los operadores físicos está representada por un DAG (gráfico acíclico dirigido).

Transformers , una colección de todos los operadores de ejecución física. Cada Transformer tiene entrada y salida. La entrada aquí puede ser los datos originales o la salida de otros Transformers. La salida aquí es el resultado del cálculo del Transformer y puede usarse como la capa anterior. Transformador: ingresa o devuelve directamente los datos de resultados del cliente.

Pipeline Executor utiliza el modo de canalización para ejecutar subtareas en el plan físico y utiliza DAG Scheduler para programar tareas para garantizar las dependencias entre la ejecución de tareas.

DAG Scheduler utiliza el algoritmo de robo de trabajo para programar el plan físico (DAG) para garantizar la máxima simultaneidad de tareas en cualquier momento.

Los planes de ejecución tradicionales se ejecutan en serie. El propósito de openGemini al introducir DAG es permitir que cada operador físico en el plan de consulta se paralelice tanto como sea posible para mejorar la eficiencia de la consulta. Esta tecnología se utiliza en ClickHouse y Flink.

Como se muestra en la figura, un plano físico generado por DAG Builder consta de 6 transformadores A, B, C, D, E y F. Entre ellos, B y C dependen de A, pero no dependen entre sí. Entonces, cuando La ejecución de A termina, B y C se pueden ejecutar en paralelo. La tarea del Programador DAG es programar efectivamente las relaciones de ejecución antes y después y el procesamiento concurrente de todos los Transformadores, lo cual es un asunto muy complejo. openGemini utiliza inteligentemente las funciones Goroutine y Channel del lenguaje Go para resolver perfectamente el problema de la programación de tareas. El método específico es atravesar el DAG y crear un canal unidireccional para cada borde como canal de transmisión de datos. Los dos transformadores en el En el borde, como el Transformador A y el Transformador B, el Transformador A escribe los resultados del cálculo en el Canal y el Transformador B obtiene los datos del Canal. Si no hay datos en el Canal, el Transformador B se bloquea. De esta manera, el programador puede ejecutar todos los Transformers al mismo tiempo y automáticamente colocar los Transformers posteriores en un estado bloqueado a través de las características del Canal. Una vez que haya datos en el Canal, Golang los programará y ejecutará inmediatamente.

El plan físico es ejecutado principalmente por el objeto PipelineExecutor. El código principal está en motor/executor/pipeline_executor.go. El método principal es Executor(). El código es el siguiente:

func (exec *PipelineExecutor) Execute(ctx context.Context) error {
	...
	for _, p := range exec.processors {
		go func(processor Processor) {
			err := exec.work(processor)
			if err != nil {
				once.Do(func() {
					processorErr = err
					statistics.ExecutorStat.ExecFailed.Increase()
					exec.Crash()
				})
			}
			processor.FinishSpan()
			wg.Done()
		}(p)
	}
	...
}

func (exec *PipelineExecutor) work(processor Processor) error {
	...
	err := processor.Work(exec.context)
	if err != nil {
		...
	}
	return err
}

El procesador en el código es una interfaz, similar a una clase base. Cada objeto Transformer implementa la interfaz de trabajo de la clase base. Esta función de trabajo es la entrada a cada Transformer. procesador.Work() es equivalente a iniciar el transformador.

El canal entre Transformers se completa cuando se inicializa PipelineExecutor.

func (exec *PipelineExecutor) init() {
	exec.processors = make(Processors, 0, len(exec.dag.mapVertexToInfo))
	for vertex, info := range exec.dag.mapVertexToInfo {
		for i, edge := range info.backwardEdges {
			_ = Connect(edge.from.transform.GetOutputs()[0], edge.to.transform.GetInputs()[i])
		}
		exec.processors = append(exec.processors, vertex.transform)
	}
}

Análisis del principio de funcionamiento de StreamAggregateTransform

Aquí tomamos el operador de agregación como ejemplo para analizar su implementación interna y ayudar a comprender cómo desarrollar un operador físico o agregar nuevas funciones dentro del operador.

StreamAggregateTransform lleva la implementación específica de todas las funciones agregadas dentro de openGemini. Veamos primero su estructura.

type StreamAggregateTransform struct {
	BaseProcessor
	
	init                 bool   //判断是否开始处理数据,在Transform退出之前检查该标志,以清空缓存
	sameInterval         bool   //
	prevSameInterval     bool
	prevChunkIntervalLen int
	bufChunkNum          int    //缓存最大容量
	proRes               *processorResults  //上一轮处理结果缓存
	iteratorParam        *IteratorParams   //迭代器参数
	chunkPool            *CircularChunkPool  //存放结果数据的池子
	newChunk             Chunk  //承载从chunkpool中拿到的chunk. trans.newChunk = trans.chunkPool.GetChunk()
	nextChunkCh          chan struct{} //用于事件通知,通过写入通知数据接收协程继续接收数据到缓存中
	reduceChunkCh        chan struct{} //用于事件通知,通过写入通知数据处理协程从缓存中拿数据处理
	bufChunk             []Chunk  //数据缓存,从Inputs中读取待处理数据,缓存在bufChunk中。
	Inputs               ChunkPorts //Transformer的输入管道
	Outputs              ChunkPorts //Transformer的输出管道
	opt                  *query.ProcessorOptions
	aggLogger            *logger.Logger
	postProcess          func(Chunk)

	span        *tracing.Span //用于信息统计使用,比如Explain命令
	computeSpan *tracing.Span //用于信息统计使用,比如Explain命令

	errs errno.Errs
}

StreamAggregateTransform funcionamiento interno

imagen-20230906153853333

Como se muestra en la figura, StreamAggregateTransform creará un objeto ProcessorResults durante la inicialización. En ProcessorResults, se registrarán una serie de iteradores (como FloatColIntegerIterator, StringColIntegerIterator) y métodos de procesamiento de datos (como FloatCountReduce e IntegerCountMerge) de acuerdo con diferentes funciones de agregación. Cada uno El iterador tiene un método Next (), que es una implementación específica de la función agregada, que utiliza métodos de procesamiento de datos registrados previamente (como FloatCountReduce e IntegerCountMerge).

La entrada de StreamAggregateTransform es la función Work(). La función principal inicia la ejecución y reduce los módulos al mismo tiempo. Running es responsable de obtener datos y reduce es responsable de llamar al iterador previamente registrado para procesar los datos agregados y devolverlos.

Análisis del código fuente de cómo funciona StreamAggregateTransform

StreamAggregateTransform se inicializa en motor/executor/agg_transform.go y el código principal es el siguiente:

func NewStreamAggregateTransform(
	inRowDataType, outRowDataType []hybridqp.RowDataType, exprOpt []hybridqp.ExprOptions, opt *query.ProcessorOptions, isSubQuery bool,
) (*StreamAggregateTransform, error) {
	...
	trans := &StreamAggregateTransform{
		opt:           opt,
		bufChunkNum:   AggBufChunkNum,
		Inputs:        make(ChunkPorts, 0, len(inRowDataType)),
		Outputs:       make(ChunkPorts, 0, len(outRowDataType)),
		bufChunk:      make([]Chunk, 0, AggBufChunkNum), //这里规定了bufChunk的容量
		nextChunkCh:   make(chan struct{}),
		reduceChunkCh: make(chan struct{}),
		iteratorParam: &IteratorParams{},
		aggLogger:     logger.NewLogger(errno.ModuleQueryEngine),
		chunkPool:     NewCircularChunkPool(CircularChunkNum, NewChunkBuilder(outRowDataType[0])),
	}
	...
    //这里初始化的trans.proRes对象,NewProcessors()完成了各类迭代器的注册,新增聚合函数需要在这里实现迭代器方法和注册
	trans.proRes, err = NewProcessors(inRowDataType[0], outRowDataType[0], exprOpt, opt, isSubQuery)
	...
}

El código principal de Work() es el siguiente:

func (trans *StreamAggregateTransform) Work(ctx context.Context) error {
	...
	for i := range trans.Inputs {
		go trans.runnable(i, ctx, errs)  //每个Input对应一个runing协程,实则只有一个Input,预留了多个而已
	}

	go trans.reduce(ctx, errs)  //开一个reduce协程

	return errs.Err()
}

El código central para ejecutar y reducir módulos es el siguiente:


func (trans *StreamAggregateTransform) runnable(in int, ctx context.Context, errs *errno.Errs) {
	...
	trans.running(ctx, in)
}
//running协程
func (trans *StreamAggregateTransform) running(ctx context.Context, in int) {
	for {
		select {
		case c, ok := <-trans.Inputs[in].State:
			...
			trans.init = true
			trans.appendChunk(c)  //放入缓存
			if len(trans.bufChunk) == trans.bufChunkNum {  //缓存满了就通知reduce处理数据
				trans.reduceChunkCh <- struct{}{}
			}
			<-trans.nextChunkCh //放一个chunk到缓存,等待reduce消费的信号
			...
		case <-ctx.Done():
			return
		}
	}
}
//reduce协程
func (trans *StreamAggregateTransform) reduce(_ context.Context, errs *errno.Errs,) {
	...
	// return true if transform is canceled
	reduceStart := func() {
		<-trans.reduceChunkCh
	}

	// return true if transform is canceled
	nextStart := func() {
		trans.nextChunkCh <- struct{}{}
	}

	trans.newChunk = trans.chunkPool.GetChunk()  //从池子中分配一片空间
	for {
		nextStart() //通知running继续往缓存放数据
		reduceStart() //消费管道里的事件通知,如果running还没给消息,阻塞在这里等待

        c := trans.nextChunk() //从缓存中取一个chunk(数据处理单位),1个chunk包含多行(rows)数据
		...
		tracing.SpanElapsed(trans.computeSpan, func() {
			trans.compute(c)  //在compute方法中处理chunk数据,最终会调到具体的一个迭代器来完成
			...
		})
	}
}

El módulo en ejecución principalmente completa la lectura de datos de Entrada, los coloca en el caché y notifica a Reducir para su procesamiento. Reduce es responsable de llamar a iteradores específicos para procesar datos. La notificación de eventos se realiza entre Running y Reduce a través de dos canales unidireccionales (nextChunkCh y reduceChunkCh).

Análisis del código fuente del iterador y registro del método de procesamiento de funciones agregadas.

La implementación de NewProcessors () está en motor/executor/call_processor.go, que completa principalmente el registro de iteradores y métodos de procesamiento de datos de cada función agregada. Las nuevas funciones agregadas deben implementar el método iterador y registrarse aquí . El código central es como sigue:

func NewProcessors(inRowDataType, outRowDataType hybridqp.RowDataType, exprOpt []hybridqp.ExprOptions, opt *query.ProcessorOptions, isSubQuery bool) (*processorResults, error) {
	...
	for i := range exprOpt {
		...
		switch expr := exprOpt[i].Expr.(type) {
		case *influxql.Call:
			...
			name := exprOpt[i].Expr.(*influxql.Call).Name
			switch name {
			case "count":
                //聚合函数count走这里,在NewCountRoutineImpl方法中注册
				routine, err = NewCountRoutineImpl(inRowDataType, outRowDataType, exprOpt[i], isSingleCall)
				coProcessor.AppendRoutine(routine)
			case "sum":
				routine, err = NewSumRoutineImpl(inRowDataType, outRowDataType, exprOpt[i], isSingleCall)
				coProcessor.AppendRoutine(routine)
			case "first":
				...
			case "last":
				...
			case "min":
				...
			case "max":
				...
			case "percentile":
				...
			case "percentile_approx", "ogsketch_percentile", "ogsketch_merge", "ogsketch_insert":
				...
			case "median":
				...
			case "mode":
				...
			case "top":
				..
			case "bottom":
				...
			case "distinct":
				...
			case "difference", "non_negative_difference":
				...
			case "derivative", "non_negative_derivative":
				...
			case "elapsed":
				...
			case "moving_average":
				...
			case "cumulative_sum":
				...
			case "integral":
				...
			case "rate", "irate":
				...
			case "absent":
				...
			case "stddev":
				...
			case "sample":
				...
			default:
				return nil, errors.New("unsupported aggregation operator of call processor")
			}
		...
	}
	...
}

La implementación del método NewCountRoutineImpl se encuentra en motor/executor/call_processor.go y el código principal es el siguiente:

func NewCountRoutineImpl(inRowDataType, outRowDataType hybridqp.RowDataType, opt hybridqp.ExprOptions, isSingleCall bool) (Routine, error) {
	...
	dataType := inRowDataType.Field(inOrdinal).Expr.(*influxql.VarRef).Type
	switch dataType {
	case influxql.Integer:  //聚合的字段是整型
		return NewRoutineImpl(
			NewIntegerColIntegerIterator(IntegerCountReduce, IntegerCountMerge, isSingleCall, inOrdinal, outOrdinal, nil, nil), inOrdinal, outOrdinal), nil
	case influxql.Float:  //聚合的字段是浮点型
		return NewRoutineImpl(
			NewFloatColIntegerIterator(FloatCountReduce, IntegerCountMerge, isSingleCall, inOrdinal, outOrdinal, nil, nil), inOrdinal, outOrdinal), nil
	case influxql.String: //聚合的字段是字符串
		return NewRoutineImpl(
			NewStringColIntegerIterator(StringCountReduce, IntegerCountMerge, isSingleCall, inOrdinal, outOrdinal, nil, nil), inOrdinal, outOrdinal), nil
	case influxql.Boolean: //聚合的字段是布尔型
		return NewRoutineImpl(
			NewBooleanColIntegerIterator(BooleanCountReduce, IntegerCountMerge, isSingleCall, inOrdinal, outOrdinal, nil, nil), inOrdinal, outOrdinal), nil
	}
}

Debido a que diferentes tipos de datos tienen diferentes métodos de procesamiento de datos, se deben adaptar diferentes iteradores. Por ejemplo, existen los siguientes datos:

h2o_feet,location=coyote_creek water_level=8.120,description="between 6 and 9 feet" 1566000000000000000
h2o_feet,location=coyote_creek water_level=8.005,description="between 6 and 9 feet" 1566000360000000000
h2o_feet,location=coyote_creek water_level=7.887,description="between 6 and 9 feet" 1566000720000000000
h2o_feet,location=coyote_creek water_level=7.762,description="between 6 and 9 feet" 1566001080000000000
h2o_feet,location=coyote_creek water_level=7.635,description="between 6 and 9 feet" 1566001440000000000

buscar frases

1. SELECT count(water_level) FROM h2o_feet
2. SELECT count(description) FROM h2o_feet

Si water_level es un tipo de coma flotante, use FloatColIntegerIterator. Si es una descripción, use StringColIntegerIterator.

Cada iterador debe contener un método de reducción y combinación. ¿Qué hace exactamente? Tome FloatCountReduce e IntegerCountMerge como ejemplos

/*
c,包含待处理数据的chunk
ordinal, 指定chunk中的数据列
star,表示chunk中数据分组的起始位置。比如查询是按时间分组再统计
end,表示chunk中数据分组的结束位置。比如查询是按时间分组再统计。如果查询没有分组,那么chunk中所有数据都属于一个分组
*/
func FloatCountReduce(c Chunk, ordinal, start, end int) (int, int64, bool) {
	var count int64
    //ordinal指定要计算chunk中第几列数据。如果指定列的数据都没有空值,直接用end-start快速计算count值
	if c.Column(ordinal).NilCount() == 0 {
		// fast path
		count = int64(end - start)
		return start, count, count == 0
	}

	//如果数据列中有空值,就要换一种计算方法
	vs, ve := c.Column(ordinal).GetRangeValueIndexV2(start, end)
	count = int64(ve - vs)
	return start, count, count == 0
}
//merge方法是做一个累计求和,把上一个chunk中统计的count值 + 当前求得的count值。因为chunk中数据量是一定的,要统计的数据可能分成了很多个chunk
func IntegerCountMerge(prevPoint, currPoint *IntegerPoint) {
	...
	prevPoint.value += currPoint.value
}

Análisis de estructura de datos fragmentados

Chunk puede verse como una tabla, que es la unidad más pequeña de transmisión y procesamiento de datos entre Transformers, y es un portador importante de vectorización.

La estructura Chunk se define de la siguiente manera:

type ChunkImpl struct {
	rowDataType   hybridqp.RowDataType
	name          string
	tags          []ChunkTags
	tagIndex      []int
	time          []int64
	intervalIndex []int
	columns       []Column
	dims          []Column
	*record.Record
}

Por ejemplo, usando los mismos datos:

h2o_feet,location=coyote_creek water_level=8.120,description="between 6 and 9 feet" 1566000000000000000
h2o_feet,location=coyote_creek water_level=8.005,description="between 6 and 9 feet" 1566000360000000000
h2o_feet,location=coyote_creek water_level=7.887,description="between 6 and 9 feet" 1566000720000000000
h2o_feet,location=coyote_creek water_level=7.762,description="between 6 and 9 feet" 1566001080000000000
h2o_feet,location=coyote_creek water_level=7.635,description="between 6 and 9 feet" 1566001440000000000

buscar frases

> SHOW TAG KEYS
name: h2o_feet
tagKey
------
location

> SELECT count(water_level) FROM h2o_feet WHERE time >= 1566000000000000000 AND time <= 1566001440000000000 GROUP By time(12m)
name: h2o_feet
time                count
----                -----
1566000000000000000 2
1566000720000000000 2
1566001440000000000 1

Extraiga uno de los fragmentos recibidos por StreamAggregateTransform y vea la composición interna específica.

Iterador Siguiente método de análisis

Tomando FloatColIntegerIterator como ejemplo, en motor/executor/agg_iterator.gen.go, el código central del método Next() es el siguiente:

func (r *FloatColIntegerIterator) Next(ie *IteratorEndpoint, p *IteratorParams) {
    //inChunk存储待处理数据,即StreamAggregateTransform接收到的Chunk最终在这里被处理,outChunk存储处理后的结果数据
	inChunk, outChunk := ie.InputPoint.Chunk, ie.OutputPoint.Chunk
    //inOrdinal是一个位置信息,初始为0,遍历chunk中的所有列。当前例子中chunk只有1列
    //这里判断列是否为空,如果为空,需要给这些列数据用nil填充
	if inChunk.Column(r.inOrdinal).IsEmpty() && r.prevPoint.isNil {
		var addIntervalLen int
		if p.sameInterval {
			addIntervalLen = inChunk.IntervalLen() - 1
		} else {
			addIntervalLen = inChunk.IntervalLen()
		}
		if addIntervalLen > 0 {
			outChunk.Column(r.outOrdinal).AppendManyNil(addIntervalLen)
		}
		return
	}

	var end int
	firstIndex, lastIndex := 0, len(inChunk.IntervalIndex())-1
    //[start,end]代表一个分组数据,分组数据是chunk数据的一个子集
	for i, start := range inChunk.IntervalIndex() {
		if i < lastIndex {
			end = inChunk.IntervalIndex()[i+1]
		} else {
			end = inChunk.NumberOfRows()
		}
        //fn为FloatCountReduce方法
		index, value, isNil := r.fn(inChunk, r.inOrdinal, start, end)
		if isNil && ((i > firstIndex && i < lastIndex) ||
			(firstIndex == lastIndex && r.prevPoint.isNil && !p.sameInterval) ||
			(firstIndex != lastIndex && i == firstIndex && r.prevPoint.isNil) ||
			(firstIndex != lastIndex && i == lastIndex && !p.sameInterval)) {
			outChunk.Column(r.outOrdinal).AppendNil()
			continue
		}
        //这里主要是处理三种情况
        //1. 分组数据跨两个chunk,当前chunk的前面部分数据与上一个chunk的最后部分数据属于一个分组,
        // 当前计算结果需要加前一个数据的结果,调用fv: FloatCountmerge方法
        //2. 分组数据跨两个chunk,当前chunk的后面部分数据与下一个chunk的最前面部分数据属于一个分组,
        // 当前计算结果需要缓存起来
        //3. 分组数据不跨chunk,计算结果为最终结果
		if i == firstIndex && !r.prevPoint.isNil {
			r.processFirstWindow(inChunk, outChunk, isNil, p.sameInterval,
				firstIndex == lastIndex, index, value)
		} else if i == lastIndex && p.sameInterval {
			r.processLastWindow(inChunk, index, isNil, value)
		} else if !isNil {
			r.processMiddleWindow(inChunk, outChunk, index, value)
		}
	}
}

Resumir

Este artículo muestra parte del código central de openGemini, realiza un análisis en profundidad del marco del motor de consultas e introduce en detalle el principio de funcionamiento de StreamAggregateTransform. Espero que sea útil para todos al leer el código fuente y usar la base de datos. .


Sitio web oficial de openGemini: http://www.openGemini.org

Dirección de código abierto de openGemini: https://github.com/openGemini

Cuenta pública openGemini:

Bienvenido a prestar atención ~ ¡Te invitamos sinceramente a unirte a la comunidad openGemini para construir, gobernar y compartir el futuro juntos!

El autor del marco de código abierto NanUI pasó a vender acero y el proyecto fue suspendido. La primera lista gratuita en la App Store de Apple es el software pornográfico TypeScript. Acaba de hacerse popular, ¿por qué los grandes empiezan a abandonarlo? Lista de octubre de TIOBE: Java tiene la mayor caída, C# se acerca Java Rust 1.73.0 lanzado Un hombre fue alentado por su novia AI a asesinar a la Reina de Inglaterra y fue sentenciado a nueve años de prisión Qt 6.6 publicado oficialmente Reuters: RISC-V La tecnología se convierte en la clave de la guerra tecnológica entre China y Estados Unidos. Nuevo campo de batalla RISC-V: no controlado por ninguna empresa o país, Lenovo planea lanzar una PC con Android.
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/3234792/blog/10109606
Recomendado
Clasificación