2013-02-14 28 views
6

Hiện tại chúng tôi đang xây dựng một dịch vụ HTTP trung tâm nhỏ và đơn giản để ánh xạ "danh tính bên ngoài" (như id facebook) thành "id nội bộ (uu)", duy nhất trên tất cả các dịch vụ của chúng tôi để trợ giúp phân tích.Thời gian phản hồi nào có thể được mong đợi từ GAE/NDB?

Nguyên mẫu đầu tiên trong "ngăn xếp của chúng tôi" (bình + postgresql) được thực hiện trong vòng một ngày. Nhưng vì chúng tôi muốn dịch vụ (gần như) không bao giờ thất bại và tự động mở rộng quy mô, chúng tôi đã quyết định sử dụng Google App Engine.

Sau một tuần đọc & cố gắng & benchmarking câu hỏi này xuất hiện:

phản ứng gì lần được coi là "bình thường" trên App Engine (với NDB)?

Chúng tôi đang nhận được thời gian đáp ứng được một cách nhất quán 500ms trên trung bìnhcao hơn 1s trong 90percentile.

Tôi đã đính kèm một phiên bản rút gọn của mã của chúng tôi bên dưới, hy vọng ai đó có thể chỉ ra lỗ hổng hiển nhiên. Chúng tôi thực sự thích việc tự động tính toán và lưu trữ phân phối, nhưng chúng tôi không thể tưởng tượng 500ms thực sự là hiệu suất mong đợi trong trường hợp của chúng tôi. Nguyên mẫu dựa trên sql trả lời nhanh hơn nhiều (nhất quán), được lưu trữ trên một dyno Heroku duy nhất sử dụng postgresql miễn phí, ít bộ nhớ cache (ngay cả với ORM).

Chúng tôi đã thử cả hai biến thể đồng bộ và không đồng bộ của mã bên dưới và xem xét hồ sơ appstats. Nó luôn luôn là các cuộc gọi RPC (cả memcache và kho dữ liệu) mất rất lâu (50ms-100ms), làm tồi tệ hơn bởi thực tế là luôn có nhiều cuộc gọi (ví dụ: mc.get() + ds.get() + ds.set () trên một viết). Chúng tôi cũng đã cố gắng trì hoãn càng nhiều càng tốt vào hàng đợi công việc, mà không có mức tăng đáng chú ý.

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) 

Đối với đầy đủ vì các (gần như mặc định) 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 

Trả lời

3

Theo kinh nghiệm của tôi, hiệu suất RPC dao động bởi thứ tự độ lớn giữa 5ms-100ms cho một get kho dữ liệu. Tôi nghi ngờ nó liên quan đến tải trung tâm dữ liệu GAE. Đôi khi nó trở nên tốt hơn, đôi khi nó trở nên tồi tệ hơn.

Thao tác của bạn trông rất đơn giản. Tôi hy vọng rằng với 3 yêu cầu, nó sẽ mất khoảng 20ms, nhưng nó có thể lên đến 300ms. Tuy nhiên, mức trung bình 500ms vẫn rất cao.

ndb lưu bộ nhớ đệm cục bộ khi tìm nạp đối tượng theo ID. Điều đó sẽ khởi động nếu bạn đang truy cập cùng một người dùng và các yêu cầu đó sẽ nhanh hơn nhiều.

Tôi cho rằng bạn đang thực hiện thử nghiệm perf trên sản xuất chứ không phải dev_appserver. Hiệu suất dev_appserver không phải là đại diện.

Không chắc bạn đã thử nghiệm bao nhiêu lần, nhưng bạn có thể muốn thử số lớn hơn để xem liệu 500ms có thực sự là mức trung bình của bạn không.

Khi bạn bị chặn đối với các cuộc gọi RPC đơn giản, không quá tối ưu bạn có thể làm.

+0

Yepp, bạn nói đúng về hiệu suất của dev_appserver (sqlite trên ssd ...), vì vậy chúng tôi thử nghiệm về sản xuất (thậm chí tài khoản trả tiền). Liên quan đến việc lặp lại, chúng tôi thường giữ cho các thử nghiệm chạy trong khoảng 5 phút. Chúng tôi cũng cố gắng đảm bảo mỗi lần chạy có số lần truy cập/số lần so sánh (bằng cách dọn sạch kho dữ liệu/memcache giữa các lần chạy hoặc bằng cách chơi xung quanh với phạm vi 'provider_user_id'). – selkie

+2

Một lưu ý: nếu bạn đang chạy một điểm chuẩn lớn, bạn phải tăng lưu lượng truy cập của bạn dần dần (nói 5-10 phút) và sau đó duy trì nó trong một thời gian (5-10 phút) để đo hiệu ứng thực tế. App Engine sẽ không quay lên các trường hợp cần thiết ngay lập tức khi tải của bạn đi từ 0 đến 100; có một "thống đốc" trong quá trình này để tránh mất ổn định. –

+0

Tôi vừa đọc về hành vi "một lần mỗi giây cho mỗi nhóm thực thể" của HRD. Trong đoạn mã trên, anh ấy có giải thích được vấn đề của chúng ta không? Chỉ có một số ít các nhà cung cấp (chủ yếu là facebook), và _Identity_ có _Provider_ với tư cách là một phụ huynh, biến chúng thành một nhóm _entity_? – selkie

1

Khoảnh khắc đầu tiên tôi thấy: bạn có thực sự cần một giao dịch trên mọi yêu cầu không?

Tôi tin rằng trừ khi hầu hết các yêu cầu của bạn tạo ra các thực thể mới thì tốt hơn nên làm .get_by_id() bên ngoài giao dịch.Và nếu thực thể không tìm thấy thì bắt đầu giao dịch hoặc thậm chí trì hoãn việc tạo ra thực thể tốt hơn.

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() 

Điều đó sẽ cung cấp thời gian phản hồi tốt hơn cho các yêu cầu của người dùng.

Tối ưu hóa thứ hai và duy nhất tôi thấy là sử dụng ndb.put_multi() để giảm thiểu các cuộc gọi RPC.

P.S. Không chắc chắn 100% nhưng bạn có thể thử vô hiệu hóa đa luồng (luồng thư: không) để nhận thời gian phản hồi ổn định hơn.

Các vấn đề liên quan