【解决了一个小问题】错误配置 s3 sdk 的 part size 导致 oom

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


某服务上线后,运行一段程序崩溃,一开始以为是panic,为所有的go出来的协程都加上了recover()处理,仍然未找到崩溃原因。
更奇怪的是,在 aws 云中,程序崩溃后,其对应的容器一直无法拉起。
最后在 SRE 的帮助下找到了最终的崩溃信息:

    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
      Started:      Fri, 06 Sep 2024 18:14:51 +0800
      Finished:     Fri, 06 Sep 2024 18:15:48 +0800

继续观察崩溃时候的golang runtime 上报:

容器最大内存为 16 gb,可见确实是某种原因导致了内存分配太多。

为了找到程序崩溃的原因,开启了程序的 pprof http 端口:

	wget -O heap_profile.out "http://[xxxxx]:18888/debug/pprof/heap"
	scp myserver:/home/ahfuzhang/temp/2024-09-09/heap_profile.out ./
	go tool pprof -http=:8090 heap_profile.out

打开浏览器,看到如下信息:

通过 top 也发现 s3 sdk 里面内存分配得很离谱:

通过源码分析:

// vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/pool.go
func (p *maxSlicePool) newSlice() *[]byte { 
	bs := make([]byte, p.sliceSize)  // 这里的 sliceSize 太大了
	return &bs
}

func newMaxSlicePool(sliceSize int64) *maxSlicePool {
	p := &maxSlicePool{sliceSize: sliceSize}  // sliceSize 是调用端传进来的
	p.allocator = p.newSlice

	return p
}

func newUploader(client s3iface.S3API, options ...func(*Uploader)) *Uploader {
	u := &Uploader{
		S3:                client,
		PartSize:          DefaultUploadPartSize,
		Concurrency:       DefaultUploadConcurrency,
		LeavePartsOnError: false,
		MaxUploadParts:    MaxUploadParts,
		BufferProvider:    defaultUploadBufferProvider(),
	}

	for _, option := range options {
		option(u)
	}

	u.partPool = newByteSlicePool(u.PartSize)  // 原来是 part size 决定了内部缓冲区的大小

	return u
}

最后,修改 part size 后问题解决:

xx := s3manager.NewUploader(session.Must(sess, err), func(u *s3manager.Uploader) {
			u.Concurrency = types.UploadConcurrency
			u.PartSize = partSize // 默认 20mb,不分片
		})

引入这个问题是因为某次讨论中认为分片没必要,直接将默认分片从 5mb 修改为 3GB
修改后未确认 s3 sdk 内部的逻辑。

posted on 2024-09-09 12:45  ahfuzhang  阅读(19)  评论(0编辑  收藏  举报