Konrad Kowalski (rootsher)Principal Platform & Reliability Architect010101111110011011110110001100101000010101100001

Edge rendering: renderowanie bliżej użytkownika

data
kategoria
Frontend
także w
Cloud · System Design
czytanie
3 min / 613 słów

Do tej pory przesuwaliśmy rendering głównie w czasie.

Mógł odbywać się:

text
podczas builda
przy pierwszym requestcie
przy każdym requestcie
albo fragmentami przez streaming

Ale jest jeszcze druga oś:

gdzie fizycznie wykonuje się kod?

Jeśli użytkownik jest w Warszawie, aplikacja działa w regionie us-east-1, a baza stoi w Wirginii, każdy dynamiczny request ma koszt geograficzny.

Naturalny pomysł brzmi:

przenieśmy compute bliżej użytkownika.

Origin nie musi być jedynym miejscem wykonania

Klasyczny model:

text
browser
  |
  v
CDN
  |
  v
origin
  |
  v
database

CDN świetnie radzi sobie ze statycznymi zasobami.

Ale dynamiczny HTML nadal często wymaga round tripu do originu.

Edge rendering próbuje przesunąć część wykonania:

text
browser
  |
  v
edge runtime
  |
  v
response

Jeżeli użytkownik znajduje się kilka milisekund od edge node'a, pierwsza część requestu może być znacznie krótsza.

„Bliżej użytkownika” brzmi lepiej niż jest

Załóżmy:

text
user -> edge      10 ms
user -> origin   120 ms

Na pierwszy rzut oka edge wygrywa bez dyskusji.

Ale render zwykle potrzebuje danych.

Jeżeli baza nadal znajduje się przy originie:

text
user
  | 10 ms
  v
edge
  | 110 ms
  v
database
  | 110 ms
  v
edge
  | 10 ms
  v
user

to przenieśliśmy compute bliżej użytkownika, ale oddaliliśmy go od danych.

W praktyce najważniejsze pytanie nie brzmi:

gdzie jest renderer?

Tylko:

gdzie znajduje się cały dependency graph potrzebny do odpowiedzi?

Komercyjny przykład: globalny storefront

Wyobraźmy sobie sklep działający w Europie, USA i Azji.

Część strony jest wspólna:

text
layout
opis produktu
zdjęcia
content marketingowy

Część zależy od regionu:

text
waluta
podatki
promocje
dostępność
język

Edge runtime może bardzo dobrze nadawać się do lekkiej logiki:

text
request
  |
  v
detect region
  |
  v
select locale
  |
  v
read edge cache
  |
  v
compose response

Jeżeli potrzebne dane są już lokalnie dostępne, można uniknąć drogiego round tripu do centralnego originu.

To właśnie dlatego edge dobrze łączy się z:

text
CDN cache
KV storage
geolokalizacja
routing
prosta personalizacja

Edge runtime to często inny runtime

W praktyce „edge” nie zawsze oznacza po prostu:

text
Node.js, tylko bliżej użytkownika

Często kod działa w bardziej ograniczonym środowisku.

Na przykład w modelu opartym o isolate'y:

text
request
  |
  v
existing lightweight isolate
  |
  v
execute handler

zamiast:

text
request
  |
  v
start process/container
  |
  v
initialize runtime
  |
  v
execute handler

To może zmniejszyć startup overhead, ale zwykle ogranicza dostępne API i sposób korzystania z natywnych zależności.

Framework może ukrywać sporą część tej różnicy, ale runtime pozostaje inny.

Ciekawostka

V8 isolate nie jest osobnym procesem.

To odseparowany kontekst wykonania JavaScriptu wewnątrz procesu V8, posiadający własny heap i stan runtime'u.

Dzięki temu wiele isolate'ów może współdzielić jeden proces, zamiast uruchamiać osobny proces systemowy dla każdego requestu.

To daje bardzo dobry profil startupu i gęstości uruchomień.

Ale granica isolate'a nie daje automatycznie takiej samej izolacji jak granica procesu.

To jeden z powodów, dla których edge runtimes mają własny zestaw ograniczeń i mechanizmów sandboxingu.

Data locality wygrywa z marketingiem

Załóżmy dwa warianty.

Origin rendering

text
user -> origin: 100 ms
origin -> database: 2 ms
render: 15 ms

Edge rendering

text
user -> edge: 10 ms
edge -> database: 90 ms
render: 15 ms

Edge nie daje tutaj spektakularnej przewagi.

Jeżeli render wykonuje kilka sekwencyjnych query:

text
edge
  |
  v
DB query A
  |
  v
DB query B
  |
  v
DB query C

problem może być wręcz gorszy.

Każde połączenie z bazą dokłada kolejne RTT.

Dlatego edge architecture bardzo często wymusza zmianę nie tylko miejsca wykonania frontendu, ale też sposobu dostępu do danych.

Replikacja danych zmienia grę

Jeżeli dane możemy replikować:

text
US database
EU replica
Asia replica

wtedy compute i dane mogą znajdować się blisko siebie.

Ale natychmiast pojawia się nowy problem:

consistency.

Jeżeli użytkownik zmieni adres dostawy w Europie, kiedy aktualizacja będzie widoczna w replice w Tokio?

Dla contentu marketingowego opóźnienie kilku sekund może być nieistotne.

Dla stanu konta albo płatności już nie.

Edge przesuwa więc frontend w świat klasycznych problemów systemów rozproszonych:

text
latency
consistency
replication
data ownership

Edge świetnie nadaje się do pewnej klasy pracy

Nie wszystko trzeba przenosić.

Dobre kandydatury to zwykle operacje:

text
krótkie
request-specific
zależne od lokalizacji
mocno cache'owalne
niewymagające wielu odległych query

Przykładowo:

text
redirect
locale detection
A/B assignment
authentication check
cache lookup
response composition

Znacznie trudniejszy przypadek:

text
complex ORM query
+
multiple internal APIs
+
transaction
+
large server render

Sam deployment „na edge” nie naprawia architektury zależności.

Edge i SSR to różne decyzje

To ważne rozróżnienie.

SSR odpowiada:

kiedy powstaje HTML?

Edge odpowiada:

gdzie wykonuje się kod?

Możemy mieć:

text
SSR on origin
SSR on edge
static HTML on edge CDN
client rendering with edge API

To niezależne osie.

Współczesne frameworki często prezentują je razem, dlatego łatwo zacząć traktować „edge rendering” jak osobny typ renderowania.

Architektonicznie to przede wszystkim decyzja o lokalizacji compute.

I znowu wracamy do klienta

Do tej pory próbowaliśmy poprawić pierwsze dostarczenie HTML-a.

Przesuwaliśmy rendering:

text
build
request
stream
edge

Ale nadal pozostaje druga połowa problemu.

Jeżeli wysyłamy użytkownikowi dużą aplikację JavaScriptową, browser może później wykonać pełną hydration niezależnie od tego, jak szybko powstał HTML.

Możemy więc zoptymalizować serwer do granic możliwości, a potem oddać main threadowi ogromne drzewo komponentów do aktywacji.

Naturalne pytanie brzmi:

czy cała strona naprawdę musi stać się interaktywna od razu?

To prowadzi do kolejnej klasy rozwiązań: Selective i Progressive Hydration.