app engine datastore benchmark

app engine最難以適應的應該就是data store了, 它是基於big table做出來的一種分散式的資料庫. 因為是分散式, 號稱非常scalable. 不過也因為是分散式, 很多在relational database很直覺的功能都不能用. app engine還有每個request只能執行30秒的限制, 所以一些database更新的行為要分段做, 更增加寫程式的困難度. 寫的時候除了更小心一點, 我還想知道app engine到底寫data store有多慢. (或多快?)



第一個實驗, 我想知道用一個loop來一筆一筆寫, 究竟在timeout前可以寫幾筆? 於是乎我寫了大致上像下面的程式片段.



        size = int(self.request.get('size', default_value='500'))

        i = 0
        try:
            for i in range(10000):
                data = Sample(text1='A' * size, text2='B' * size, text3='C' * size, text4='D' * size)
                data.put()
            self.response.out.write('count: %d, data size = %d' % (i, size * 4 * i))
        except DeadlineExceededError:
            self.response.out.write('count: %d, data size = %d' % (i, size * 4 * i))



一個sample有四個欄位, 每個欄位預設是500bytes, 所以存一個sample是2K. 因為是TextProperty, 所以不會建index. 程式會跑到自然停為止, 跑了五輪, 大致落在400~600間.


第二個實驗, 想要知道Sample大小對寫的速度的影響, 所以把上面的size變數, 設為1000, 2000, 4000. 也就是一筆為變成是4K, 8K, 16K. 結果幾乎沒影響, 跑五輪還是落在400~600間.


第三個實驗, 想要知道Delete的速度. 寫了下面的code來測在timeout前能delete的個數.



        sample = Sample.all()

        i = 0
        try:
            items = sample.fetch(1000)
            for item in items:
                i += 1
                item.delete()
            self.response.out.write('deleted %d items, done!' % i)
        except DeadlineExceededError:
            self.response.out.write('deleted %d items' % i)

想必Delete跟寫是一樣的行為, 差不多也還是落在400~600筆左右. 一秒鐘只能刪十多筆, 比我想像慢得多了.


實驗到這裡, 免費的data store quota居然被我用了超過50%. CPU也用超過了40%. 第一次這麼充份利用這個福利. 但是這些垃圾資料留著也沒用, 要把它刪光光. 一秒鐘只刪十幾筆, 我手應該要reload到抽筋. 想想就試著寫看看task queue, 能不能自動把它刪光光. 就又寫了下面的code.


實驗四


        key = int(self.request.get('key', default_value='0'))
        q = Sample.all()
        last_cursor = memcache.get('sample_cursor')
        if last_cursor:
            q.with_cursor(last_cursor)
        
        items = q.fetch(200)


        cursor = q.cursor()
        memcache.set('sample_cursor', cursor)
        
        i = 0
        start = quota.get_request_cpu_usage()
        for item in items:
            i += 1
            item.delete()
        end = quota.get_request_cpu_usage()
        logging.error('[%d]delete count: %d, cost %d megacycles.' % (key, i, end - start))
        if i == 200:
            logging.error('taskqueue.add() %d', key)
            taskqueue.add(url='/taskdel', params={'key': key+1})
        else:
            logging.error('dont add task')

這段code就有趣一點了, 先做一個query, 然後從memcache裡看有沒有已經存在的cursor, 如果有, 就用那個cursor開始往下抓200筆, 然後一筆一筆delete. 選200筆是因為照之前benchmark的結果, 應該一定不會timeout. 如果確實delete掉了200筆, 代表這個query還沒完, 就把現在的cursor再存到memcache裡, 再queue一個task, 待下次起來再做. 裡面的key只是記錄這是第幾個task, 之後看log可以知道這種task究竟執行了幾次, 再乘200, 再加最後一次的個數, 就是總共刪除的總數了.

最後結果花了67分鐘刪除了94875個entities, 平均一秒刪除23.6個. 看來這種事還是要想個辦法平行處理才能再拉高速度.

實驗五, 在不同的文件裡都有提到, 可以把幾個entities串成一個list, 然後用db.put()一次存, 會再快一點. 又再用下面的片段來測試.


        size = int(self.request.get('size', default_value='500'))
        i = 0
        j = 0
        try:
            for i in range(1,81):
                data_l= []
                for j in range(1,201):
                    data = Sample(text1='A' * size, text2='B' * size, text3='C' * size, text4='D' * size)
                    data_l.append(data)
                db.put(data_l)
                
            self.response.out.write('count: %dx%d, data size = %d' % (i, j, size * 4 * i * j))
        except DeadlineExceededError:
            self.response.out.write('count: %dx%d, data size = %d' % (i, j, size * 4 * i * j))


程式碼裡的81跟201這種魔術數字, 我在測試的時候試過一些不同的組合. 主要是讓它一次生成100筆資料後, 一次存進去. 然後測很多輪, 看測到timeout時存了多少組.
第一次測20x100, 共2000筆, 在timeout前就完成了.
第二次測80x100, 測了幾輪, 在timeout時平均存了32.5組. 共3250筆.
第三次測80x200, 測了幾輪, 在timeout時平均存了18組, 共3600筆.

所以用這個方法, 的確可以大幅度的提升write速度(5x~6x)!

留言

這個網誌中的熱門文章

買車記

怎麼在兩台linux server間用scp而不需打密碼?

Costco退貨真爽快