この記事では、主にソース コード レベルから openGemini フレームワークを分析し、新しい演算子の開発に役立つ集計関数の例を通じて演算子の 1 つ (StreamAggregateTransform) の内部構造を分析します。
openGemini クエリ エンジン フレームワーク
openGemini のクエリ エンジンのフレームワークは図に示されており、クエリ ステートメントのコンパイル システムとクエリ ステートメントの実行システムの 2 つの部分に分かれています。
ビルドシステム
HTTP インターフェイス、クライアント要求を監視します。openGemini は外部に HTTP RESTFULL インターフェイスを提供し、クライアントはクエリ ステートメントを送信します。
SELECT count(water_level)
FROM h2o_feet
WHERE time > now()-1h
内部で HTTP メッセージに変換され、「/query」インターフェイスを呼び出してサーバー (ts-sql) に送信されます。
curl -i -XPOST "http://localhost:8086/query" -k --insecure --data-urlencode "q=SELECT count(water_level) FROM h2o_feet WHERE time > now()-1h"
入り口は open_src/influx/httpd/handler.go にあり、h.serveQuery 関数がリクエストの処理を担当します。コアコードは次のとおりです。
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 は、クエリ文の正当性チェック、字句解析、構文解析を実行し、抽象構文ツリー (AST) をコンパイルして生成します。新しく追加された関数が登録されていない場合は、ここでのcompileFunction 関数によって検出されます。特定の関数呼び出し ERR: undefined function xxx
スタックは次のとおりです。
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
論理プラン ビルダー は、時系列データベースで設計された論理演算子と論理代数に基づいて、抽象構文ツリーから対応する論理プランを生成します。これは、独立した論理演算子 (LogicalAggregate、LogicalLimit、LogicalJoin など) を組み立てて、クエリ ステートメントで期待される関数を共同で完成させることと同じです。各論理演算子は物理演算子 (実際の計算の機能エンティティとして理解できます) に対応します。count、min、max、mode などのすべての集計関数は論理演算子に属します。対応する物理演算子は次のLogicalAggregate
とおりLogicalAggregate
ですStreamAggregateTransform
。 StreamAggregateTransform
それぞれ count、min、max に対応する関数。
クエリ プランが論理演算子に関連付けられている場所は、engine/executor/logic_plan.go です。
func (b *LogicalPlanBuilderImpl) Aggregate() LogicalPlanBuilder {
last := b.stack.Pop()
plan := NewLogicalAggregate(last, b.schema) // 在这里
b.stack.Push(plan)
return b
}
コールスタックは次のとおりです。
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
論理演算子が物理演算子に関連する場所は、engine/executor/agg_transform.go です。
147: var _ = RegistryTransformCreator(&LogicalAggregate{}, &StreamAggregateTransformCreator{})
これは、システムの起動時に実行され、LogicalAggregate を StreamAggregateTransform にバインドするレジスタです。次のような他の論理演算子と物理演算子も同じ方法でバインドされます。
var _ = RegistryTransformCreator(&LogicalGroupBy{}, &GroupByTransformCreator{})
ヒューリスティック オプティマイザーであるOptimizer は、設計の代数規則に従って、ルールベースの最適化と論理プランの再編成を実行します。現時点では、新しい集計演算子は関与しません。
実行システム
DAG ビルダー は、最適化された論理プランから物理プランを生成します。単純に論理演算子を対応する物理演算子(トランスフォーマー)に置き換えると理解でき、物理演算子間の依存関係はDAG(有向非巡回グラフ)で表現されます。
Transformers は、すべての物理的な実行オペレーターのコレクションです。各 Transformer には入力と出力があります。ここでの入力は、元のデータまたは他の Transformer の出力にすることができます。ここでの出力は、Transformer の計算結果であり、前のレイヤーとして使用できますTransformer. クライアントの結果データを入力するか、直接返します。
Pipeline Executor は、パイプライン モードを使用して物理プラン内のサブタスクを実行し、DAG Scheduler を使用してタスクをスケジュールし、タスク実行間の依存関係を確保します。
DAG スケジューラは、ワークスチール アルゴリズムを使用して物理プラン (DAG) をスケジュールし、いつでも最大のタスクの同時実行性を確保します。
従来の実行プランは逐次的に実行されます。openGemini が DAG を導入する目的は、クエリ プラン内の各物理演算子を可能な限り並列化してクエリ効率を向上させることです。このテクノロジーは ClickHouse と Flink で使用されています。
図に示すように、DAG Builder で生成される物理プランは 6 つのトランスフォーマー A、B、C、D、E、F で構成されます。 A の実行が終了すると、B と C は並行して実行できます。DAG スケジューラのタスクは、すべての Transformer の実行前後の関係と同時処理を効率的にスケジュールすることですが、これは非常に複雑な問題です。openGemini は Go 言語の Goroutine と Channel 機能を巧みに使用して、タスク スケジューリングの問題を完全に解決します。具体的な方法は、DAG をトラバースし、データ送信チャネルとして各エッジに一方向の Channel を作成することです。トランスフォーマ A とトランスフォーマ B のようなエッジでは、トランスフォーマ A は計算結果をチャネルに書き込み、トランスフォーマ B はチャネルからデータを取得します。チャネルにデータがない場合、トランスフォーマ B はブロックされます。このように、スケジューラはすべての Transformers を同時に実行することができ、Channel の特性を利用して後続の Transformers を自動的にブロック状態にします。Channel にデータが存在すると、すぐにスケジュールされ、Golang によって実行されます。
物理プランは主に PipelineExecutor オブジェクトによって実行されます。コア コードは、engine/executor/pipeline_executor.go にあります。コア メソッドは Executor() です。コードは次のとおりです。
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
}
コード内のプロセッサは、基本クラスと同様のインターフェイスです。各 Transformer オブジェクトは、基本クラスの作業インターフェイスを実装します。この作業関数は、各 Transformer への入り口となります。processor.Work() は、Transformer を開始することと同じです。
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)
}
}
StreamAggregateTransformの動作原理の分析
ここでは、物理演算子の開発方法や演算子内に新しい関数を追加する方法を理解するために、集約演算子を例としてその内部実装を分析します。
StreamAggregateTransform は、openGemini 内のすべての集計関数の特定の実装を実行します。まずその構造を見てみましょう。
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 の内部動作
図に示すように、StreamAggregateTransform は初期化中に ProcessorResults オブジェクトを作成し、ProcessorResults には、さまざまな集計関数に従って一連のイテレーター (FloatColIntegerIterator、StringColIntegerIterator など) とデータ処理メソッド (FloatCountReduce、IntegerCountMerge など) が登録されます。 iterator (イテレーター) には Next() メソッドがあり、このメソッドは、事前に登録されたデータ処理メソッド (FloatCountReduce や IntegerCountMerge など) を使用する集計関数関数の具体的な実装です。
StreamAggregateTransform の入り口は Work() 関数です。コア関数は実行モジュールと Reduce モジュールを同時に開始します。Running モジュールはデータのフェッチを担当し、Reduce モジュールは事前に登録されたイテレータを呼び出して集計されたデータを処理して返します。
StreamAggregateTransform がどのように動作するかのソース コード分析
StreamAggregateTransform は、engine/executor/agg_transform.go で初期化されます。コア コードは次のとおりです。
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)
...
}
Work() のコアコードは次のとおりです。
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()
}
実行モジュールと Reduce モジュールのコア コードは次のとおりです。
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数据,最终会调到具体的一个迭代器来完成
...
})
}
}
実行モジュールは主に、入力からのデータの読み取り、キャッシュへの格納、処理のために Reduce への通知を完了します。Reduce は、特定のイテレータを呼び出してデータを処理する役割を果たします。イベント通知はRunningとReduceの間で2つの一方向パイプ(nextChunkChとreduceChunkCh)を通じて行われます。
イテレータのソースコード解析と集合関数の処理方法登録
NewProcessors() の実装は、engine/executor/call_processor.go にあり、主に各集約関数のイテレータとデータ処理メソッドの登録を完了します。新しい集約関数は、ここでイテレータ メソッドと登録を実装する必要があります。コア コードは次のとおりです。以下に続きます:
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")
}
...
}
...
}
NewCountRoutineImpl メソッドの実装は、engine/executor/call_processor.go にあります。コア コードは次のとおりです。
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
}
}
データ型が異なればデータ処理方法も異なるため、異なる反復子を適応させる必要があります。たとえば、次のようなデータがあります。
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
フレーズをチェックする
1. SELECT count(water_level) FROM h2o_feet
2. SELECT count(description) FROM h2o_feet
Water_level が浮動小数点型の場合は FloatColIntegerIterator を使用し、記述型の場合は StringColIntegerIterator を使用します。
各イテレータには Reduce メソッドと Merge メソッドが含まれている必要があります。正確には何をするのでしょうか?FloatCountReduce と IntegerCountMerge を例として取り上げます
/*
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
}
チャンクデータ構造解析
チャンクはテーブルとして見ることができます。これは、Transformer 間のデータ送信およびデータ処理の最小単位であり、ベクトル化の重要なキャリアです。
チャンク構造は次のように定義されます。
type ChunkImpl struct {
rowDataType hybridqp.RowDataType
name string
tags []ChunkTags
tagIndex []int
time []int64
intervalIndex []int
columns []Column
dims []Column
*record.Record
}
たとえば、同じデータを使用すると、次のようになります。
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
フレーズをチェックする
> 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
StreamAggregateTransform によって受信されたチャンクの 1 つを抽出し、特定の内部構成を表示します。
Iterator Next メソッドの分析
FloatColIntegerIterator を例として、engine/executor/agg_iterator.gen.go 内の Next() メソッドのコア コードは次のとおりです。
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)
}
}
}
要約する
この記事では、openGemini のコア コードの一部を示し、クエリ エンジン フレームワークの詳細な分析を行い、StreamAggregateTransform の動作原理を詳細に紹介しますので、ソース コードを読んでデータベースを使用する際の皆様の参考になれば幸いです。 。
openGemini公式ウェブサイト:http://www.openGemini.org
openGemini オープンソース アドレス: https://github.com/openGemini
openGemini パブリック アカウント:
注目へようこそ~openGeminiコミュニティに参加して、一緒に未来を構築、管理、共有することを心から歓迎します!
オープンソース フレームワーク NanUI の作者がスチールの販売に切り替えたため、プロジェクトは中断されました。Apple App Store の無料リストのナンバー 1 はポルノ ソフトウェア TypeScript です。人気が出てきたばかりなのに、なぜ大手はそれを放棄し始めるのでしょうか。 ? TIOBE 10月リスト:Javaが最大の下落、C#はJavaに迫る Rust 1.73.0リリース AIガールフレンドにイギリス女王暗殺を勧められた男性に懲役9年の実刑判決 Qt 6.6正式リリース ロイター:RISC-Vテクノロジーが中米テクノロジー戦争の鍵となる 新たな戦場 RISC-V: 単一の企業や国に支配されない レノボ、Android PC の発売を計画