2010-02-19 2 views
16

현재 내 응용 프로그램은 다음과 같이 memcache에에서 모델을 캐시 :AppEngine Model Memcaching을 수행하는 가장 좋은 방법은 무엇입니까?

memcache.set("somekey", aModel) 

그러나 http://blog.notdot.net/2009/9/Efficient-model-memcaching에서 닉스 '이후 처음 protobuffers로 변환하는 것이 훨씬 더 효율적입니다 제안합니다. 그러나 몇 가지 테스트를 실행 한 후에 크기가 실제로 작다는 것을 알았지 만 실제로는 이 더 느립니다. (~ 10 %).

다른 사람들이 동일한 경험을했거나 잘못된 것이 있습니까?

테스트 결과 : 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

나는 정말로 크고 복잡한 모델을 사용해 보았지만 그 결과는 거의 같았다. –

+0

GAE에는 http://docs.python.org/library/timeit.html이 있습니까? 이것은 더 정확한 결과를 보여 주어야하지만 여전히 - 링크 된 블로그 항목을 읽은 후에 protobuffers와 pickle의 성능 차이가 예상됩니다 - 그리고 이것은 time.time()에 의해 잡혀 있어야합니다 .. –

+0

나는 java appengine을 사용하여이 이론을 테스트하기에는 너무 게으른 편이다. to_protobuf가 아닌 반면에 어딘가에서 결과를 캐싱하는 pickle()인가? 이 기사를 토대로 필자는 protobuf 버전을 사용해도 여전히 피클이 호출됨에 따라 속도가 엄청나게 빨라질 것이라고 기대하지는 않는다. 사용 된 공간은 분명히 훨씬 더 작을 수 있습니다. –

답변

4

Memcache의 호출이 여전히 또는 protobuf을 사용하지 않고 객체를 피클. 피클은 따라서 그들이 Memcache의 시간을 절약하지만, 더 프로세서 시간이 protobuf 변환

일에가, 빨리가

일반 피클 객체가 protobuf + 피클 객체보다 더 큰 매우 간단한 모델을 사용하기 때문에 protobuf 개체입니다

따라서 일반적으로 두 방법 모두 동일하게 작동합니다 ... 그러나

protobuf를 사용해야하는 이유는 Pickle이 오류가있는 반면 모델 버전 간의 변경 사항을 처리 할 수 ​​있다는 것입니다. 이 문제는 언젠가 당신을 물으므로 더 빨리 처리 할 수 ​​있습니다.

+1

몇 가지 좋은 지적이 있지만 모든 진술이 사실이 아니더라도. 코드를 살펴보면 memcache API는 비 문자열만을 피클합니다. 그래서 protobuffed 모델을 가진리스트는 pickled 될 것이고, 하나의 모델은 pickle되지 않을 것입니다. 사실 protobufs 출력은 더 간단하고 작습니다. 내 테스트는 CPU 집약적 인 것이 아니라는 것을 제안합니다. 따라서 원래의 질문입니다. 모델 버전 포인트는 유효하지만 어쨌든 잘못된 캐시 결과를 다루는 방법을 가져야하므로별로 중요하지 않으며 자주 발생하지는 않습니다. –

1

피클과 protobufs 모두 순수 Python으로 구현되어있어 App Engine에서 속도가 느립니다. 나는 대부분의 작업이 C로 끝나기 때문에 str.join과 같은 메서드를 사용하여 내 자신의 간단한 직렬화 코드를 작성하는 것이 더 빠르다는 것을 알았지 만 단순한 데이터 유형에서만 작동합니다.

+0

모델 개체에도이 작업을 수행 했습니까? 나는 당신의 구현을보고 싶을 것이다. –

+0

필자는이 작업을 해왔지만 python2.7을 사용하면 더 빨리 해결할 수 있습니다. – FoxyLad

1

더 빨리 수행하는 한 가지 방법은 모델을 사전으로 변환하고 악의적 인 평가와 마찬가지로주의 깊게 (비) 시리얼 라이저로 원시 eval/repr 함수를 사용하는 것입니다. 외부적인 조치가 없다는 점을 고려할 때 여기에서 안전해야합니다.

다음은 정확히 구현 한 Fake_entity의 예입니다. 먼저 사전을 fake = Fake_entity(entity)으로 작성한 다음 memcache.set(key, fake.serialize())을 통해 데이터를 저장할 수 있습니다. serialize()는 repr의 네이티브 사전 메서드에 대한 간단한 호출이며 필요할 경우 추가 할 수 있습니다 (예 : 문자열의 시작 부분에 식별자를 추가).

다시 가져 오려면 fake = Fake_entity(memcache.get(key))을 사용하면됩니다. Fake_entity 객체는 키가 속성으로 액세스 할 수있는 간단한 사전입니다. referenceProperties가 객체를 가져 오는 대신 키를 제공한다는 점을 제외하면 엔티티 속성에 정상적으로 액세스 할 수 있습니다 (실제로 유용합니다). 또한 실제 엔티티를 fake.get() 또는 더 관심있게 get() 할 수 있으며, 변경 한 다음 fake.put()을 사용하여 저장할 수 있습니다.

쿼리에서 여러 항목을 가져 오는 경우 목록에서 작동하지 않지만 '### FAKE MODEL ENTITY ###'같은 식별자를 구분 기호로 사용하여 조인/분할 기능으로 쉽게 조정할 수 있습니다 . db.Model과 함께 사용하면 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 

속도 테스트를 환영합니다. 다른 접근 방식보다 훨씬 빠르다고 생각합니다. 또한 그동안 모델이 어떻게 든 변경된 경우 위험이 없습니다.

다음은 직렬화 된 위조 된 엔티티의 모습입니다.특정 (생성) 날짜 봐뿐만 아니라 참조 속성 (하위 도메인)을 가지고 :

### 가짜 모델의 ENTITY ###
{ '상태'u'admin ','session_expiry '없음' u'Le Sieur ','modified_by ': 없음,'password_hash ': u'a9993e364706816aba3e25717000000000000000', 'language': u'fr ','created ': datetime.datetime ('이름 ','이름 ' 2010, 7, 18, 21, 50, 11, 750000), 'modified': 없음, 'created_by': 없음, 'email': u' [email protected] ','key ':'agdqZXJLZ2xlcgwLEgVMb2dpbhjmAQw ','session_ref ':'_cls ':'models.Login ','groups ': [],'email___password_hash ': u' [email protected]+a9993e364706816aba3e25717000000000000000', 'subdomain': datastore_types.Key.from_path (u'Subdomain ' , 229L, _app = u'jeregle '),'allowed ': [],'permissions ': []}


개인적으로 단기간에 내 엔티티를 캐시하고 서버가 변경되거나 메모리가 어떤 이유로 플러시되었을 때 데이터 저장소를 페치하기 위해 정적 변수 (memcache보다 빠름)를 사용합니다 (사실 아주 자주 발생합니다) .

관련 문제