Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ability to explain an executed request #1188

Merged
merged 23 commits into from
Apr 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
074634f
PR: Add ability to specify execute explain request
shahzadlone Mar 29, 2023
85e13d2
PR: Move `explainRequest` function
shahzadlone Mar 16, 2023
ca997d3
PR: Fix a panic that's caused in the new testing frame work when no e…
shahzadlone Mar 16, 2023
cad30f6
PR: Specify explain type for explainablePlanNodes interface `Explain`…
shahzadlone Mar 16, 2023
045999e
PR: Split simple & execute logic within Explain()
shahzadlone Mar 29, 2023
9d2d43e
PR: Implement structuring of gathered datapoints
shahzadlone Apr 1, 2023
86f440d
PR: Remove commented typeIndexJoin code
shahzadlone Mar 29, 2023
aafb1c0
Add select node execute datapoints
shahzadlone Apr 1, 2023
ed5c6c0
PR: Add scan node execute datapoints
shahzadlone Apr 1, 2023
4105fa4
PR: Rename utils file to fixture.go
shahzadlone Mar 29, 2023
5395311
PR: Add testing fixtures to create documents
shahzadlone Apr 1, 2023
b807a7f
PR: Add type index join node execute datapoints
shahzadlone Apr 1, 2023
487fbf5
PR: Add average node execute datapoints
shahzadlone Apr 1, 2023
584d35f
PR: Add count node execute datapoints
shahzadlone Apr 1, 2023
467335c
PR: Add sum node execute datapoints
shahzadlone Mar 29, 2023
a7966f1
PR: Add toplevel node execute test (no datapoint)
shahzadlone Apr 1, 2023
c3ea916
PR: Add create node execute datapoints
shahzadlone Apr 1, 2023
7b735e6
PR: Add delete node execute datapoints
shahzadlone Apr 1, 2023
fd76a10
PR: Add dagscan node execute datapoints
shahzadlone Apr 1, 2023
690bc89
PR: Add order node execute datapoints
shahzadlone Apr 1, 2023
4da30a1
PR: Add limit node execute datapoints
shahzadlone Apr 1, 2023
77cffc6
PR: Add update node execute datapoints
shahzadlone Apr 1, 2023
5d8eb56
PR: Add group node execute datapoints
shahzadlone Apr 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion client/request/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ type ExplainType string

// Types of explain requests.
const (
SimpleExplain ExplainType = "simple"
SimpleExplain ExplainType = "simple"
ExecuteExplain ExplainType = "execute"
)
24 changes: 22 additions & 2 deletions planner/average.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ type averageNode struct {
sumFieldIndex int
countFieldIndex int
virtualFieldIndex int

execInfo averageExecInfo
}

type averageExecInfo struct {
// Total number of times averageNode was executed.
iterations uint64
}

func (p *Planner) Average(
Expand Down Expand Up @@ -64,6 +71,8 @@ func (n *averageNode) Close() error { return n.plan.Close() }
func (n *averageNode) Source() planNode { return n.plan }

func (n *averageNode) Next() (bool, error) {
n.execInfo.iterations++

hasNext, err := n.plan.Next()
if err != nil || !hasNext {
return hasNext, err
Expand Down Expand Up @@ -100,6 +109,17 @@ func (n *averageNode) SetPlan(p planNode) { n.plan = p }

// Explain method returns a map containing all attributes of this node that
// are to be explained, subscribes / opts-in this node to be an explainablePlanNode.
func (n *averageNode) Explain() (map[string]any, error) {
return map[string]any{}, nil
func (n *averageNode) Explain(explainType request.ExplainType) (map[string]any, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: It feels a bit wasteful both in terms of execution, and maintenance to have Explain take explainType as a param, and then perform the same switch for each node. It might be nicer to instead have two (parameterless) functions on planNode ExplainSimple() and ExplainExecute() and just do the switch once in explain.go

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a good point, I think I went this way because I didn't want to add another function to this interface:

type explainablePlanNode interface {
	planNode
	Explain(explainType request.ExplainType) (map[string]any, error)
}

Which would then result in another public function on all the explainable nodes:

averageNode
countNode
createNode
dagScanNode
deleteNode
groupNode
limitNode
orderNode
scanNode
selectNode
selectTopNode
sumNode
topLevelNode
typeIndexJoin
updateNode

How strong of a preference do you have for this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing too strong as it doesnt really affect anything besides the explain code - I just think it would be slightly nicer. If you prefer it as is, for sure keep it as is - you'll almost certainly be working more than me with it :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the potential upside of making it 2 exlicit methods instead of one, is controlling which nodes actually need a "custom" explain implementation, and which can kinda just skate by with either no explain, or a basic wrappedExplain which just tracks some basic info that is common across all nodes (like #of invocations, results, etc..).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good points, will keep in mind to split these outside this PR when implementing debug or predict explain, keeping as is for now, unless someone has a really strong preference.

switch explainType {
case request.SimpleExplain:
return map[string]any{}, nil

case request.ExecuteExplain:
return map[string]any{
"iterations": n.execInfo.iterations,
}, nil

default:
return nil, ErrUnknownExplainRequestType
}
}
44 changes: 34 additions & 10 deletions planner/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ type dagScanNode struct {
fetcher fetcher.HeadFetcher
spans core.Spans
commitSelect *mapper.CommitSelect

execInfo dagScanExecInfo
}

type dagScanExecInfo struct {
// Total number of times dag scan was issued.
iterations uint64
}

func (p *Planner) DAGScan(commitSelect *mapper.CommitSelect) *dagScanNode {
Expand Down Expand Up @@ -118,23 +125,21 @@ func (n *dagScanNode) Close() error {

func (n *dagScanNode) Source() planNode { return nil }

// Explain method returns a map containing all attributes of this node that
// are to be explained, subscribes / opts-in this node to be an explainablePlanNode.
func (n *dagScanNode) Explain() (map[string]any, error) {
explainerMap := map[string]any{}
func (n *dagScanNode) simpleExplain() (map[string]any, error) {
simpleExplainMap := map[string]any{}

// Add the field attribute to the explanation if it exists.
if n.commitSelect.FieldName.HasValue() {
explainerMap["field"] = n.commitSelect.FieldName.Value()
simpleExplainMap["field"] = n.commitSelect.FieldName.Value()
} else {
explainerMap["field"] = nil
simpleExplainMap["field"] = nil
}

// Add the cid attribute to the explanation if it exists.
if n.commitSelect.Cid.HasValue() {
explainerMap["cid"] = n.commitSelect.Cid.Value()
simpleExplainMap["cid"] = n.commitSelect.Cid.Value()
} else {
explainerMap["cid"] = nil
simpleExplainMap["cid"] = nil
}

// Build the explanation of the spans attribute.
Expand All @@ -152,12 +157,31 @@ func (n *dagScanNode) Explain() (map[string]any, error) {
}
}
// Add the built spans attribute, if it was valid.
explainerMap[spansLabel] = spansExplainer
simpleExplainMap[spansLabel] = spansExplainer

return explainerMap, nil
return simpleExplainMap, nil
}

// Explain method returns a map containing all attributes of this node that
// are to be explained, subscribes / opts-in this node to be an explainablePlanNode.
func (n *dagScanNode) Explain(explainType request.ExplainType) (map[string]any, error) {
switch explainType {
case request.SimpleExplain:
return n.simpleExplain()

case request.ExecuteExplain:
return map[string]any{
"iterations": n.execInfo.iterations,
}, nil

default:
return nil, ErrUnknownExplainRequestType
}
}

func (n *dagScanNode) Next() (bool, error) {
n.execInfo.iterations++

var currentCid *cid.Cid
store := n.planner.txn.DAGstore()

Expand Down
41 changes: 33 additions & 8 deletions planner/count.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/sourcenetwork/immutable"
"github.com/sourcenetwork/immutable/enumerable"

"github.com/sourcenetwork/defradb/client/request"
"github.com/sourcenetwork/defradb/core"
"github.com/sourcenetwork/defradb/planner/mapper"
)
Expand All @@ -33,6 +34,13 @@ type countNode struct {

virtualFieldIndex int
aggregateMapping []mapper.AggregateTarget

execInfo countExecInfo
}

type countExecInfo struct {
// Total number of times countNode was executed.
iterations uint64
}

func (p *Planner) Count(field *mapper.Aggregate, host *mapper.Select) (*countNode, error) {
Expand Down Expand Up @@ -60,33 +68,50 @@ func (n *countNode) Close() error { return n.plan.Close() }

func (n *countNode) Source() planNode { return n.plan }

// Explain method returns a map containing all attributes of this node that
// are to be explained, subscribes / opts-in this node to be an explainablePlanNode.
func (n *countNode) Explain() (map[string]any, error) {
func (n *countNode) simpleExplain() (map[string]any, error) {
sourceExplanations := make([]map[string]any, len(n.aggregateMapping))

for i, source := range n.aggregateMapping {
explainerMap := map[string]any{}
simpleExplainMap := map[string]any{}

// Add the filter attribute if it exists.
if source.Filter == nil || source.Filter.ExternalConditions == nil {
explainerMap[filterLabel] = nil
simpleExplainMap[filterLabel] = nil
} else {
explainerMap[filterLabel] = source.Filter.ExternalConditions
simpleExplainMap[filterLabel] = source.Filter.ExternalConditions
}

// Add the main field name.
explainerMap[fieldNameLabel] = source.Field.Name
simpleExplainMap[fieldNameLabel] = source.Field.Name

sourceExplanations[i] = explainerMap
sourceExplanations[i] = simpleExplainMap
}

return map[string]any{
sourcesLabel: sourceExplanations,
}, nil
}

// Explain method returns a map containing all attributes of this node that
// are to be explained, subscribes / opts-in this node to be an explainablePlanNode.
func (n *countNode) Explain(explainType request.ExplainType) (map[string]any, error) {
switch explainType {
case request.SimpleExplain:
return n.simpleExplain()

case request.ExecuteExplain:
return map[string]any{
"iterations": n.execInfo.iterations,
}, nil

default:
return nil, ErrUnknownExplainRequestType
}
}

func (n *countNode) Next() (bool, error) {
n.execInfo.iterations++

hasValue, err := n.plan.Next()
if err != nil || !hasValue {
return hasValue, err
Expand Down
31 changes: 28 additions & 3 deletions planner/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"encoding/json"

"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/client/request"
"github.com/sourcenetwork/defradb/core"
"github.com/sourcenetwork/defradb/db/base"
"github.com/sourcenetwork/defradb/planner/mapper"
Expand Down Expand Up @@ -44,6 +45,13 @@ type createNode struct {

returned bool
results planNode

execInfo createExecInfo
}

type createExecInfo struct {
// Total number of times createNode was executed.
iterations uint64
}

func (n *createNode) Kind() string { return "createNode" }
Expand All @@ -62,6 +70,8 @@ func (n *createNode) Start() error {

// Next only returns once.
func (n *createNode) Next() (bool, error) {
n.execInfo.iterations++

if n.err != nil {
return false, n.err
}
Expand Down Expand Up @@ -121,9 +131,7 @@ func (n *createNode) Close() error {

func (n *createNode) Source() planNode { return n.results }

// Explain method returns a map containing all attributes of this node that
// are to be explained, subscribes / opts-in this node to be an explainablePlanNode.
func (n *createNode) Explain() (map[string]any, error) {
func (n *createNode) simpleExplain() (map[string]any, error) {
data := map[string]any{}
err := json.Unmarshal([]byte(n.newDocStr), &data)
if err != nil {
Expand All @@ -135,6 +143,23 @@ func (n *createNode) Explain() (map[string]any, error) {
}, nil
}

// Explain method returns a map containing all attributes of this node that
// are to be explained, subscribes / opts-in this node to be an explainablePlanNode.
func (n *createNode) Explain(explainType request.ExplainType) (map[string]any, error) {
switch explainType {
case request.SimpleExplain:
return n.simpleExplain()

case request.ExecuteExplain:
return map[string]any{
"iterations": n.execInfo.iterations,
}, nil

default:
return nil, ErrUnknownExplainRequestType
}
}

func (p *Planner) CreateDoc(parsed *mapper.Mutation) (planNode, error) {
results, err := p.Select(&parsed.Select)
if err != nil {
Expand Down
41 changes: 33 additions & 8 deletions planner/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package planner

import (
"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/client/request"
"github.com/sourcenetwork/defradb/core"
"github.com/sourcenetwork/defradb/planner/mapper"
)
Expand All @@ -27,9 +28,18 @@ type deleteNode struct {

filter *mapper.Filter
ids []string

execInfo deleteExecInfo
}

type deleteExecInfo struct {
// Total number of times deleteNode was executed.
iterations uint64
}

func (n *deleteNode) Next() (bool, error) {
n.execInfo.iterations++

next, err := n.source.Next()
if err != nil {
return false, err
Expand Down Expand Up @@ -74,22 +84,37 @@ func (n *deleteNode) Source() planNode {
return n.source
}

// Explain method returns a map containing all attributes of this node that
// are to be explained, subscribes / opts-in this node to be an explainablePlanNode.
func (n *deleteNode) Explain() (map[string]any, error) {
explainerMap := map[string]any{}
func (n *deleteNode) simpleExplain() (map[string]any, error) {
simpleExplainMap := map[string]any{}

// Add the document id(s) that request wants to delete.
explainerMap[idsLabel] = n.ids
simpleExplainMap[idsLabel] = n.ids

// Add the filter attribute if it exists, otherwise have it nil.
if n.filter == nil || n.filter.ExternalConditions == nil {
explainerMap[filterLabel] = nil
simpleExplainMap[filterLabel] = nil
} else {
explainerMap[filterLabel] = n.filter.ExternalConditions
simpleExplainMap[filterLabel] = n.filter.ExternalConditions
}

return explainerMap, nil
return simpleExplainMap, nil
}

// Explain method returns a map containing all attributes of this node that
// are to be explained, subscribes / opts-in this node to be an explainablePlanNode.
func (n *deleteNode) Explain(explainType request.ExplainType) (map[string]any, error) {
switch explainType {
case request.SimpleExplain:
return n.simpleExplain()

case request.ExecuteExplain:
return map[string]any{
"iterations": n.execInfo.iterations,
}, nil

default:
return nil, ErrUnknownExplainRequestType
}
}

func (p *Planner) DeleteDocs(parsed *mapper.Mutation) (planNode, error) {
Expand Down
10 changes: 8 additions & 2 deletions planner/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ package planner
import "github.com/sourcenetwork/defradb/errors"

const (
errUnknownDependency string = "given field does not exist"
errFailedToClosePlan string = "failed to close the plan"
errUnknownDependency string = "given field does not exist"
errFailedToClosePlan string = "failed to close the plan"
errFailedToCollectExecExplainInfo string = "failed to collect execution explain information"
)

var (
Expand All @@ -30,6 +31,7 @@ var (
ErrMissingChildValue = errors.New("expected child value, however none was yielded")
ErrUnknownRelationType = errors.New("failed sub selection, unknown relation type")
ErrUnknownExplainRequestType = errors.New("can not explain request of unknown type")
ErrFailedToCollectExecExplainInfo = errors.New(errFailedToCollectExecExplainInfo)
ErrUnknownDependency = errors.New(errUnknownDependency)
)

Expand All @@ -40,3 +42,7 @@ func NewErrUnknownDependency(name string) error {
func NewErrFailedToClosePlan(inner error, location string) error {
return errors.Wrap(errFailedToClosePlan, inner, errors.NewKV("Location", location))
}

func NewErrFailedToCollectExecExplainInfo(inner error) error {
return errors.Wrap(errFailedToCollectExecExplainInfo, inner)
}
Loading