2010-02-19 13 views
16

Obecnie moja aplikacja buforuje modele w memcache tak:Jaki jest najlepszy sposób na tworzenie memoingu modelu AppEngine?

memcache.set("somekey", aModel) 

Ale po Nicks' w http://blog.notdot.net/2009/9/Efficient-model-memcaching sugeruje, że najpierw przekształcenie go do protobuffers jest dużo bardziej efektywne. Ale po uruchomieniu niektórych testów okazało się, że jest on rzeczywiście mniejszy, ale w rzeczywistości wolniejszy (~ 10%).

Czy inni mają takie samo doświadczenie lub czy robię coś nie tak?

Wyniki badań: http://1.latest.sofatest.appspot.com/?times=1000

import pickle 
import time 
import uuid 

from google.appengine.ext import webapp 
from google.appengine.ext import db 
from google.appengine.ext.webapp import util 
from google.appengine.datastore import entity_pb 
from google.appengine.api import memcache 

class Person(db.Model): 
name = db.StringProperty() 

times = 10000 

class MainHandler(webapp.RequestHandler): 

def get(self): 

    self.response.headers['Content-Type'] = 'text/plain' 

    m = Person(name='Koen Bok') 

    t1 = time.time() 

    for i in xrange(int(self.request.get('times', 1))): 
    key = uuid.uuid4().hex 
    memcache.set(key, m) 
    r = memcache.get(key) 

    self.response.out.write('Pickle took: %.2f' % (time.time() - t1)) 


    t1 = time.time() 

    for i in xrange(int(self.request.get('times', 1))): 
    key = uuid.uuid4().hex 
    memcache.set(key, db.model_to_protobuf(m).Encode()) 
    r = db.model_from_protobuf(entity_pb.EntityProto(memcache.get(key))) 


    self.response.out.write('Proto took: %.2f' % (time.time() - t1)) 


def main(): 
application = webapp.WSGIApplication([('/', MainHandler)], debug=True) 
util.run_wsgi_app(application) 


if __name__ == '__main__': 
main() 
+0

Po prostu wypróbowałem to z naprawdę dużymi i złożonymi modelami, ale wynik był prawie taki sam. –

+0

Może jest http://docs.python.org/library/timeit.html na GAE? Powinno to pokazywać dokładniejsze wyniki, ale mimo to - po przeczytaniu wpisu na blogu, do którego się przyłączyłeś, oczekiwałbym różnicy wielkości między wydajnością protobufferów a piklami - i to powinno być przechwycone przez time.time() mimo wszystko .. –

+0

używając java appengine, więc jestem zbyt leniwy, aby przetestować tę teorię - jest to pikle() buforujące wyniki za kulisami, podczas gdy to_protobuf nie jest? Opierając się na artykule, nie jestem pewien, czy spodziewałbym się wzrostu prędkości o cały rząd wielkości, ponieważ pikle wciąż są wywoływane nawet przy użyciu wersji protobuf. Wykorzystana przestrzeń z pewnością mogłaby być znacznie mniejsza. –

Odpowiedz

4

Wezwanie Memcache nadal pikle obiekt z lub bez użycia Protobuf. Marynata jest szybsza z obiektem Protobuf ponieważ ma bardzo prostego modelu

obiekty Plain marynowane są większe niż Protobuf + ogórek obiektów, stąd one zaoszczędzić czas na Memcache, ale jest więcej czasu procesora w sposób konwersję Protobuf

W związku z tym, ogólnie rzecz biorąc, każda z metod działa na ten sam ... ale

Powodem, dla którego powinieneś używać protobuf, jest to, że może obsłużyć zmiany między wersjami modeli, podczas gdy pikle będą powodowały błąd. Ten problem cię kiedyś ugryzie, więc najlepiej sobie z tym poradzić wcześniej

+1

Mimo pewnych dobrych punktów, ale nie wszystkie stwierdzenia są prawdziwe. Jeśli spojrzysz na kod, to memcache api tylko piknie nie-łańcuchy. Tak więc listy z prototypowymi modelami będą marynowane, a pojedyncze modele nie. Rzeczywiście protobufs jest prostszy i mniejszy, moje testy sugerują, że nie jest to mniej intensywne cpu - stąd oryginalne pytanie. Punkt wersji modelu jest ważny, ale nie jest dla mnie zbyt ważny, ponieważ i tak powinieneś mieć sposób radzenia sobie z nieprawidłowymi wynikami pamięci podręcznej i nie będzie to zbyt często, jak sądzę. –

1

Zarówno pikle jak i protobufy są wolne w App Engine, ponieważ są zaimplementowane w czystym Pythonie. Zauważyłem, że pisanie własnego, prostego kodu serializacji za pomocą metod takich jak str.join wydaje się być szybsze, ponieważ większość prac jest wykonywana w C. Ale to działa tylko dla prostych typów danych.

+0

Czy robiłeś to również dla obiektów modelu? Chciałbym zobaczyć twoją implementację. –

+0

Kiedyś to robiłem, ale python2.7 daje nam cpickle i jest teraz szybszy. – FoxyLad

1

Jednym ze sposobów, aby zrobić to szybciej, jest przekształcenie modelu w słownik i użycie natywnej funkcji eval/repr jako swoich (de) serializerów - z ostrożnością oczywiście, jak zawsze w przypadku złego ewaluacji, ale powinno to być bądź bezpieczny tutaj, ponieważ nie ma zewnętrznego kroku.

Poniżej przykład klasy Fake_entity implementującej dokładnie to. Najpierw utwórz słownik przez fake = Fake_entity(entity), a następnie po prostu zapisz dane za pomocą memcache.set(key, fake.serialize()). Funkcja serialize() jest prostym wywołaniem metody repr z natywnego słownika, z pewnymi dodatkami, jeśli potrzebujesz (np. Dodaj identyfikator na początku łańcucha).

Aby pobrać, użyj po prostu fake = Fake_entity(memcache.get(key)). Obiekt Fake_entity jest prostym słownikiem, którego klucze są również dostępne jako atrybuty. Możesz uzyskać dostęp do właściwości obiektu w normalny sposób, z tym że referenceProperties daje klucze zamiast pobierania obiektu (co jest całkiem przydatne). Możesz również uzyskać() rzeczywistą jednostkę za pomocą fake.get() lub bardziej interesująco, zmień ją, a następnie zapisz za pomocą fake.put().

Nie działa z listami (jeśli pobierasz wiele obiektów z zapytania), ale można je łatwo dostosować za pomocą funkcji łączenia/podziału, używając identyfikatora takiego jak "### FAKE MODEL ENTITY ###" jako separatora . Używaj tylko z db.Model, wymagałoby drobnych korekt dla Expando.

class Fake_entity(dict): 
    def __init__(self, record): 
     # simple case: a string, we eval it to rebuild our fake entity 
     if isinstance(record, basestring): 
      import datetime # <----- put all relevant eval imports here 
      from google.appengine.api import datastore_types 
      self.update(eval(record)) # careful with external sources, eval is evil 
      return None 

     # serious case: we build the instance from the actual entity 
     for prop_name, prop_ref in record.__class__.properties().items(): 
      self[prop_name] = prop_ref.get_value_for_datastore(record) # to avoid fetching entities 
     self['_cls'] = record.__class__.__module__ + '.' + record.__class__.__name__ 
     try: 
      self['key'] = str(record.key()) 
     except Exception: # the key may not exist if the entity has not been stored 
      pass 

    def __getattr__(self, k): 
     return self[k] 

    def __setattr__(self, k, v): 
     self[k] = v 

    def key(self): 
     from google.appengine.ext import db 
     return db.Key(self['key']) 

    def get(self): 
     from google.appengine.ext import db 
     return db.get(self['key']) 

    def put(self): 
     _cls = self.pop('_cls') # gets and removes the class name form the passed arguments 
     # import xxxxxxx ---> put your model imports here if necessary 
     Cls = eval(_cls) # make sure that your models declarations are in the scope here 
     real_entity = Cls(**self) # creates the entity 
     real_entity.put() # self explanatory 
     self['_cls'] = _cls # puts back the class name afterwards 
     return real_entity 

    def serialize(self): 
     return '### FAKE MODEL ENTITY ###\n' + repr(self) 
     # or simply repr, but I use the initial identifier to test and eval directly when getting from memcache 

Chciałbym powitać testy prędkości na tym, zakładam, że jest to znacznie szybciej niż inne podejścia. Dodatkowo, nie masz żadnego ryzyka, jeśli twoje modele w międzyczasie uległy zmianie.

Poniżej przykład tego, jak wygląda serializowana podróbka.Weźmy konkretny przyjrzeć datetime (utworzone), jak również właściwości referencyjnych (subdomeny):

### FAKE MODEL PODMIOT ###
{ 'status': u'admin', 'session_expiry': None " first_name ': u'Louis', 'last_name': u'Le Sieur ',' modified_by ': None,' password_hash ': u'a9993e364706816aba3e25717000000000000000', 'language': u'fr ',' created ': datetime.datetime (2010, 7, 18, 21, 50, 11, 750000), "zmodyfikowane": Brak, "created_by": Brak, "email": u' [email protected] ',' key ':' agdqZXJlZ2xlcgwLEgVMb2dpbhjmAQw ',' session_ref ": Brak," _cls ":" models.Login "," grupy ": []," email___password_hash ": u' [email protected]+a9993e364706816aba3e25717000000000000000 ',' subdomena ': datastore_types.Key.from_path (u'Subdomain' , 229L, _app = u'jeregle ")," dozwolone ": []," uprawnienia ": []}


Osobiście używam również zmiennych statycznych (szybszych niż memcache) do buforowania moich obiektów w krótkim czasie i pobierania magazynu danych, gdy serwer się zmienił lub jego pamięć została wyczyszczona z jakiegoś powodu (co zdarza się dość często w rzeczywistości) .

Powiązane problemy