2013-02-14 10 views
6

हम वर्तमान में एक छोटी और सरल केंद्रीय HTTP सेवा का निर्माण कर रहे हैं जो "बाहरी पहचान" (एक फेसबुक आईडी की तरह) को "आंतरिक (यूयू) आईडी" पर मैप करता है, जो विश्लेषिकी में सहायता के लिए हमारी सभी सेवाओं में अद्वितीय है।जीएई/एनडीबी से किस प्रतिक्रिया समय की उम्मीद की जा सकती है?

"हमारे स्टैक" (फ्लास्क + पोस्टग्रेस्क्ल) में पहला प्रोटोटाइप एक दिन के भीतर किया गया था। लेकिन चूंकि हम चाहते हैं कि सेवा (लगभग) कभी विफल न हो और स्वचालित रूप से स्केल न करें, हमने Google App Engine का उपयोग करने का निर्णय लिया।

& पढ़ने & इस सवाल बेंचमार्किंग की कोशिश कर के एक सप्ताह के बाद उभर रहे हैं:

क्या प्रतिक्रिया समय (NDB) के साथ अनुप्रयोग इंजन पर विचार कर रहे हैं "सामान्य"?

हम प्रतिक्रिया समय है कि अच्छी तरह 90percentile में 1s ऊपर पर औसत और लगातार ऊपर 500ms हैं हो रही है।

मैंने नीचे दिए गए हमारे कोड का एक अलग संस्करण संलग्न किया है, उम्मीद है कि कोई स्पष्ट दोष बता सकता है। हम वास्तव में ऑटोस्केलिंग और वितरित भंडारण पसंद करते हैं, लेकिन हम कल्पना नहीं कर सकते कि 500ms वास्तव में हमारे मामले में अपेक्षित प्रदर्शन है। एसक्यूएल आधारित प्रोटोटाइप ने मुफ्त, कैश-कम पोस्टग्रेस्क्ल (यहां तक ​​कि एक ओआरएम के साथ) का उपयोग करके एक ही हरोकू डिनो पर होस्ट किया, जो बहुत तेज (लगातार) प्रतिक्रिया देता है।

हमने नीचे दिए गए कोड के सिंक्रोनस और एसिंक्रोनस वेरिएंट दोनों की कोशिश की और ऐपस्टैट प्रोफ़ाइल को देखा। यह हमेशा आरपीसी कॉल (मेमचेचे और डेटास्टोर दोनों) है जो बहुत लंबा (50 एमएमएस -100 एमएमएस) लेते हैं, इस तथ्य से भी बदतर बनाते हैं कि हमेशा कई कॉल होते हैं (उदाहरण के लिए 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 

उत्तर

3

मेरे अनुभव में, RPC प्रदर्शन एक डेटासंग्रह प्राप्त के लिए, परिमाण के आदेश से उतार चढ़ाव होता रहता 5ms-100ms के बीच। मुझे संदेह है कि यह जीएई डेटासेंटर लोड से संबंधित है। कभी-कभी यह बेहतर हो जाता है, कभी-कभी यह और भी खराब हो जाता है।

आपका ऑपरेशन बहुत आसान दिखता है। मुझे उम्मीद है कि 3 अनुरोधों के साथ, इसमें लगभग 20 मिनट लगेंगे, लेकिन यह 300ms तक हो सकता है। यद्यपि 500 ​​मिमी का निरंतर औसत बहुत अधिक लगता है।

एनडीबी आईडी द्वारा वस्तुओं को प्राप्त करते समय स्थानीय कैशिंग करता है। यदि आप एक ही उपयोगकर्ता तक पहुंच रहे हैं, तो उसमें लात मारना चाहिए, और उन अनुरोधों को बहुत तेज होना चाहिए।

मुझे लगता है कि आप उत्पादन पर perf परीक्षण कर रहे हैं और dev_appserver नहीं। dev_appserver प्रदर्शन प्रतिनिधि नहीं है।

सुनिश्चित नहीं है कि आपने कितने पुनरावृत्तियों का परीक्षण किया है, लेकिन आप यह देखने के लिए बड़ी संख्या में प्रयास करना चाहेंगे कि 500ms वास्तव में आपका औसत है या नहीं।

जब आप सरल आरपीसी कॉल पर अवरुद्ध होते हैं, तो आप ऐसा करने के लिए बहुत अनुकूल नहीं होते हैं।

+0

याप, आप dev_appserver के प्रदर्शन (ssd पर sqlite ...) के बारे में सही हैं, इसलिए हम उत्पादन (यहां तक ​​कि भुगतान किए गए खाते) पर भी परीक्षण करते हैं। पुनरावृत्तियों के बारे में हम आमतौर पर परीक्षण लगभग 5 मिनट तक चलते रहते हैं। हम यह भी सुनिश्चित करने का प्रयास करते हैं कि प्रत्येक रन में हिट/मिस की तुलनीय मात्रा होती है (रन के बीच डेटास्टोर/मेमकैच खाली करके या 'provider_user_id' श्रेणी के साथ खेलकर)। – selkie

+2

एक नोट: यदि आप एक बड़ा बेंचमार्क चला रहे हैं, तो आपको धीरे-धीरे अपने ट्रैफिक को स्पिन करना होगा (5-10 मिनट कहें) और फिर यथार्थवादी प्रभावों को मापने के लिए इसे थोड़ी देर (अन्य 5-10 मिनट) तक बनाए रखें। जब आपका लोड 0 से 100 हो जाता है तो ऐप इंजन तत्काल आवश्यक उदाहरणों को नहीं बढ़ाएगा; अस्थिरता से बचने के लिए इस प्रक्रिया पर "गवर्नर" है। –

+0

मैंने अभी एचआरडी के "एक प्रति प्रति समूह समूह" व्यवहार के बारे में पढ़ा है। उपरोक्त कोड में, क्या वह हमारे मुद्दों की व्याख्या नहीं करेगा? वहां केवल कुछ मुट्ठी भर प्रदाता (ज्यादातर फेसबुक) हैं, और _Identity_ में माता-पिता के रूप में _Provider_ है, जिससे उन्हें _entity group_ बना दिया जाता है? – selkie

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() का उपयोग करना है।

पीएस 100% निश्चित नहीं है लेकिन आप अधिक स्थिर प्रतिक्रिया समय प्राप्त करने के लिए मल्टीथ्रेडिंग (थ्रेडसेव: नहीं) अक्षम करने का प्रयास कर सकते हैं।

संबंधित मुद्दे