@@ -58,12 +58,16 @@ type ExecutorConfig struct {
5858
5959 // RangeConfig determines how to optimize range reads in the V2 engine.
6060 RangeConfig rangeio.Config `yaml:"range_reads" category:"experimental" doc:"description=Configures how to read byte ranges from object storage when using the V2 engine."`
61+
62+ // MetaqueriesEnabled toggles the metaquery planning stage.
63+ MetaqueriesEnabled bool `yaml:"metaqueries_enabled" category:"experimental"`
6164}
6265
6366func (cfg * ExecutorConfig ) RegisterFlagsWithPrefix (prefix string , f * flag.FlagSet ) {
6467 f .IntVar (& cfg .BatchSize , prefix + "batch-size" , 100 , "Experimental: Batch size of the next generation query engine." )
6568 f .IntVar (& cfg .MergePrefetchCount , prefix + "merge-prefetch-count" , 0 , "Experimental: The number of inputs that are prefetched simultaneously by any Merge node. A value of 0 means that only the currently processed input is prefetched, 1 means that only the next input is prefetched, and so on. A negative value means that all inputs are be prefetched in parallel." )
6669 cfg .RangeConfig .RegisterFlags (prefix + "range-reads." , f )
70+ f .BoolVar (& cfg .MetaqueriesEnabled , prefix + "metaquery-enable" , false , "Experimental: Enable the metaquery planning stage that precomputes catalog lookups." )
6771}
6872
6973// Params holds parameters for constructing a new [Engine].
@@ -107,6 +111,13 @@ type Engine struct {
107111 limits logql.Limits // Limits to apply to engine queries.
108112
109113 metastore metastore.Metastore
114+ // metaqueriesEnabled gates the metaquery planning stage.
115+ metaqueriesEnabled bool
116+ metaqueryRunner physical.MetaqueryRunner
117+ }
118+
119+ func (e * Engine ) metaqueriesActive () bool {
120+ return e .metaqueriesEnabled && e .metaqueryRunner != nil
110121}
111122
112123// New creates a new Engine.
@@ -120,9 +131,10 @@ func New(params Params) (*Engine, error) {
120131 metrics : newMetrics (params .Registerer ),
121132 rangeConfig : params .Config .RangeConfig ,
122133
123- scheduler : params .Scheduler ,
124- bucket : bucket .NewXCapBucket (params .Bucket ),
125- limits : params .Limits ,
134+ scheduler : params .Scheduler ,
135+ bucket : bucket .NewXCapBucket (params .Bucket ),
136+ limits : params .Limits ,
137+ metaqueriesEnabled : params .Config .MetaqueriesEnabled ,
126138 }
127139
128140 if e .bucket != nil {
@@ -133,6 +145,12 @@ func New(params Params) (*Engine, error) {
133145 e .metastore = metastore .NewObjectMetastore (indexBucket , e .logger , params .Registerer )
134146 }
135147
148+ if e .metaqueriesEnabled && e .metastore != nil {
149+ e .metaqueryRunner = & physical.LocalMetaqueryRunner {Metastore : e .metastore }
150+ } else {
151+ e .metaqueriesEnabled = false
152+ }
153+
136154 return e , nil
137155}
138156
@@ -305,9 +323,31 @@ func (e *Engine) buildPhysicalPlan(ctx context.Context, logger log.Logger, param
305323 region := xcap .RegionFromContext (ctx )
306324 timer := prometheus .NewTimer (e .metrics .physicalPlanning )
307325
308- // TODO(rfratto): To improve the performance of the physical planner, we
309- // may want to parallelize metastore lookups across scheduled tasks as well.
310- catalog := physical .NewMetastoreCatalog (ctx , e .metastore )
326+ var (
327+ catalog physical.Catalog
328+ metaDuration time.Duration
329+ metaRequests int
330+ )
331+ if e .metaqueriesActive () {
332+ // run all the metastore lookups at this point and prepare a metastore catalog that already has all the answers
333+ var err error
334+ catalog , metaDuration , metaRequests , err = e .prepareCatalogWithMetaqueries (ctx , params , logicalPlan )
335+ if err != nil {
336+ level .Warn (logger ).Log ("msg" , "failed to prepare metaqueries" , "err" , err )
337+ region .RecordError (err )
338+ return nil , 0 , ErrPlanningFailed
339+ }
340+ e .metrics .metaqueryPlanning .Observe (metaDuration .Seconds ())
341+ level .Info (logger ).Log ("msg" , "finished metaquery planning" , "duration" , metaDuration .String (), "requests" , metaRequests )
342+ region .AddEvent ("finished metaquery planning" ,
343+ attribute .Stringer ("duration" , metaDuration ),
344+ attribute .Int ("requests" , metaRequests ),
345+ )
346+ } else {
347+ // TODO(rfratto): To improve the performance of the physical planner, we
348+ // may want to parallelize metastore lookups across scheduled tasks as well.
349+ catalog = physical .NewMetastoreCatalog (ctx , e .metastore )
350+ }
311351
312352 // TODO(rfratto): It feels strange that we need to past the start/end time
313353 // to the physical planner. Isn't it already represented by the logical
@@ -341,6 +381,32 @@ func (e *Engine) buildPhysicalPlan(ctx context.Context, logger log.Logger, param
341381 return physicalPlan , duration , nil
342382}
343383
384+ func (e * Engine ) prepareCatalogWithMetaqueries (ctx context.Context , params logql.Params , logicalPlan * logical.Plan ) (physical.Catalog , time.Duration , int , error ) {
385+ start := time .Now ()
386+
387+ collector := physical .NewMetaqueryCollectorCatalog ()
388+ collectorPlanner := physical .NewPlanner (physical .NewContext (params .Start (), params .End ()), collector )
389+ if _ , err := collectorPlanner .Build (logicalPlan ); err != nil {
390+ return nil , 0 , 0 , err
391+ }
392+
393+ requests := collector .Requests ()
394+
395+ prepared := physical .NewMetaqueryPreparedCatalog ()
396+ for _ , req := range requests {
397+ result , err := e .metaqueryRunner .Run (ctx , req )
398+ if err != nil {
399+ return nil , 0 , 0 , fmt .Errorf ("executing metaquery: %w" , err )
400+ }
401+ err = prepared .Store (req , result )
402+ if err != nil {
403+ return nil , 0 , 0 , fmt .Errorf ("storing metaquery result: %w" , err )
404+ }
405+ }
406+
407+ return prepared , time .Since (start ), len (requests ), nil
408+ }
409+
344410// buildWorkflow builds a workflow from the given physical plan.
345411func (e * Engine ) buildWorkflow (ctx context.Context , logger log.Logger , physicalPlan * physical.Plan ) (* workflow.Workflow , time.Duration , error ) {
346412 tenantID , err := user .ExtractOrgID (ctx )
0 commit comments