Skip to content

storage: concurrent disk image write. #2395

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
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
30 changes: 28 additions & 2 deletions pkg/storage/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
log "github.com/rs/zerolog/log"
"github.com/threefoldtech/zos/pkg"
"github.com/threefoldtech/zos/pkg/gridtypes"
"golang.org/x/sync/errgroup"
)

const (
Expand Down Expand Up @@ -145,10 +146,35 @@ func (s *Module) DiskWrite(name string, image string) error {
return fmt.Errorf("image size is bigger than disk")
}

_, err = io.Copy(file, source)
if err != nil {
// writing the image concurrently to speedup the previous sequential write.
// the sequential write is slow because the data source is from the remote server.
var (
// use errgroup because there is no point in continuing if one of the goroutines failed
group = new(errgroup.Group)
Copy link
Collaborator

Choose a reason for hiding this comment

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

should be usable on the zero value or with a context if required

Copy link
Member Author

Choose a reason for hiding this comment

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

should be usable on the zero value

could you elaborate more on this?

or with a context if required

not possible for now:

  • the func doesn't have context
  • io.Copy itself doesn't have context.

I think make it context aware will need separate ticket

Copy link
Member Author

Choose a reason for hiding this comment

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

I think make it context aware will need separate ticket

DiskWrite handled by storaged, called by provisiond through zbus.
So, plain Go context will not really work here.
But of course we can start with context inside storaged.

As a side note, i noticed that DiskWrite keep going even tho provisiond already cancel the deployment/contract.
So, we already have real cancellation issue over zbus.

Copy link
Collaborator

Choose a reason for hiding this comment

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

can you please mention the cancellation problem as a known issue on the top of PR to be easily found later on

Copy link
Member Author

Choose a reason for hiding this comment

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

can you please mention the cancellation problem as a known issue on the top of PR to be easily found later on

Maybe create separate issue because it is not really specific to this PR

concurrentNum int = 5
imgSize int64 = imgStat.Size()
chunkSize = imgSize / int64(concurrentNum)
)

log.Info().Int("concurrentNum", concurrentNum).Msg("writing image concurrently")
for i := 0; i < concurrentNum; i++ {
index := i
group.Go(func() error {
start := chunkSize * int64(index)
len := chunkSize
if index == concurrentNum-1 { //last chunk
len = imgSize - start
}
wr := io.NewOffsetWriter(file, start)
rd := io.NewSectionReader(source, start, len)
_, err = io.Copy(wr, rd)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe using io.CopyBuffer could avoid temporal buffers https://pkg.go.dev/io#CopyBuffer

Copy link
Collaborator

Choose a reason for hiding this comment

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

also, no need to call file.Sync?

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe using io.CopyBuffer could avoid temporal buffers https://pkg.go.dev/io#CopyBuffer

the difference between io.Copy and CopyBuffer is : CopyBuffer lets us choose our own buffer.
io.Copy will also use buffer.
The slowness here is because of downloading the file from the hub, so i don't think that the buffer will make big differences here.

Copy link
Member Author

Choose a reason for hiding this comment

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

also, no need to call file.Sync?

i don't think so, we're copying to rfs anyway.
CMIIW

Copy link
Collaborator

Choose a reason for hiding this comment

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

io.CopyBuffer uses a buffer your provide instead of io.Copy keeping allocating them during every call (it's still an io to save imo)

Copy link
Member Author

Choose a reason for hiding this comment

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

instead of io.Copy keeping allocating them during every call

No, the buffer used by io.Copy also only allocated once.
See https://github.com/golang/go/blob/master/src/io/io.go#L417-L427

if what you mean is creating global buffer, outside the DiskWrite func then it would be lot of works for very small benefit.

return err
})
}
if err := group.Wait(); err != nil {
return errors.Wrap(err, "failed to write disk image")
}
log.Info().Msg("writing image finished")

return nil
}
Expand Down
Loading