15

ブログ/ニュース サイトのコードを書いています。メインページには最新の 10 件の記事があり、すべての記事が変更時間の降順でソートされたアーカイブセクションもあります。アーカイブ セクションでは、カーソルに基づいたページネーションを使用し、2 ページ目から結果をキャッシュします。これは、新しい記事が公開された場合、または既存の記事が何らかの理由で下書きになった場合にのみページが変更されるためです。各ページには 10 個の記事があります。そのため、ユーザーが (最初のページではなく) ある番号のアーカイブ ページにアクセスすると、最初にそのページ番号の結果について memcache がチェックされます。ページがそこにない場合、memcache でそのページのカーソルがチェックされ、そのカーソルを使用してデータストアから結果が取得されます。

class archivePage:
    def GET(self, page):
        if not page:
            articles = memcache.get('archivePage')
            if not articles:
                articles = fetchArticles()
                memcache.set('archivePage', articles)
        else:
            if int(page) == 0 or int(page) == 1:
                raise web.seeother('/archive')
            articles = memcache.get('archivePage'+page)
            if not articles:
                pageCursor = memcache.get('ArchivePageMapping'+page)
                if not pageCursor:
                    pageMapping = ArchivePageMapping.query(ArchivePageMapping.page == int(page)).get()
                    pageCursor = pageMapping.cursor
                    memcache.set('ArchivePageMapping'+page, pageCursor)
                articles = fetchArticles(cursor=Cursor(urlsafe=pageCursor))
                memcache.set('archivePage'+page, articles)

新しい記事が作成されるか、既存の記事のステータスが変更される (ドラフト/公開) たびに、アーカイブ ページの結果とカーソルのキャッシュを更新します。記事をデータストアに保存した後に実行します。

class addArticlePage:     
    def POST(self):
        formData = web.input()
        if formData.title and formData.content:
            article = Article(title=formData.title,
                              content=formData.content,
                              status=int(formData.status))
            key = article.put()
            if int(formData.status) == 1:
                cacheArchivePages()
            raise web.seeother('/article/%s' % key.id())

def cacheArchivePages():
    articles, cursor, moreArticles = fetchArticlesPage()
    memcache.set('archivePage', articles)
    pageNumber=2
    while moreArticles:
        pageMapping = ArchivePageMapping.query(ArchivePageMapping.page == pageNumber).get()
        if pageMapping:
            pageMapping.cursor = cursor.urlsafe()
        else:
            pageMapping = ArchivePageMapping(page=pageNumber,
                                            cursor=cursor.urlsafe())
        pageMapping.put()
        memcache.set('ArchivePageMapping'+str(pageNumber), cursor.urlsafe())
        articles, cursor, moreArticles = fetchArticlesPage(cursor=cursor)
        memcache.set('archivePage'+str(pageNumber), articles)
        pageNumber+=1

そして、ここで問題が発生します。キャッシュを更新した後、更新前と同じ結果とアーカイブ ページのカーソルが表示されることがあります (規則はなく、ランダムに発生します)。たとえば、新しい記事を追加します。データストアに保存され、フロント ページとアーカイブの最初のページに表示されます (アーカイブの最初のページはキャッシュされません)。ただし、他のアーカイブ ページは更新されません。cacheArchivePages() 関数をテストしましたが、期待どおりに動作します。データストアに更新を put() してから cacheArchivePages() 関数で fetchArticlesPage() を実行するまでの時間が短すぎたのではないでしょうか? 書き込みトランザクションがまだ終了していないため、古い結果が得られたのではないでしょうか? time.sleep() を使用しようとし、数秒待ってから cacheArchivePages() を呼び出しましたが、その場合、その動作を再現できませんでした。しかし、 time.sleep() は良い考えではないようです。とにかく、その行動の正確な原因と対処方法を知る必要があります。

4

1 に答える 1

24

「結果的に一貫性のあるクエリ」に見舞われる可能性が最も高いです。HR データストアを使用する場合、クエリは少し古いデータを使用する場合があり、put() によって書き込まれたデータがクエリに表示されるまでに時間がかかります (キーまたは ID による get() の場合、そのような遅延はありません)。通常、遅延は秒単位で測定されますが、上限を保証するとは思いません。不幸なネットワーク パーティションに見舞われた場合、数時間かかる可能性があると思います。

最新の書き込みの作成者がクエリ結果を表示しているときにチートすることから、先祖クエリ (独自の制限があります) を使用することまで、あらゆる種類の部分的な解決策があります。キャッシュの有効期間を制限し、書き込みではなく読み取りで更新するだけです。

幸運を!

于 2012-06-16T15:17:23.543 に答える