-

Jak poprawić wydajność strony z modułem Drupal BigPipe? Kompleksowy przewodnik

Szybkość witryny ma kluczowe znaczenie, szczególnie dziś, kiedy nowoczesne strony internetowe są bardziej dynamiczne i interaktywne. Tradycyjne podejście do ich obsługi jest zatem szczególnie nieefektywne. Istnieje wiele technik pozwalających osiągnąć optymalną wydajność, a jedną z metod jest technika BigPipe, opracowana przez Facebooka. Dobrą wiadomością jest to, że moduł BigPipe z tą samą funkcjonalnością, został zintegrowany z rdzeniem Drupala 8 od wersji 8.1.

Jak działa Drupal BigPipe?

Ogólna idea techniki BigPipe polega na dekomponowaniu stron internetowych na małe fragmenty (pagelets) i przepuszczaniu ich w pipeline przez kilka etapów realizacji wewnątrz serwerów internetowych oraz przeglądarek. 

Na najwyższym poziomie, BigPipe wysyła odpowiedź HTML w kawałkach:

1. Jeden fragment: wszystko do momentu tuż przed </body> zawiera symbole zastępcze BigPipe dla spersonalizowanych części strony. Stąd wysyła niespersonalizowane części strony. Nazwijmy to Szkieletem (ang. The Skeleton).

2. N fragmentów: znacznik <script> na element zastępczy BigPipe w The Skeleton.

3. Jeden fragment: </body> i wszystko po nim.

Jest to koncepcyjnie identyczne z BigPipe Facebooka (stąd nazwa).

Czym Drupal BigPipe różni się od techniki Facebooka?

Moduł Drupala znacznie różni się od implementacji Facebooka (i innych) możliwością automatycznego określenia, które części strony internetowej mogą skorzystać z mechanizmu BigPipe.

System renderowania Drupala ma koncepcję “auto-placeholderingu”. Co to oznacza? Treść zbyt dynamiczna jest zastępowana symbolem zastępczym, który może zostać wyrenderowany później. 

Co więcej, moduł ma również koncepcję “strategii zastępczych” (ang. placeholder strategies). Domyślnie symbole zastępcze są zamieniane po stronie serwera, a odpowiedź jest blokowana po zastąpieniu ich wszystkich. Możliwe jest jednak dodanie dodatkowych strategii zastępczych. BigPipe to tylko kolejna z nich. Innymi mogą być ESI, AJAX itp. 

BigPipe zaimplementowany przez Facebooka może działać tylko wtedy, gdy JavaScript jest włączony. Moduł Drupal BigPipe umożliwia natomiast zastępowanie symboli zastępczych bez JavaScript “no-JS BigPipe”. Technicznie nie jest to wcale BigPipe, ale jest to po prostu użycie tzw. multiple flushes. 

Pozwala nam to używać zarówno no-JS BigPipe, jak i “klasycznego BigPipe” w tej samej odpowiedzi, aby zmaksymalizować ilość treści, które możemy wysłać tak wcześnie, jak to możliwe.

Zasadniczo dzieje się to podczas procesu renderowania strony:

  • Spersonalizowane części są przekształcane w placeholdery
    <span data-big-pipe-placeholder-id="callback=d_profiles.builder%3Abuild&amp;args%5B0%5D=profile_widget_mini&amp;token=6NHeAQvXLYdzXuoWp2TRCvedTO2WAoVKnpW-5_pV9gk"></span>.
    Symbol zastępczy zawiera informacje o tym, które wywołanie zwrotne należy wywołać i jakie argumenty należy do niego przekazać. Następnie renderer kontynuuje przechodzenie przez tablicę i konwertowanie jej na HTML. Wynikowy kod HTML, w tym symbole zastępcze, jest buforowany. Następnie, w zależności od używanej strategii renderowania, symbole zastępcze są zastępowane ich dynamiczną zawartością.
  • Zastępowanie elementów zastępczych odbywa się za pomocą JavaScript. Wywołanie zwrotne zaczyna szukać zastępczych elementów skryptu po wydrukowaniu i znalezieniu specjalnego elementu
    <script type="application/vnd.drupal-ajax" data-big-pipe-event="stop">
  • W ostatniej chwili jest ona zastępowana rzeczywistą treścią. Ta nowa strategia pozwala nam najpierw opróżnić początkową stronę internetową, a następnie przesyłać strumieniowo zamienniki dla symboli zastępczych.

Kiedy używać lazy buildera?

Zasadniczo należy rozważyć użycie lazy buildera, gdy zawartość dodawana do tablicy renderowania jest jednym z poniższych typów.

  • Zawartość, która miałaby wysoką kardynalność, gdyby była zcachowana. Na przykład blok wyświetlający nazwę użytkownika. Może być cachowany, ale ponieważ różni się w zależności od użytkownika, prawdopodobnie spowoduje to również cachowanie obiektów o niskim współczynniku trafień (ang. hit rate).
  • Zawartość, która nie może być cachowana lub ma bardzo wysoki współczynnik unieważnień. Na przykład wyświetlanie bieżącej daty/godziny lub statystyk, które muszą być zawsze tak aktualne, jak to tylko możliwe.
  • Zawartość, która wymaga długiego i potencjalnie powolnego procesu składania (ang. assembly process). Na przykład blok wyświetlający zawartość z interfejsu API innej firmy, w którym żądanie zawartości z interfejsu API powoduje obciążenie.

Użycie lazy builderów w praktyce

Dla zapewnienia kompleksowego przykładu implementacji lazy builderów, rozważmy scenariusz z niestandardowym blokiem widgetu profilu umieszczonym na stronie internetowej. Blok zawiera zdjęcie użytkownika, jego imię i nazwisko oraz menu profilu.

Niestandardowy widget profilu na stronie testowej udostępniony w celu użycia modułu Drupal BigPipe.

Aby upewnić się, że każdy użytkownik widzi swoje spersonalizowane informacje, możemy wdrożyć określone strategie, takie jak ustawienie “max-age” na 0 lub wykorzystanie kontekstów użytkownika i tagów. Należy jednak pamiętać, że ustawienie “max-age” na 0 spowoduje, że reszta strony internetowej nie będzie cachowana.

Dzięki koncepcji “auto-placeholdering”, Drupal uznaje ten blok za spersonalizowaną część i zamienia go w placeholder.

Jedynym problemem jest to, że cały blok jest zastępowany przez symbol zastępczy:
<span data-big-pipe-placeholder-id="callback=Drupal%5Cblock%5CBlockViewBuilder%3A%3AlazyBuilder&amp;args%5B0%5D
=profilewidget&amp;args%5B1%5D=full&amp;args%5B2%5D
&amp;token=QzMTPnxwihEGOitjJB_tahJj8V-L-KopAVnEjVEMSsk"></span>

Niemniej jednak warto zauważyć, że określone dane w bloku mogą pozostać statyczne lub spójne dla wszystkich użytkowników, tak jak w tym przypadku:

Blok widgetu profilu ze statycznymi i spójnymi elementami do testowania modułu Drupal BigPipe.

Aby uczynić nasz blok bardziej szczegółowym, możemy przekształcić dynamiczne części w symbole zastępcze, podczas gdy pozostała zawartość bloku pozostaje w pamięci podręcznej i ładuje się podczas początkowego ładowania strony.

Krok 1. Tworzenie lazy builderów

Lazy buildery, podobnie jak inne elementy, są implementowane przy użyciu tablicy renderowania typu #lazy_builder. Tablica renderowania musi zawierać wywołanie zwrotne jako pierwszy element i tablicę argumentów do tego wywołania zwrotnego jako drugi element.

Elementy renderujące Lazy Builder powinny zawierać tylko właściwości #cache, #weight i #create_placeholder. 

public function build() {

 $build['user_data'] = [

   '#lazy_builder' => [

     'd_profiles.builder:build',

     [],

   ],

   '#create_placeholder' => TRUE,

 ];



 $build['user_menu'] = $this->buildMenu()



 $build['user_img'] = [

   '#lazy_builder' => [

     'd_profiles.builder:build',

     ['profile_widget_mini'],

   ],

   '#create_placeholder' => TRUE,

 ];



 return $build;

}


Krok 2. Implementacja interfejsu TrustedCallbackInterface

Zanim przejdziemy dalej, musimy upewnić się, że nasza implementacja lazy buildera może wywołać metodę lazyBuilder(). Aby to zrobić, musimy zaimplementować TrustedCallbackInterface, aby przekazać Drupalowi, że nasze wywołanie zwrotne lazy buildera jest dozwolone.

Podczas wdrożenia tego interfejsu musimy dodać metodę o nazwie trustedCallbacks(), która będzie wywoływana automatycznie przez Drupala poprzez wykrycie interfejsu. Wartością zwrotną tej metody muszą być dowolne metody w tej klasie, które mogą być używane jako wywołania zwrotne.

Oto podstawowa implementacja dla naszego bloku:

/**

* Provides a lazy builder for the profile block.

*/

class ProfileBuilder implements TrustedCallbackInterface {



 /**

  * {@inheritdoc}

  */

 public static function trustedCallbacks() {

   return ['build'];

 }



 /**

  * Build profile details.

  */

 public function build($view_mode = 'navbar_widget') {

   return $this->entityBuilder->build($this->loadProfile(), $view_mode);

 }

}

W rezultacie cachowany blok będzie wyglądał następująco:

<div id="profile-widget" class="profile-widget dropdown">

  <button class="btn dropdown-toggle" type="button" id="dropdown-menu-button" data-toggle="dropdown" aria-expanded="false">       

    <div id="block-profilewidget" class="img img--round">

      <span data-big-pipe-placeholder-id="callback=em_profiles.builder%3Abuild&amp;args%5B0%5D=profile_widget_mini&amp;token=ArkAzE-rR2gaSeRCkyb61vLT6nWbvDcIx0HQ8gjUMUs"></span>

    </div>

  </button>

  <div class="dropdown-menu" aria-labelledby="dropdown-menu-button">

    <section class="profile-widget__data p-5">

      <h3 class="profile-widget__name title-h4 mb-2">Profile</h3>

      <span data-big-pipe-placeholder-id="callback=em_profiles.builder%3Abuild&amp;&amp;token=ODDkF_Laqrq9ERh-djJN_UI_C1J2L6FtmRMh8luWPqk"></span>

    </section>

    <nav class="profile-widget__menu p-5" aria-labelledby="profile-widget">        

      <ul class="nav navbar-nav">

        <li class="nav-item">

          <a href="/user/settings" class="nav-link--icon-settings nav-link--icon nav-link" data-drupal-link-system-path="user/settings">Settings</a>

        </li>

        <li class="nav-item">

          <a href="/user/logout" class="nav-link--icon nav-link--icon-logout nav-link" data-drupal-link-system-path="user/logout">Logout</a>

        </li>

      </ul>

    </nav>

  </div>

</div>

Zazwyczaj wywołanie zwrotne lazy buildera będzie wykonywane przy każdym ładowaniu strony, co jest zamierzonym zachowaniem. Jednak w niektórych przypadkach może być również konieczne cachowanie symboli zastępczych. Aby to osiągnąć, musimy dołączyć klucze pamięci podręcznej wraz z kontekstem pamięci podręcznej, jak w poniższym przykładzie:

$build['user_data'] = [

  '#lazy_builder' => [

    'em_profiles.builder:build',

    [],

   ],

  '#create_placeholder' => TRUE,

  '#cache' => [

    'contexts' => ['user'],

    'keys' => [

      'entity_view',

      'user',

     'profile',

     'navbar_widget',

    ],

  ],

];

Krok 3. Zapewnienie płynnego wizualnego ładowania strony

Ponieważ Drupal BigPipe leniwie ładuje niektóre części strony, może to powodować uciążliwe ładowanie strony. Zależy to od naszego motywu i lokalizacji leniwie ładowanej zawartości.

Najprostszym rozwiązaniem jest wyświetlanie leniwie ładowanych treści w zarezerwowanym dla nich miejscu, co pozwala uniknąć ponownego ich wyświetlania. Alternatywnie, możemy zastosować animację “ładowania” do wszystkich symboli zastępczych BigPipe w naszym motywie za pomocą CSS.

Ostatnią możliwą opcją jest zdefiniowanie podglądu interfejsu, który zostanie wypełniony przez BigPipe, przy użyciu szablonu Twig.

Porównajmy końcowy wynik niestandardowej strategii lazy builder (1) ze strategią “auto-placeholding” (2).

Wynik użycia niestandardowej strategii lazy builderów w bloku widgetu profilu na stronie testowej.

Niestandardowa strategia lazy builder (1)

 

Efekt strategii "auto-placeholdering" Drupala w bloku widgetu profilu na stronie internetowej.

Strategia “auto-placeholdingu” Drupala (2)

Obie strategie działają dobrze, ale można dostrzec wady automatycznego wstrzymywania miejsca, takie jak gwałtowne ładowanie strony (drastyczna zmiana układu).

Więcej przykładów:

1. Blok statystyk ze statyczną częścią ładowaną natychmiast i dynamiczną zawartością ładowaną później:

--


2. Widoki ze szkieletem (The Skeleton):

-


Rozwiązywanie problemów z lazy builderami

Jeśli zaimplementowałeś lazy builder i nie przyspiesza on ładowania strony na Drupalu lub po prostu nie działa zgodnie z oczekiwaniami, możesz spróbować kilku rzeczy:

  • Upewnij się, że moduł Drupal BigPipe jest aktywny. 
  • Sprawdź ustawienia pamięci podręcznej metody wywołania zwrotnego lazy buildera. Domyślnie Drupal przyjmuje pewne założenia dotyczące sposobu cachowania, co nie zawsze jest odpowiednie dla danego przypadku użycia. Zamiast tego można jawnie ustawić ustawienia pamięci podręcznej.
  • Upstream CDN lub warstwa Varnish mogą cachować całą stronę internetową, więc wszystkie dane wyjściowe procesu renderowania BigPipe będą obsługiwane jednocześnie. Należy znaleźć inny mechanizm, aby to obejść.

Drupal BigPipe - podsumowanie 

W tym artykule zbadaliśmy alternatywną strategię renderowania, która pozwala nam odroczyć renderowanie wysoce dynamicznej zawartości dopiero po załadowaniu statycznych części strony internetowej z pamięci podręcznej.

BigPipe, moduł Drupala, może automatycznie zwiększyć wydajność strony internetowej dzięki ulepszonemu pipeline’owi i interfejsowi API renderowania, w szczególności metadanym cachowania i automatycznemu placeholderingowi.

Należy jednak pamiętać, że korzystanie z Drupal BigPipe nie zastępuje rozwiązania podstawowych problemów z wydajnością. Wdrożenie lazy builderów w celu złagodzenia wpływu powolnego kodu na witrynę jedynie zamaskuje problem, a nie całkowicie go rozwiąże. Dzięki skutecznemu wdrożeniu tych technik można zoptymalizować wydajność i wzbogacić ogólne wrażenia użytkownika na stronach internetowych opartych na Drupalu.

3. Najlepsze praktyki zespołów programistycznych