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

[PPS] CreateDatum hierarchical input types #9928

Merged
merged 12 commits into from Apr 22, 2024

Conversation

smalyala
Copy link
Contributor

@smalyala smalyala commented Apr 4, 2024

This PR adds support for Union, Cross, Join, and Group inputs to the CreateDatum RPC. It builds upon the changes made in #9712.

One can visualize the Input as a tree. Inputs deeper in the spec are represented by deeper levels in the tree. When more datums need to be created, a datum request is propagated down the tree level-by-level via channels until it reaches the PFS inputs (leaves). The PFS nodes then send shards up one level for processing. The processed result shards are further propagated upwards, until eventually, they reach the root. The processed results at the root level are sent to the iterator, which iterates over each shard (file set) and sends the datums back to the client.

Logic by input:

  • Union
    • shards received from children inputs are simply propagated up
  • Cross, Join
    • a shard associated with a child input is received
    • a cartesian product occurs to create all permutations of shards with the shard just received along with ones from other children inputs
      • each permutation represents a Cross/Join task (one shard per child input)
      • the comments for shardPermute() contain an example
  • Group
    • all key file sets are needed for the Group task so this is largely unchanged from it's ListDatum implementation

Jira: INT-1204

@smalyala smalyala force-pushed the smalyala/createdatum-hierarchical branch 4 times, most recently from 5647ad3 to 21b54d1 Compare April 7, 2024 18:57
@smalyala smalyala force-pushed the smalyala/createdatum-hierarchical branch 3 times, most recently from f800f2e to 968c0ff Compare April 8, 2024 18:38
Copy link

codecov bot commented Apr 8, 2024

Codecov Report

Attention: Patch coverage is 74.19355% with 72 lines in your changes are missing coverage. Please review.

Project coverage is 58.09%. Comparing base (9697f86) to head (7964d98).
Report is 20 commits behind head on master.

Files Patch % Lines
src/server/worker/datum/create_stream.go 73.72% 72 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9928      +/-   ##
==========================================
+ Coverage   58.00%   58.09%   +0.08%     
==========================================
  Files         608      608              
  Lines       73924    74189     +265     
==========================================
+ Hits        42883    43099     +216     
- Misses      30485    30539      +54     
+ Partials      556      551       -5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@smalyala smalyala force-pushed the smalyala/createdatum-hierarchical branch from 8ce580d to 781a105 Compare April 10, 2024 16:21
@smalyala smalyala marked this pull request as ready for review April 10, 2024 16:22
@smalyala smalyala requested review from a team as code owners April 10, 2024 16:22
@smalyala smalyala removed the request for review from brycemcanally April 10, 2024 16:26
Copy link
Contributor

@bbonenfant bbonenfant left a comment

Choose a reason for hiding this comment

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

Approving, but we should look into why these formatter changes are happening spuriously.

Copy link
Member

@jrockway jrockway left a comment

Choose a reason for hiding this comment

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

This is looking good. I think you want to figure out how to test each of the stream producers outside of pachd and write some unit tests that cover the actual calculations; are the joins correct, are the crosses correct, etc. That is what is currently missing.

// Consumes file set id shards from each child input as they arrive. Calls cb with the index of
// the child input. Function returns when all children channels are closed or the context is done.
func consumeChildrenFsidChans(ctx context.Context, childrenFsidChans []chan string, cb func(int, string) error) error {
cases := make([]reflect.SelectCase, len(childrenFsidChans)+1)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should use reflection for this. Just use a single channel and if you need to retain the index information, send struct{ id int; fsid string } instead of just the fsid.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason I use one channel per child input is so the child input can easily indicate to the parent it's done sending all its fsids by closing its channel. With the single channel approach, we can mimic that behavior by returning an empty string for the fsid (for example) to indicate a child is done sending its fsids. Does that sound okay?

@@ -61,17 +61,17 @@ func DoOne(ctx context.Context, doer Doer, input *anypb.Any) (*anypb.Any, error)

// DoBatch executes a batch of tasks.
func DoBatch(ctx context.Context, doer Doer, inputs []*anypb.Any, cb CollectFunc) error {
var eg errgroup.Group
eg, egCtx := errgroup.WithContext(ctx)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it's a big problem to call this variable ctx. It prevents someone from accidentally using the parent context when they intend to use this one.

}
listDatumTimeToFirstDatum = time.Since(start).Seconds()
return nil
testTimeToFirstDatum := func(input *pps.Input) {
Copy link
Member

Choose a reason for hiding this comment

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

Do these tests need to use Pachyderm running in k8s? RealEnv has been replaced with pachd.NewTestPachd and can probably run tasks.

if index == len(input) {
temp := make([]string, len(result))
copy(temp, result)
*output = append(*output, temp)
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need a copy of result here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We modify the underlying array in result to generate permutations. Once we create a permutation, a copy needs to be created, because in subsequent iterations to create other permutations, the underlying array for result is modified.

fsidChan := make(chan string)
go streamingCreate(egCtx, c, taskDoer, input, fsidChan, errChan, requestDatumsChan)
for fsid := range fsidChan {
if err := renewer.Add(ctx, fsid); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

I think this should use the error group context. If the error group is done but the parent isn't, then this goroutine will live too long.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch

return errors.Wrap(err, "consumeChildrenFsidChans")
}
} else {
finished[i] = true
Copy link
Member

Choose a reason for hiding this comment

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

I think you need to remove the i-th selector from cases; nothing guarantees not hitting this 'matched because the channel is closed' case on the next Select call. It could happen forever.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is the concern that we'll never exit from the loop or that we'll unnecessarily do extra iterations by matching on the closed channel repeatedly?

{"c", "2", "x"},
{"c", "2", "y"},
}
for _, e := range expected {
Copy link
Member

Choose a reason for hiding this comment

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

You should rework this test to use require.NoDiff. I think that slicesEqualUnordered and sliceExistsInSlices only exist to get around having to sort expected and expected[], you should just sort those (with cmpopt.SortSlices for []string and [][]string). The output will be much easier to read and you don't need to maintain these helpers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like that better. Will do.

client.NewPFSInputOpts(repo, pfs.DefaultProjectName, repo, "master", "/file-?*(??)0", "$1", "", false, false, nil),
client.NewPFSInputOpts(repo, pfs.DefaultProjectName, repo, "master", "/file-?0(??)0", "$1", "", false, false, nil),
)
testTimeToFirstDatum(input)
})
Copy link
Member

Choose a reason for hiding this comment

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

This should be a table-driven test:

testData := []struct{name string; input *pfs.Input}{
  {"PFSInput", client.NewPFSInput(...)},
  {"UnionInput", client.NewUnionInput(...)},
  ...
}
for _, test := range testData {
   t.Run(test.name, func(t *testing.T) { 
      t.Parallel()
      var eg errgroup.Group
      var listDatumTimeToFirstDatum, createDatumTimeToFirstDatum float64
      ...
    })
}

Also, I am not sure how reliable it is to collect one timing data point each time the test is run. Nothing guarantees that each goroutine is given the same scheduling latency or the same amount of time on the CPU, so even if CreateDatum is faster than ListDatum, you aren't guaranteed to measure that every time. It's just a recipe for a flaky test. This could be a benchmark (func BenchmarkCreateDatum(b *testing.B) if you want to make sure that you collect enough timing samples to be confident in the result.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed it to a benchmark to avoid flakiness. Running the benchmark a few times confirmed the improved latency in getting the first datum.

require.NoError(t, datumClient.Send(&pps.CreateDatumRequest{Body: &pps.CreateDatumRequest_Start{Start: &pps.StartCreateDatumRequest{Input: input}}}))
n, err := grpcutil.Read[*pps.DatumInfo](datumClient, make([]*pps.DatumInfo, 11))
require.True(t, stream.IsEOS(err))
require.Equal(t, 10, n)
Copy link
Member

Choose a reason for hiding this comment

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

Somewhere, there needs to be a test that actually checks that CreateDatum produces correct result. You run all the code in these tests, which is good, but you aren't checking that the results are correct. Anything that returns 10 copies of DatumInfo (perhaps nil 10 times) will pass the test, which is not good enough for someone to be able to work on this code confidently.

Copy link
Contributor Author

@smalyala smalyala Apr 16, 2024

Choose a reason for hiding this comment

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

I tested correctness for the inputs manually. I'll add some of those to the test suite.

@smalyala smalyala force-pushed the smalyala/createdatum-hierarchical branch from 680a12f to 5750f05 Compare April 16, 2024 23:11
@smalyala smalyala force-pushed the smalyala/createdatum-hierarchical branch from 5750f05 to 7964d98 Compare April 17, 2024 00:03
@smalyala smalyala merged commit 02063a2 into master Apr 22, 2024
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants