我们目前正在构建一个小而简单的中央HTTP服务,将“外部身份”(如facebook id)映射到“内部(uu)id”,这是我们所有服务中唯一的,以帮助进行分析。
“我们的堆栈”(flask + postgresql)中的第一个原型在一天内完成。但由于我们希望服务(几乎)永远不会失败并自动扩展,我们决定使用Google App Engine。
经过一周的阅读和尝试与基准测试后,这个问题出现了:
App Engine(使用NDB)的响应时间被视为“正常”?
我们的响应时间一直平均<500>以上 远远超过90%中的1s。
我在下面附上了我们代码的精简版本,希望有人可以指出明显的缺陷。我们非常喜欢自动缩放和分布式存储,但我们无法想象500ms在我们的情况下确实是预期的性能。基于sql的原型响应速度更快(一致),使用免费的无缓存postgresql(即使使用ORM)托管在一个Heroku dyno上。
我们尝试了下面代码的同步和异步变体,并查看了appstats配置文件。它总是RPC调用(memcache和数据存储区)需要很长时间(50ms-100ms),因为总是存在多个调用(例如.mc.get()+ ds.get()+ ds.set( )写一个)。我们还尝试尽可能地推迟到任务队列,没有明显的收益。
import json
import uuid
from google.appengine.ext import ndb
import webapp2
from webapp2_extras.routes import RedirectRoute
def _parse_request(request):
if request.content_type == 'application/json':
try:
body_json = json.loads(request.body)
provider_name = body_json.get('provider_name', None)
provider_user_id = body_json.get('provider_user_id', None)
except ValueError:
return webapp2.abort(400, detail='invalid json')
else:
provider_name = request.params.get('provider_name', None)
provider_user_id = request.params.get('provider_user_id', None)
return provider_name, provider_user_id
class Provider(ndb.Model):
name = ndb.StringProperty(required=True)
class Identity(ndb.Model):
user = ndb.KeyProperty(kind='GlobalUser')
class GlobalUser(ndb.Model):
uuid = ndb.StringProperty(required=True)
@property
def identities(self):
return Identity.query(Identity.user==self.key).fetch()
class ResolveHandler(webapp2.RequestHandler):
@ndb.toplevel
def post(self):
provider_name, provider_user_id = _parse_request(self.request)
if not provider_name or not provider_user_id:
return self.abort(400, detail='missing provider_name and/or provider_user_id')
identity = ndb.Key(Provider, provider_name, Identity, provider_user_id).get()
if identity:
user_uuid = identity.user.id()
else:
user_uuid = uuid.uuid4().hex
GlobalUser(
id=user_uuid,
uuid=user_uuid
).put_async()
Identity(
parent=ndb.Key(Provider, provider_name),
id=provider_user_id,
user=ndb.Key(GlobalUser, user_uuid)
).put_async()
return webapp2.Response(
status='200 OK',
content_type='application/json',
body = json.dumps({
'provider_name' : provider_name,
'provider_user_id' : provider_user_id,
'uuid' : user_uuid
})
)
app = webapp2.WSGIApplication([
RedirectRoute('/v1/resolve', ResolveHandler, 'resolve', strict_slash=True)
], debug=False)
为了完整起见(几乎是默认的)app.yaml
application: GAE_APP_IDENTIFIER
version: 1
runtime: python27
api_version: 1
threadsafe: yes
handlers:
- url: .*
script: main.app
libraries:
- name: webapp2
version: 2.5.2
- name: webob
version: 1.2.3
inbound_services:
- warmup
答案 0 :(得分:3)
根据我的经验,RPC性能波动数量级,数据存储区获得的时间间隔为5ms-100ms。我怀疑它与GAE数据中心负载有关。有时情况会好转,有时会变得更糟。
您的操作看起来非常简单。我希望有3个请求,它应该需要大约20ms,但它可能高达300ms。持续平均500毫秒听起来非常高。
ndb在按ID提取对象时执行本地缓存。如果您访问相同的用户,那应该会启动,这些请求应该更快。
我假设您正在对生产进行性能测试而不是dev_appserver。 dev_appserver性能不具代表性。
不确定您测试了多少次迭代,但您可能想尝试更大的数字来查看500ms是否真的是您的平均值。
当您在简单的RPC调用中被阻止时,您无法进行太多优化。
答案 1 :(得分:1)
我看到的第一个显而易见的时刻:你真的需要每次请求都有交易吗?
我相信除非您的大多数请求都创建新实体,否则最好在事务之外执行.get_by_id()。如果找不到实体,则开始交易,甚至更好地推迟实体的创建。
def request_handler(key, data):
entity = key.get()
if entity:
return 'ok'
else:
defer(_deferred_create, key, data)
return 'ok'
def _deferred_create(key, data):
@ndb.transactional
def _tx():
entity = key.get()
if not entity:
entity = CreateEntity(data)
entity.put()
_tx()
这应该为面向用户的请求提供更好的响应时间。
我看到的第二个也是唯一的优化是使用ndb.put_multi()来最小化RPC调用。
P.S。不是100%肯定,但您可以尝试禁用多线程( threadsave:no )以获得更稳定的响应时间。