본문 바로가기
golang

[TIL] Go buffer read

by marble25 2023. 10. 27.

golang 개발하다가 buffer 관련해서 몰랐던 사실을 알게되어 정리해 둔다.

Context

입력으로 이미지를 받고, 해당 이미지에 대한 정보를 불러온 후, 원본 이미지를 그대로 다음 middleware로 전달하는 코드가 있다.

var buffer bytes.Buffer
_, err := io.Copy(&buffer, src)
if err != nil {
	return err
}

img, _, err := image.Decode(buffer)
if err != nil {
	return err
}

// 이후 buffer 그대로 사용

Problem

이미지가 다음 미들웨어로 들어오지 않아서 에러가 발생했다.

확인해 보니 image.Decode(buffer)에서 buffer를 모두 읽어와서 발생한 에러다.

func (b *Buffer) Read(p []byte) (n int, err error) {
	b.lastRead = opInvalid
	if b.empty() {
		// Buffer is empty, reset to recover space.
		b.Reset()
		if len(p) == 0 {
			return 0, nil
		}
		return 0, io.EOF
	}
	n = copy(p, b.buf[b.off:])
	b.off += n
	if n > 0 {
		b.lastRead = opRead
	}
	return n, nil
}

buffer의 Read 함수를 읽어보니 off를 지속적으로 증가시켰다. 그래서 다음번 Read 호출시 더 읽어올 데이터가 없었다.

Fix

1차적으로는 다음과 같이 해결했다.

var buffer bytes.Buffer
_, err := io.Copy(&buffer, src)
if err != nil {
	return err
}

bb := buffer.Bytes()
tmpBuffer := bytes.NewBuffer(bb)

img, _, err := image.Decode(tmpBuffer)
if err != nil {
	return err
}

// 이후 buffer 그대로 사용

하지만 불필요한 변수가 추가로 필요한게 마음에 들지는 않는다.

더 찾아보니 teareader를 이용하는 방법과 readseeker를 이용하는 방법이 존재했다.

reader := strings.NewReader("the quick brown fox jumps over the lazy dog")
buf := &bytes.Buffer{}
tee := io.TeeReader(reader, buf)
reader1, _ := ioutil.ReadAll(tee)
reader2, _ := ioutil.ReadAll(buf)
fmt.Println(string(reader1))
fmt.Println(string(reader2))
reader := strings.NewReader("the quick brown fox jumps over the lazy dog")
reader1, _ := ioutil.ReadAll(reader)
reader.Seek(0, io.SeekStart)
reader2, _ := ioutil.ReadAll(reader)
fmt.Println(string(reader1))
fmt.Println(string(reader2))

하지만 내 케이스에는 더 찾아본 방법들이 크게 장점이 없을 듯 하여 기존 방법을 고수하려고 한다.

다만 한 가지 우려되는 내용은 bytes로 복사하고 이를 이용해서 버퍼를 새로 만드는 과정에서 메모리를 2배 차지하지는 않을까 하는 생각이 든다.

Action Item

다음 번에는 한번 consume된 버퍼는 다시 사용하지 않도록 해야 한다.

부득이 다시 사용할 일이 있으면 복사를 철저히 해야 한다.

'golang' 카테고리의 다른 글

Go google style guide - overview & guide  (1) 2023.11.19
Go garbage collection  (1) 2023.10.30
Go convention 정리  (0) 2023.10.03
golang에서 for loop scoping  (1) 2023.10.02
Go 상속 vs 구성  (1) 2023.10.01