Google App Engine Go 内存管理

huangapple go评论75阅读模式
英文:

Google App Engine Go memory management

问题

最近我遇到了一个问题,App Engine会终止我的Go实例,因为它说内存用完了。实例的内存限制设置为128Mb。

然而,我很难弄清楚所有的内存都被分配到哪里了。当我运行以下代码时:

var s runtime.MemStats
runtime.ReadMemStats(&s)
c.Debugf("allocated memory: %d", s.Alloc)

它告诉我当分配的内存达到大约39-40Mb时,我的应用就会被终止,并显示以下错误:

Exceeded soft private memory limit with 135.082 MB after servicing 1 requests total

类似地,当runtime.ReadMemStats(&s)显示我使用了20Mb时,App Engine控制台显示我的实例使用了92Mb。再次进行相同的请求,runtime.ReadMemStats(&s)仍然显示20Mb,而App Engine控制台显示119Mb。

我已经禁用了appstats,但没有帮助。

我的大部分内存都被一个内存缓存使用,我可以减少它以适应限制(或增加实例的内存限制),但是我想知道所有那些内存都被用在哪里。如果有人能够解释一下,或者如何正确地分析App Engine上的内存使用情况,那将非常有帮助。

更新:在本地成功复现了这个问题。

以下是一个示例应用程序,在一个请求中分配一些整数,并在下一个请求中进行垃圾回收:

// Package test implements a simple memory test for Google App Engine.
package test

import (
	"net/http"
	"runtime"

	"appengine"
)

var buffer []int64

func init() {
	http.HandleFunc("/", handler)
}

func handler(w http.ResponseWriter, r *http.Request) {
	var s runtime.MemStats
	c := appengine.NewContext(r)
	if len(buffer) == 0 {
		// Allocate 2^22 integers.
		runtime.ReadMemStats(&s)
		c.Debugf("Memory usage: %d bytes (%d system).", s.Alloc, s.Sys)
		buffer = make([]int64, 4*1024*1024)
		for i, _ := range buffer {
			buffer[i] = int64(i*i)
		}
		runtime.ReadMemStats(&s)
		c.Debugf("Memory usage increased to: %d bytes (%d system).", s.Alloc, s.Sys)
	} else {
		// Remove all references to the slice pointed to by buffer.
		// This should mark it for garbage collection.
		runtime.ReadMemStats(&s)
		c.Debugf("Memory usage: %d bytes (%d system).", s.Alloc, s.Sys)
		buffer = nil
		runtime.GC()
		runtime.ReadMemStats(&s)
		c.Debugf("After GC event: %d bytes (%d system).", s.Alloc, s.Sys)
	}
	w.WriteHeader(http.StatusTeapot)
}

在使用开发服务器运行时:

$ ./go_appengine/dev_appserver.py test

2013/09/16 12:28:28 DEBUG: Memory usage: 833096 bytes (272681032 system).
2013/09/16 12:28:28 DEBUG: Memory usage increased to: 34335216 bytes (308332616 system).
INFO     2013-09-16 12:28:28,884 module.py:593] default: "GET / HTTP/1.1" 418 -
2013/09/16 12:28:29 DEBUG: Memory usage: 34345896 bytes (308332616 system).
2013/09/16 12:28:29 DEBUG: After GC event: 781504 bytes (308332616 system).
INFO     2013-09-16 12:28:29,560 module.py:593] default: "GET / HTTP/1.1" 418 -
2013/09/16 12:28:30 DEBUG: Memory usage: 791616 bytes (308332616 system).
2013/09/16 12:28:30 DEBUG: Memory usage increased to: 34337392 bytes (308332616 system).
INFO     2013-09-16 12:28:30,276 module.py:593] default: "GET / HTTP/1.1" 418 -
2013/09/16 12:28:36 DEBUG: Memory usage: 34347536 bytes (308332616 system).
2013/09/16 12:28:36 DEBUG: After GC event: 783632 bytes (308332616 system).
INFO     2013-09-16 12:28:36,224 module.py:593] default: "GET / HTTP/1.1" 418 -

看起来内存分配和垃圾回收工作正常。然而,查看ps输出,似乎释放内存并没有减少进程的虚拟内存使用量:

$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 381248 56608
$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 676324 57652
$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 750056 57856
$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 750056 57856

似乎运行底层Go实例的Python进程不断增加其虚拟内存,但它从未被释放。似乎在生产服务器上也发生了类似的情况:实例运行时报告的分配内存与内核报告的使用内存不同。

正如@Kluyg所建议的,管理员控制台显示的是系统分配的内存,这是有道理的。

英文:

I've run into a problem recently where App Engine would terminate my Go instance because it says it ran out of memory. The memory limit for the instance is set to 128Mb.

However, I have trouble figuring out where all that memory is being allocated. When I run the following code:

<!-- language: go -->

var s runtime.MemStats
runtime.ReadMemStats(&amp;s)
c.Debugf(&quot;allocated memory: %d&quot;, s.Alloc)

it shows me that when allocated memory reaches about 39-40Mb, my app gets terminated with the following error:

> Exceeded soft private memory limit with 135.082 MB after servicing 1 requests total

Similarly, when runtime.ReadMemStats(&amp;s) indicates that I'm using 20 Mb, the App Engine console shows that my instance is using 92Mb. Redoing the same request, runtime.ReadMemStats(&amp;s) still shows 20Mb, while the App Engine console shows 119Mb.

I have disabled appstats, still no help.

Most of my memory is used up by a memory cache, which I could reduce to fit into the constraints (or increase the memory limit on my instance), but I'd like to know where all that memory is being used. If someone could shed some light on that, or how to correctly profile memory usage on App Engine, that would help a lot.

Update: managed to reproduce this locally.

The following is a sample application that allocates some integers in one request and garbage collects them in the next request:

// Package test implements a simple memory test for Google App Engine.
package test

import (
	&quot;net/http&quot;
	&quot;runtime&quot;

	&quot;appengine&quot;
)

var buffer []int64

func init() {
	http.HandleFunc(&quot;/&quot;, handler)
}

func handler(w http.ResponseWriter, r *http.Request) {
	var s runtime.MemStats
	c := appengine.NewContext(r)
	if len(buffer) == 0 {
		// Allocate 2^22 integers.
		runtime.ReadMemStats(&amp;s)
		c.Debugf(&quot;Memory usage: %d bytes (%d system).&quot;, s.Alloc, s.Sys)
		buffer = make([]int64, 4*1024*1024)
		for i, _ := range buffer {
			buffer[i] = int64(i*i)
		}
		runtime.ReadMemStats(&amp;s)
		c.Debugf(&quot;Memory usage increased to: %d bytes (%d system).&quot;, s.Alloc, s.Sys)
	} else {
		// Remove all references to the slice pointed to by buffer.
		// This should mark it for garbage collection.
		runtime.ReadMemStats(&amp;s)
		c.Debugf(&quot;Memory usage: %d bytes (%d system).&quot;, s.Alloc, s.Sys)
		buffer = nil
		runtime.GC()
		runtime.ReadMemStats(&amp;s)
		c.Debugf(&quot;After GC event: %d bytes (%d system).&quot;, s.Alloc, s.Sys)
	}
	w.WriteHeader(http.StatusTeapot)
}

When run using the development server:

$ ./go_appengine/dev_appserver.py test

2013/09/16 12:28:28 DEBUG: Memory usage: 833096 bytes (272681032 system).
2013/09/16 12:28:28 DEBUG: Memory usage increased to: 34335216 bytes (308332616 system).
INFO     2013-09-16 12:28:28,884 module.py:593] default: &quot;GET / HTTP/1.1&quot; 418 -
2013/09/16 12:28:29 DEBUG: Memory usage: 34345896 bytes (308332616 system).
2013/09/16 12:28:29 DEBUG: After GC event: 781504 bytes (308332616 system).
INFO     2013-09-16 12:28:29,560 module.py:593] default: &quot;GET / HTTP/1.1&quot; 418 -
2013/09/16 12:28:30 DEBUG: Memory usage: 791616 bytes (308332616 system).
2013/09/16 12:28:30 DEBUG: Memory usage increased to: 34337392 bytes (308332616 system).
INFO     2013-09-16 12:28:30,276 module.py:593] default: &quot;GET / HTTP/1.1&quot; 418 -
2013/09/16 12:28:36 DEBUG: Memory usage: 34347536 bytes (308332616 system).
2013/09/16 12:28:36 DEBUG: After GC event: 783632 bytes (308332616 system).
INFO     2013-09-16 12:28:36,224 module.py:593] default: &quot;GET / HTTP/1.1&quot; 418 -

It appears that memory allocation and garbage collection works fine. However, looking at ps output, it seems that deallocating memory does not decrease the virtual memory usage of the process:

$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 381248 56608
$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 676324 57652
$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 750056 57856
$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 750056 57856

It seems that the Python process that runs the underlying Go instance keeps growing its virtual memory, but it never gets freed. It also seems that a similar thing happens on the production server: the allocated memory reported by the instance runtime is different than the used memory reported by the kernel.

As suggested by @Kluyg, it seems that the admin console shows the system allocated memory, which makes sense.

答案1

得分: 5

根据文档,"Alloc"字段显示已分配且仍在使用的字节数。然而,在垃圾回收的语言中,当内存被垃圾回收释放时,它不会立即返回给系统(因为它可能很快被再次请求,所以为什么要麻烦地返回它呢?)。因此,你真正需要监控的是"Sys"字段,它计算从系统获取的字节数。
你可能对这篇文章感兴趣,其中提供了一些关于如何最小化内存使用的见解。

英文:

According to the docs Alloc field shows bytes allocated and still in use. In garbage-collected language, however, when memory is released by GC, it doesn't immediately returned to the system (it can be requested again soon, so why bother returning it back?). So what you really need to monitor is Sys field which counts bytes obtained from the system.
You may be interested in this article with some insights how to minimise memory usage.

huangapple
  • 本文由 发表于 2013年9月16日 19:20:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/18826678.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定