Jak działa buforowanie oparte na kluczach?

10

Niedawno przeczytałem artykuł na blogu 37Signals i zastanawiam się, jak to jest, że dostają klucz pamięci podręcznej.

Dobrze jest mieć klucz pamięci podręcznej, który zawiera znacznik czasu obiektu (oznacza to, że po zaktualizowaniu obiektu pamięć podręczna zostanie unieważniona); ale jak następnie użyć klucza pamięci podręcznej w szablonie bez powodowania trafienia DB dla samego obiektu, który próbujesz pobrać z pamięci podręcznej.

W szczególności, w jaki sposób wpływa to na relacje One to Many, w których na przykład renderujesz komentarze do posta.

Przykład w Django:

{% for comment in post.comments.all %}
   {% cache comment.pk comment.modified %}
     <p>{{ post.body }}</p>
   {% endcache %}
{% endfor %}

Czy buforowanie w Railsach jest inne niż na przykład prośby do memcached (wiem, że konwertują twój klucz pamięci podręcznej na coś innego). Czy buforują również klucz pamięci podręcznej?

Dominic Santos
źródło
Spójrz na rossp.org/blog/2012/feb/29/fragment-caching na przykład Django!
vdboor
Już na to spojrzałem i wydaje się, że cierpi na dokładnie ten sam problem. Dane, które próbuje buforować, są wymagane w celu uzyskania dostępu do bufora. Wydaje się, że jedyną rzeczą, na której oszczędza, jest wewnętrzna kosztowna operacja, która w przeciwieństwie do większości przypadków użycia dla tego rodzaju buforowania.
Dominic Santos
To prawda, dzieje się tak również z kodem 37signals, jest on skoncentrowany na kodzie renderowania. Sztuką jest buforowanie całej listy w innym kontenerze lub buforowanie pobierania obiektu w innym miejscu.
vdboor
W rzeczywistości ich strategia buforowania wydaje się nieco bardziej wykształcona. Polecam również ten artykuł: 37signals.com/svn/posts/…
JensG
Wygląda na to, że fragment kodu ma literówkę - tak post.bodymiało być comment.body?
Izkata

Odpowiedzi:

3

Za buforowanie prostego zrzutu pojedynczego, już załadowanego obiektu, tak, nie zyskujesz nic lub prawie nic. Nie tak opisują te przykłady - opisują hierarchię, w której każda zmiana czegoś niższego powinna również spowodować aktualizację wszystkiego wyżej w hierarchii.

Pierwszy przykład z bloga 37signals używa Project -> Todolist -> Todojako hierarchii. Wypełniony przykład może wyglądać następująco:

Project: Foo (last_modified: 2014-05-10)
   Todolist:  Bar1 (last_modified: 2014-05-10)
       Todo:  Bang1 (last_modified: 2014-05-09)
       Todo:  Bang2 (last_modified: 2014-05-09)

   Todolist:  Bar2 (last_modified: 2014-04-01)
       Todo:  Bang3 (last_modified: 2014-04-01)
       Todo:  Bang4 (last_modified: 2014-04-01)

Powiedzmy, że Bang3został zaktualizowany. Wszyscy jego rodzice również zostaną zaktualizowani:

Project: Foo (last_modified: 2014-05-16)
   Todolist:  Bar2 (last_modified: 2014-05-16)
       Todo:  Bang3 (last_modified: 2014-05-16)

Jeśli chodzi o czas renderowania, ładowanie Projectz bazy danych jest w zasadzie nieuniknione. Musisz zacząć od punktu. Ponieważ last_modifiedjest to wskaźnik wszystkich jego elementów podrzędnych , właśnie tego używasz jako klucza pamięci podręcznej przed próbą załadowania elementów podrzędnych.


Podczas gdy posty na blogu używają oddzielnych szablonów, połączę je w jeden. Mam nadzieję, że zobaczenie pełnej interakcji w jednym miejscu sprawi, że będzie trochę jaśniej.

Szablon Django może więc wyglądać mniej więcej tak:

{% cache 9999 project project.cache_key %}
<h2>{{ project.name }}<h2>
<div>
   {% for list in project.todolist.all %}
   {% cache 9999 todolist list.cache_key %}
      <ul>
         {% for todo in list.todos.all %}
            <li>{{ todo.body }}</li>
         {% endfor %}
      </ul>
   {% endcache %}
   {% endfor %}
</div>
{% endcache %}

Powiedzmy, że przekazujemy projekt, który cache_keynadal istnieje w pamięci podręcznej. Ponieważ propagujemy zmiany do wszystkich powiązanych obiektów do elementu nadrzędnego, fakt, że ten konkretny klucz nadal istnieje, oznacza, że całą renderowaną zawartość można pobrać z pamięci podręcznej.

Jeśli ten konkretny projekt został właśnie zaktualizowany - na przykład tak jak Foopowyżej - wówczas będzie musiał renderować swoje dzieci, i dopiero wtedy uruchomi zapytanie dla wszystkich Todolists dla tego projektu. Podobnie w przypadku konkretnego Todolisty - jeśli istnieje klucz cache tej listy, to todos w nim się nie zmieniły, a całość można wyciągnąć z bufora.

Zauważ też, że nie używam todo.cache_keytego szablonu. Nie warto, ponieważ, jak mówisz w pytaniu, bodyzostało już usunięte z bazy danych. Jednak trafienia do bazy danych nie są jedynym powodem, dla którego możesz buforować coś. Na przykład pobranie surowego tekstu znaczników (takiego jak to, co wpisujemy w polach pytań / odpowiedzi na StackExchange) i konwersja go do HTML może zająć wystarczająco dużo czasu, aby buforowanie wyniku było bardziej wydajne.

Gdyby tak było, wewnętrzna pętla w szablonie mogłaby wyglądać mniej więcej tak:

         {% for todo in list.todos.all %}
            {% cache 9999 todo todo.cache_key %}
               <li>{{ todo.body|expensive_markup_parser }}</li>
            {% endcache %}
         {% endfor %}

Aby więc zebrać wszystko w całość, wróćmy do moich oryginalnych danych na początku tej odpowiedzi. Jeśli założymy:

  • Wszystkie obiekty zostały zbuforowane w oryginalnym stanie
  • Bang3 został właśnie zaktualizowany
  • Renderujemy zmodyfikowany szablon (w tym expensive_markup_parser)

W ten sposób wszystko zostanie załadowane:

  • Foo jest pobierany z bazy danych
  • Foo.cache_key (2014-05-16) nie istnieje w pamięci podręcznej
  • Foo.todolists.all()jest pytany: Bar1i Bar2są pobierane z bazy danych
  • Bar1.cache_key(2014-05-10) już istnieje w pamięci podręcznej ; pobierz i wyślij go
  • Bar2.cache_key (2014-05-16) nie istnieje w pamięci podręcznej
  • Bar2.todos.all()jest pytany: Bang3i Bang4są pobierane z bazy danych
  • Bang3.cache_key (2014-05-16) nie istnieje w pamięci podręcznej
  • {{ Bang3.body|expensive_markup_parser }} jest renderowane
  • Bang4.cache_key(2014-04-01) już istnieje w pamięci podręcznej ; pobierz i wyślij go

Oszczędności z pamięci podręcznej w tym niewielkim przykładzie to:

  • Uniknięto trafienia bazy danych: Bar1.todos.all()
  • expensive_markup_parserunikać 3 razy: Bang1, Bang2, iBang4

I oczywiście przy następnym wyświetleniu Foo.cache_keyzostanie znaleziony, więc jedynym kosztem renderowania jest Foosamodzielne pobieranie z bazy danych i sprawdzanie pamięci podręcznej.

Izkata
źródło
-2

Twój przykład jest dobry, jeśli wymaga wyszukiwania lub przetwarzania danych dla każdego komentarza. Jeśli tylko weźmiesz ciało i wyświetlisz je - pamięć podręczna będzie bezużyteczna. Ale możesz buforować wszystkie drzewa komentarzy (w tym {% dla%}). W takim przypadku musisz unieważnić go przy każdym dodanym komentarzu, abyś mógł umieścić znacznik czasu ostatniego komentarza lub komentarze liczone gdzieś w Post i zbudować z nim klucz pamięci podręcznej komentarzy. Jeśli wolisz bardziej znormalizowane dane i używasz komentarzy tylko na jednej stronie, możesz po prostu wyczyścić klucz pamięci podręcznej przy zapisywaniu komentarzy.

Dla mnie zapisywanie liczby komentarzy w Postie wygląda wystarczająco dobrze (jeśli nie pozwalasz na usuwanie i edytowanie komentarzy) - masz wartość do pokazania gdziekolwiek z Postem i klucz do buforowania.

ilvar
źródło