github.com/m3db/m3@v1.5.0/src/query/plan/physical.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package plan 22 23 import ( 24 "fmt" 25 "time" 26 27 "github.com/m3db/m3/src/query/executor/transform" 28 "github.com/m3db/m3/src/query/models" 29 "github.com/m3db/m3/src/query/parser" 30 ) 31 32 // PhysicalPlan represents the physical plan. 33 type PhysicalPlan struct { 34 steps map[parser.NodeID]LogicalStep 35 pipeline []parser.NodeID // Ordered list of steps to be performed 36 ResultStep ResultOp 37 TimeSpec transform.TimeSpec 38 Debug bool 39 BlockType models.FetchedBlockType 40 LookbackDuration time.Duration 41 } 42 43 // ResultOp is responsible for delivering results to the clients. 44 type ResultOp struct { 45 Parent parser.NodeID 46 } 47 48 // NewPhysicalPlan is used to generate a physical plan. 49 // Its responsibilities include creating consolidation nodes, result nodes, 50 // pushing down predicates, and changing the ordering for nodes. 51 // nolint: unparam 52 func NewPhysicalPlan( 53 lp LogicalPlan, 54 params models.RequestParams, 55 ) (PhysicalPlan, error) { 56 if params.Step <= 0 { 57 return PhysicalPlan{}, fmt.Errorf("expected non-zero step size, got %d", 58 params.Step) 59 } 60 61 // generate a new physical plan after cloning the logical plan so that any 62 // changes here do not update the logical plan. 63 cloned := lp.Clone() 64 p := PhysicalPlan{ 65 steps: cloned.Steps, 66 pipeline: cloned.Pipeline, 67 TimeSpec: transform.TimeSpec{ 68 Start: params.Start, 69 End: params.ExclusiveEnd(), 70 Now: params.Now, 71 Step: params.Step, 72 }, 73 Debug: params.Debug, 74 BlockType: params.BlockType, 75 LookbackDuration: params.LookbackDuration, 76 } 77 78 pl, err := p.createResultNode() 79 if err != nil { 80 return PhysicalPlan{}, err 81 } 82 83 // Update times 84 pl = pl.shiftTime() 85 return pl, nil 86 } 87 88 func (p PhysicalPlan) shiftTime() PhysicalPlan { 89 var maxRange time.Duration 90 91 for _, transformID := range p.pipeline { 92 node := p.steps[transformID] 93 boundOp, ok := node.Transform.Op.(transform.BoundOp) 94 if !ok { 95 continue 96 } 97 98 spec := boundOp.Bounds() 99 100 if spec.Range > maxRange { 101 maxRange = spec.Range 102 } 103 } 104 105 startShift := p.LookbackDuration 106 if maxRange > 0 { 107 startShift = maxRange 108 } 109 110 remainder := startShift % p.TimeSpec.Step 111 var extraShift time.Duration 112 if remainder != 0 { 113 // Align the shift to be divisible by step. 114 extraShift = p.TimeSpec.Step - remainder 115 } 116 117 alignedShift := startShift + extraShift 118 119 p.TimeSpec.Start = p.TimeSpec.Start.Add(-1 * alignedShift) 120 121 return p 122 } 123 124 func (p PhysicalPlan) createResultNode() (PhysicalPlan, error) { 125 leaf, err := p.leafNode() 126 if err != nil { 127 return p, err 128 } 129 130 p.ResultStep = ResultOp{Parent: leaf.ID()} 131 return p, nil 132 } 133 134 func (p PhysicalPlan) leafNode() (LogicalStep, error) { 135 var leaf LogicalStep 136 found := false 137 for _, transformID := range p.pipeline { 138 node, ok := p.steps[transformID] 139 if !ok { 140 return leaf, fmt.Errorf("transform not found, %s", transformID) 141 } 142 143 if len(node.Children) == 0 { 144 if found { 145 return leaf, fmt.Errorf("multiple leaf nodes found, %v - %v", leaf, node) 146 } 147 148 leaf = node 149 found = true 150 } 151 } 152 153 return leaf, nil 154 } 155 156 // Step gets the logical step using its unique ID in the DAG. 157 func (p PhysicalPlan) Step(ID parser.NodeID) (LogicalStep, bool) { 158 // Editor complains when inlining the map get 159 step, ok := p.steps[ID] 160 return step, ok 161 } 162 163 // String representation of the physical plan. 164 func (p PhysicalPlan) String() string { 165 return fmt.Sprintf("StepCount: %s, Pipeline: %s, Result: %s, TimeSpec: %v", 166 p.steps, p.pipeline, p.ResultStep, p.TimeSpec) 167 }