diff options
Diffstat (limited to 'system/docker/patches/0002-Avoid-buffering-to-tempfile-when-pushing-with-V2.patch')
-rw-r--r-- | system/docker/patches/0002-Avoid-buffering-to-tempfile-when-pushing-with-V2.patch | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/system/docker/patches/0002-Avoid-buffering-to-tempfile-when-pushing-with-V2.patch b/system/docker/patches/0002-Avoid-buffering-to-tempfile-when-pushing-with-V2.patch new file mode 100644 index 0000000000..f8d827fb82 --- /dev/null +++ b/system/docker/patches/0002-Avoid-buffering-to-tempfile-when-pushing-with-V2.patch @@ -0,0 +1,220 @@ +From cccc745d93a59fdbb4dd7d7562ee8dd684a00786 Mon Sep 17 00:00:00 2001 +From: Stephen J Day <stephen.day@docker.com> +Date: Tue, 11 Aug 2015 13:47:08 -0700 +Subject: [PATCH 2/4] Avoid buffering to tempfile when pushing with V2 + +The practice of buffering to a tempfile during a pushing contributes massively +to slow V2 push performance perception. The protocol was actually designed to +avoid precalculation, supporting cut-through data push. This means we can +assemble the layer, calculate its digest and push to the remote endpoint, all +at the same time. + +This should increase performance massively on systems with slow disks or IO +bottlenecks. + +Signed-off-by: Stephen J Day <stephen.day@docker.com> +--- + graph/graph.go | 21 -------------- + graph/push_v2.go | 51 ++++++++++++++------------------- + integration-cli/docker_cli_push_test.go | 2 +- + pkg/jsonmessage/jsonmessage.go | 6 ++++ + pkg/jsonmessage/jsonmessage_test.go | 4 +-- + 5 files changed, 31 insertions(+), 53 deletions(-) + +diff --git a/graph/graph.go b/graph/graph.go +index be911b0..885de87 100644 +--- a/graph/graph.go ++++ b/graph/graph.go +@@ -2,7 +2,6 @@ package graph + + import ( + "compress/gzip" +- "crypto/sha256" + "encoding/json" + "errors" + "fmt" +@@ -329,26 +328,6 @@ func (graph *Graph) newTempFile() (*os.File, error) { + return ioutil.TempFile(tmp, "") + } + +-func bufferToFile(f *os.File, src io.Reader) (int64, digest.Digest, error) { +- var ( +- h = sha256.New() +- w = gzip.NewWriter(io.MultiWriter(f, h)) +- ) +- _, err := io.Copy(w, src) +- w.Close() +- if err != nil { +- return 0, "", err +- } +- n, err := f.Seek(0, os.SEEK_CUR) +- if err != nil { +- return 0, "", err +- } +- if _, err := f.Seek(0, 0); err != nil { +- return 0, "", err +- } +- return n, digest.NewDigest("sha256", h), nil +-} +- + // Delete atomically removes an image from the graph. + func (graph *Graph) Delete(name string) error { + id, err := graph.idIndex.Get(name) +diff --git a/graph/push_v2.go b/graph/push_v2.go +index 92d63ca..0ec8cfd 100644 +--- a/graph/push_v2.go ++++ b/graph/push_v2.go +@@ -2,8 +2,8 @@ package graph + + import ( + "fmt" ++ "io" + "io/ioutil" +- "os" + + "github.com/Sirupsen/logrus" + "github.com/docker/distribution" +@@ -199,7 +199,7 @@ func (p *v2Pusher) pushV2Tag(tag string) error { + func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (digest.Digest, error) { + out := p.config.OutStream + +- out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Buffering to Disk", nil)) ++ out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Preparing", nil)) + + image, err := p.graph.Get(img.ID) + if err != nil { +@@ -209,52 +209,45 @@ func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (d + if err != nil { + return "", err + } +- +- tf, err := p.graph.newTempFile() +- if err != nil { +- return "", err +- } +- defer func() { +- tf.Close() +- os.Remove(tf.Name()) +- }() +- +- size, dgst, err := bufferToFile(tf, arch) +- if err != nil { +- return "", err +- } ++ defer arch.Close() + + // Send the layer +- logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size) + layerUpload, err := bs.Create(context.Background()) + if err != nil { + return "", err + } + defer layerUpload.Close() + ++ digester := digest.Canonical.New() ++ tee := io.TeeReader(arch, digester.Hash()) ++ + reader := progressreader.New(progressreader.Config{ +- In: ioutil.NopCloser(tf), ++ In: ioutil.NopCloser(tee), // we'll take care of close here. + Out: out, + Formatter: p.sf, +- Size: int(size), +- NewLines: false, +- ID: stringid.TruncateID(img.ID), +- Action: "Pushing", ++ // TODO(stevvooe): This may cause a size reporting error. Try to get ++ // this from tar-split or elsewhere. The main issue here is that we ++ // don't want to buffer to disk *just* to calculate the size. ++ Size: int(img.Size), ++ ++ NewLines: false, ++ ID: stringid.TruncateID(img.ID), ++ Action: "Pushing", + }) +- n, err := layerUpload.ReadFrom(reader) ++ ++ out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pushing", nil)) ++ nn, err := io.Copy(layerUpload, reader) + if err != nil { + return "", err + } +- if n != size { +- return "", fmt.Errorf("short upload: only wrote %d of %d", n, size) +- } + +- desc := distribution.Descriptor{Digest: dgst} +- if _, err := layerUpload.Commit(context.Background(), desc); err != nil { ++ dgst := digester.Digest() ++ if _, err := layerUpload.Commit(context.Background(), distribution.Descriptor{Digest: dgst}); err != nil { + return "", err + } + +- out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Image successfully pushed", nil)) ++ logrus.Debugf("uploaded layer %s (%s), %d bytes", img.ID, dgst, nn) ++ out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pushed", nil)) + + return dgst, nil + } +diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go +index 111e9f3..c17a0ea 100644 +--- a/integration-cli/docker_cli_push_test.go ++++ b/integration-cli/docker_cli_push_test.go +@@ -108,7 +108,7 @@ func (s *DockerRegistrySuite) TestPushInterrupt(c *check.C) { + } + + // Interrupt push (yes, we have no idea at what point it will get killed). +- time.Sleep(200 * time.Millisecond) ++ time.Sleep(50 * time.Millisecond) // dependent on race condition. + if err := pushCmd.Process.Kill(); err != nil { + c.Fatalf("Failed to kill push process: %v", err) + } +diff --git a/pkg/jsonmessage/jsonmessage.go b/pkg/jsonmessage/jsonmessage.go +index 7db1626..c4b311e 100644 +--- a/pkg/jsonmessage/jsonmessage.go ++++ b/pkg/jsonmessage/jsonmessage.go +@@ -61,8 +61,14 @@ func (p *JSONProgress) String() string { + } + pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) + } ++ + numbersBox = fmt.Sprintf("%8v/%v", current, total) + ++ if p.Current > p.Total { ++ // remove total display if the reported current is wonky. ++ numbersBox = fmt.Sprintf("%8v", current) ++ } ++ + if p.Current > 0 && p.Start > 0 && percentage < 50 { + fromStart := time.Now().UTC().Sub(time.Unix(int64(p.Start), 0)) + perEntry := fromStart / time.Duration(p.Current) +diff --git a/pkg/jsonmessage/jsonmessage_test.go b/pkg/jsonmessage/jsonmessage_test.go +index 2e78fa7..889b0ba 100644 +--- a/pkg/jsonmessage/jsonmessage_test.go ++++ b/pkg/jsonmessage/jsonmessage_test.go +@@ -3,12 +3,12 @@ package jsonmessage + import ( + "bytes" + "fmt" ++ "strings" + "testing" + "time" + + "github.com/docker/docker/pkg/term" + "github.com/docker/docker/pkg/timeutils" +- "strings" + ) + + func TestError(t *testing.T) { +@@ -45,7 +45,7 @@ func TestProgress(t *testing.T) { + } + + // this number can't be negative gh#7136 +- expected = "[==================================================>] 50 B/40 B" ++ expected = "[==================================================>] 50 B" + jp5 := JSONProgress{Current: 50, Total: 40} + if jp5.String() != expected { + t.Fatalf("Expected %q, got %q", expected, jp5.String()) +-- +2.4.3 + |