Skip to content

Commit b8df887

Browse files
committed
Improve performance by using bufio with large objects by default
1 parent cc6108a commit b8df887

File tree

4 files changed

+164
-10
lines changed

4 files changed

+164
-10
lines changed

dlo.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ type DynamicLargeObjectCreateFile struct {
1212
// DynamicLargeObjectCreateFile creates a dynamic large object
1313
// returning an object which satisfies io.Writer, io.Seeker, io.Closer
1414
// and io.ReaderFrom. The flags are as passes to the
15-
// LargeObjectCreate method.
15+
// largeObjectCreate method.
1616
func (c *Connection) DynamicLargeObjectCreateFile(opts *LargeObjectOpts) (LargeObjectFile, error) {
17-
lo, err := c.LargeObjectCreate(opts)
17+
lo, err := c.largeObjectCreate(opts)
1818
if err != nil {
1919
return nil, err
2020
}
2121

22-
return &DynamicLargeObjectCreateFile{
22+
return withBuffer(opts, &DynamicLargeObjectCreateFile{
2323
largeObjectCreateFile: *lo,
24-
}, nil
24+
}), nil
2525
}
2626

2727
// DynamicLargeObjectCreate creates or truncates an existing dynamic

largeobjects.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package swift
22

33
import (
4+
"bufio"
45
"bytes"
56
"crypto/rand"
67
"crypto/sha1"
@@ -103,6 +104,7 @@ type LargeObjectOpts struct {
103104
MinChunkSize int64 // Minimum chunk size, automatically set for SLO's based on info
104105
SegmentContainer string // Name of the container to place segments
105106
SegmentPrefix string // Prefix to use for the segments
107+
NoBuffer bool // Prevents using a bufio.Writer to write segments
106108
}
107109

108110
type LargeObjectFile interface {
@@ -113,12 +115,12 @@ type LargeObjectFile interface {
113115
Flush() error
114116
}
115117

116-
// LargeObjectCreate creates a large object at opts.Container, opts.ObjectName.
118+
// largeObjectCreate creates a large object at opts.Container, opts.ObjectName.
117119
//
118120
// opts.Flags can have the following bits set
119121
// os.TRUNC - remove the contents of the large object if it exists
120122
// os.APPEND - write at the end of the large object
121-
func (c *Connection) LargeObjectCreate(opts *LargeObjectOpts) (*largeObjectCreateFile, error) {
123+
func (c *Connection) largeObjectCreate(opts *LargeObjectOpts) (*largeObjectCreateFile, error) {
122124
var (
123125
segmentPath string
124126
segmentContainer string
@@ -398,3 +400,50 @@ func (file *largeObjectCreateFile) writeSegment(buf []byte, writeSegmentIdx int,
398400
}
399401
return &Object{Name: segmentName, Bytes: int64(segmentSize), Hash: headers["Etag"]}, sizeToRead, nil
400402
}
403+
404+
func withBuffer(opts *LargeObjectOpts, lo LargeObjectFile) LargeObjectFile {
405+
if !opts.NoBuffer {
406+
return &bufferedLargeObjectFile{
407+
LargeObjectFile: lo,
408+
bw: bufio.NewWriterSize(lo, int(opts.ChunkSize)),
409+
}
410+
}
411+
return lo
412+
}
413+
414+
type bufferedLargeObjectFile struct {
415+
LargeObjectFile
416+
bw *bufio.Writer
417+
}
418+
419+
func (blo *bufferedLargeObjectFile) Close() error {
420+
err := blo.bw.Flush()
421+
if err != nil {
422+
return err
423+
}
424+
return blo.LargeObjectFile.Close()
425+
}
426+
427+
func (blo *bufferedLargeObjectFile) Write(p []byte) (n int, err error) {
428+
return blo.bw.Write(p)
429+
}
430+
431+
func (blo *bufferedLargeObjectFile) Seek(offset int64, whence int) (int64, error) {
432+
err := blo.bw.Flush()
433+
if err != nil {
434+
return 0, err
435+
}
436+
return blo.LargeObjectFile.Seek(offset, whence)
437+
}
438+
439+
func (blo *bufferedLargeObjectFile) Size() int64 {
440+
return blo.LargeObjectFile.Size() + int64(blo.bw.Buffered())
441+
}
442+
443+
func (blo *bufferedLargeObjectFile) Flush() error {
444+
err := blo.bw.Flush()
445+
if err != nil {
446+
return err
447+
}
448+
return blo.LargeObjectFile.Flush()
449+
}

slo.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type swiftSegment struct {
3535

3636
// StaticLargeObjectCreateFile creates a static large object returning
3737
// an object which satisfies io.Writer, io.Seeker, io.Closer and
38-
// io.ReaderFrom. The flags are as passed to the LargeObjectCreate
38+
// io.ReaderFrom. The flags are as passed to the largeObjectCreate
3939
// method.
4040
func (c *Connection) StaticLargeObjectCreateFile(opts *LargeObjectOpts) (LargeObjectFile, error) {
4141
info, err := c.cachedQueryInfo()
@@ -49,13 +49,13 @@ func (c *Connection) StaticLargeObjectCreateFile(opts *LargeObjectOpts) (LargeOb
4949
if realMinChunkSize > opts.MinChunkSize {
5050
opts.MinChunkSize = realMinChunkSize
5151
}
52-
lo, err := c.LargeObjectCreate(opts)
52+
lo, err := c.largeObjectCreate(opts)
5353
if err != nil {
5454
return nil, err
5555
}
56-
return &StaticLargeObjectCreateFile{
56+
return withBuffer(opts, &StaticLargeObjectCreateFile{
5757
largeObjectCreateFile: *lo,
58-
}, nil
58+
}), nil
5959
}
6060

6161
// StaticLargeObjectCreate creates or truncates an existing static

swift_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,6 +1987,7 @@ func TestDLOSegmentation(t *testing.T) {
19871987
ObjectName: OBJECT,
19881988
ContentType: "image/jpeg",
19891989
ChunkSize: 6,
1990+
NoBuffer: true,
19901991
}
19911992

19921993
testSegmentation(t, func() swift.LargeObjectFile {
@@ -2032,6 +2033,57 @@ func TestDLOSegmentation(t *testing.T) {
20322033
})
20332034
}
20342035

2036+
func TestDLOSegmentationBuffered(t *testing.T) {
2037+
opts := swift.LargeObjectOpts{
2038+
Container: CONTAINER,
2039+
ObjectName: OBJECT,
2040+
ContentType: "image/jpeg",
2041+
ChunkSize: 6,
2042+
}
2043+
2044+
testSegmentation(t, func() swift.LargeObjectFile {
2045+
out, err := c.DynamicLargeObjectCreate(&opts)
2046+
if err != nil {
2047+
t.Fatal(err)
2048+
}
2049+
return out
2050+
}, []segmentTest{
2051+
{
2052+
writes: []string{"0", "1", "2", "3", "4", "5", "6", "7", "8"},
2053+
expectedSegs: []string{"012345", "678"},
2054+
expectedValue: "012345678",
2055+
},
2056+
{
2057+
writes: []string{"012345", "012345"},
2058+
expectedSegs: []string{"012345", "012345"},
2059+
expectedValue: "012345012345",
2060+
},
2061+
{
2062+
writes: []string{"0123456", "0123456"},
2063+
expectedSegs: []string{"012345", "6", "012345", "6"},
2064+
expectedValue: "01234560123456",
2065+
},
2066+
{
2067+
writes: []string{"0123456", "0123456"},
2068+
seeks: []int{-4, 0},
2069+
expectedSegs: []string{"012012", "3456"},
2070+
expectedValue: "0120123456",
2071+
},
2072+
{
2073+
writes: []string{"0123456", "0123456", "abcde"},
2074+
seeks: []int{0, -11, 0},
2075+
expectedSegs: []string{"012abc", "d", "e12345", "6"},
2076+
expectedValue: "012abcde123456",
2077+
},
2078+
{
2079+
writes: []string{"0123456", "ab"},
2080+
seeks: []int{-4, 0},
2081+
expectedSegs: []string{"012ab5", "6"},
2082+
expectedValue: "012ab56",
2083+
},
2084+
})
2085+
}
2086+
20352087
func TestSLOCreate(t *testing.T) {
20362088
headers := swift.Headers{}
20372089
err := c.ContainerCreate(SEGMENTS_CONTAINER, headers)
@@ -2241,6 +2293,7 @@ func TestSLOMinChunkSize(t *testing.T) {
22412293
ContentType: "image/jpeg",
22422294
ChunkSize: 6,
22432295
MinChunkSize: 0,
2296+
NoBuffer: true,
22442297
}
22452298

22462299
testSLOSegmentation(t, func() swift.LargeObjectFile {
@@ -2260,6 +2313,7 @@ func TestSLOSegmentation(t *testing.T) {
22602313
ContentType: "image/jpeg",
22612314
ChunkSize: 6,
22622315
MinChunkSize: 4,
2316+
NoBuffer: true,
22632317
}
22642318
testSLOSegmentation(t, func() swift.LargeObjectFile {
22652319
out, err := c.StaticLargeObjectCreate(&opts)
@@ -2270,6 +2324,57 @@ func TestSLOSegmentation(t *testing.T) {
22702324
})
22712325
}
22722326

2327+
func TestSLOSegmentationBuffered(t *testing.T) {
2328+
opts := swift.LargeObjectOpts{
2329+
Container: CONTAINER,
2330+
ObjectName: OBJECT,
2331+
ContentType: "image/jpeg",
2332+
ChunkSize: 6,
2333+
MinChunkSize: 4,
2334+
}
2335+
testSegmentation(t, func() swift.LargeObjectFile {
2336+
out, err := c.StaticLargeObjectCreate(&opts)
2337+
if err != nil {
2338+
t.Fatal(err)
2339+
}
2340+
return out
2341+
}, []segmentTest{
2342+
{
2343+
writes: []string{"0", "1", "2", "3", "4", "5", "6", "7", "8"},
2344+
expectedSegs: []string{"012345", "678"},
2345+
expectedValue: "012345678",
2346+
},
2347+
{
2348+
writes: []string{"012345", "012345"},
2349+
expectedSegs: []string{"012345", "012345"},
2350+
expectedValue: "012345012345",
2351+
},
2352+
{
2353+
writes: []string{"0123456", "0123456"},
2354+
expectedSegs: []string{"012345", "601234", "56"},
2355+
expectedValue: "01234560123456",
2356+
},
2357+
{
2358+
writes: []string{"0123456", "0123456"},
2359+
seeks: []int{-4, 0},
2360+
expectedSegs: []string{"012012", "3456"},
2361+
expectedValue: "0120123456",
2362+
},
2363+
{
2364+
writes: []string{"0123456", "0123456", "abcde"},
2365+
seeks: []int{0, -11, 0},
2366+
expectedSegs: []string{"012abc", "de1234", "56"},
2367+
expectedValue: "012abcde123456",
2368+
},
2369+
{
2370+
writes: []string{"0123456", "ab"},
2371+
seeks: []int{-4, 0},
2372+
expectedSegs: []string{"012ab5", "6"},
2373+
expectedValue: "012ab56",
2374+
},
2375+
})
2376+
}
2377+
22732378
func testSLOSegmentation(t *testing.T, createObj func() swift.LargeObjectFile) {
22742379
testCases := []segmentTest{
22752380
{

0 commit comments

Comments
 (0)