Portal miejski, czyli redakcja, która publikuje codziennie
Strona olshtyn.com powstała jako portal informacyjny dla mieszkańców Olsztyna i osób, które planują tam przyjazd. Olsztyn jest stolicą województwa warmińsko-mazurskiego, liczy około stu siedemdziesięciu tysięcy mieszkańców, ma w granicach administracyjnych kilkanaście jezior i sezon turystyczny wyraźnie skupiony wokół lata. Te trzy fakty wyglądają jak ciekawostki krajoznawcze, a w praktyce ustawiły cały projekt: serwis obsługuje dwie zupełnie różne grupy czytelników, a ruch na nim nie jest równy przez rok.
Mieszkaniec wraca po aktualność. Interesuje go to, co wydarzyło się wczoraj i co wydarzy się w weekend, a portal odwiedza często i krótko. Turysta przychodzi raz, zwykle z telefonu, zwykle spoza regionu, i szuka rzeczy trwałych: co warto zobaczyć, jak dojechać, gdzie jest co. Pierwsza grupa potrzebuje strumienia, druga struktury. Serwis, który obsługuje tylko strumień, po roku ma nieczytelne archiwum. Serwis, który obsługuje tylko strukturę, przestaje być powodem do powrotu.
Projekt trafił do realizacji w 2012 roku, obejmował także warstwę identyfikacji wizualnej, a samo wdrożenie zajęło około sześciu tygodni. Nie ma tu wskaźników wyników, bo dane o oglądalności portalu należą do jego wydawcy, a nie do naszego opisu realizacji. To, co da się opowiedzieć uczciwie, to decyzje techniczne i ich konsekwencje, a akurat one przenoszą się na kolejne projekty.
Model treści: co było naprawdę trudne
Najwięcej pracy pochłonął model treści, a nie wygląd. Układ i rozmieszczenie elementów dostarczył klient, więc po naszej stronie leżało przełożenie go na szablony, zachowanie responsywne i strukturę danych, którą redakcja utrzyma bez programisty.
Portal miejski ma tę właściwość, że jego materiały żyją w różnych rytmach. Wiadomość ma wartość przez kilka dni i potem interesuje już tylko wyszukiwarkę. Zapowiedź wydarzenia jest wartościowa aż do dnia wydarzenia, a potem zmienia znak: przestaje być zaproszeniem i staje się archiwum. Opis zabytku albo szlaku nie starzeje się w ogóle i to on ściąga ruch z wyszukiwarki przez lata. W standardowym WordPressie wszystkie trzy wpadają do jednego worka wpisów i sortują się po dacie, co oznacza, że najcenniejszy materiał znika z widoku najszybciej.
Rozwiązaniem były osobne typy treści: aktualność, wydarzenie z datą i miejscem, oraz materiał przewodnikowy bez daty ważności. Rozdzielenie ma konsekwencję, którą widać dopiero po roku. Wydarzenie z zakończoną datą samo schodzi z listy głównej, zamiast wisieć tam do czasu, aż ktoś je ręcznie zdejmie. Materiał przewodnikowy nie konkuruje o miejsce z bieżącą wiadomością, więc nie trzeba go co miesiąc podbijać sztucznym odświeżeniem daty publikacji. A redakcja, dodając wpis, odpowiada na pytanie o typ materiału jeden raz, przy tworzeniu, zamiast pilnować jego widoczności co tydzień.
Druga decyzja dotyczyła taksonomii. Kuszące jest opisanie wszystkiego wszystkim: dzielnicą, tematem, wydarzeniem, osobą i obiektem naraz. Taki model wygląda elastycznie w dniu wdrożenia i rozpada się w szóstym miesiącu, bo dwóch redaktorów opisuje ten sam materiał inaczej, a archiwum przestaje się filtrować. Zestaw kategorii został więc zamknięty i krótki, a to, co naprawdę zmienne, opisują luźne tagi, których nikt nie używa do budowania nawigacji.
Trzecia decyzja dotyczyła adresów. Portal informacyjny buduje wartość w wyszukiwarce latami i robi to przez konkretne materiały, nie przez stronę główną. Adres wpisu nie zawiera więc daty ani identyfikatora numerycznego, tylko czytelny tytuł materiału, a kategoria nie wchodzi do ścieżki. Powód jest prozaiczny: kategoria bywa zmieniana po fakcie, a zmiana adresu opublikowanego rok wcześniej tekstu kosztuje pozycję i wymaga przekierowania. Adres, który nie zależy od decyzji porządkowych redakcji, przeżywa reorganizację serwisu bez żadnej pracy.
Znaczniki semantyczne HTML5 i dane strukturalne schema.org zostały wprowadzone tam, gdzie opisują coś prawdziwego, a nie w każdym możliwym miejscu. Wydarzenie ma datę, miejsce i nazwę, więc opis strukturalny wydarzenia niesie informację. Artykuł ma autora i datę publikacji. Materiał przewodnikowy nie udaje ani jednego, ani drugiego. Nadmiar danych strukturalnych, wciskanych do każdego widoku, nie poprawia widoczności, a utrudnia późniejsze zmiany, bo każda korekta modelu treści wymaga wtedy ruszenia kilku niezależnych opisów.
Warstwa dostarczania i cache przy nierównym ruchu
Stos to WordPress na PHP z bazą MySQL, Redis i Memcached jako pamięci podręczne, warstwa CDN przed serwisem oraz zaplecze z HTML5, CSS3, SASS i JavaScriptem po stronie przeglądarki. Kod jest wersjonowany w Git, a wymiana danych z frontem idzie przez AJAX i punkty końcowe REST.
Cache w portalu informacyjnym rządzi się inną logiką niż w sklepie. Sklep ma koszyk i sesję, więc jego strony trudno wydać z pamięci. Portal ma sytuację odwrotną: prawie każdy widok jest identyczny dla wszystkich odwiedzających i zmienia się wtedy, gdy redakcja coś opublikuje. To znaczy, że zdecydowaną większość odsłon da się oddać bez uruchamiania PHP i bez pytania bazy o cokolwiek. Problem leży gdzie indziej, w unieważnianiu.
Redakcja portalu miejskiego publikuje w reakcji na zdarzenie. Kiedy w mieście dzieje się coś istotnego, tekst musi być widoczny od razu, a nie po wygaśnięciu pamięci podręcznej. Jednocześnie ten sam moment przynosi najwyższy ruch w całym roku, więc to najgorsza chwila na oddawanie wszystkiego z PHP. Rozstrzygnięcie polegało na powiązaniu unieważnienia ze zdarzeniem publikacji, a nie z upływem czasu: zapis wpisu czyści pamięć dla strony głównej, listy kategorii i samego wpisu, natomiast reszta serwisu pozostaje nietknięta. Bez tego dzieje się jedno z dwóch, i oba są złe. Albo redakcja nie widzi własnego tekstu i zaczyna omijać cache, żądając jego wyłączenia, albo cache jest tak krótki, że w szczycie ruchu nie robi różnicy.
Redis i Memcached siedzą tu na różnych zadaniach. Pamięć obiektowa skraca zapytania powtarzane przy każdym żądaniu, czyli menu, listy kategorii i wyniki cięższych zapytań archiwalnych. Sesje i dane ulotne, które nie muszą przeżyć restartu, leżą osobno. Rozdzielenie ma sens operacyjny: czyszczenie jednej warstwy nie wywraca drugiej, więc awaryjne opróżnienie pamięci obiektowej nie wylogowuje nikogo z panelu.
Warstwa CDN dokłada trzeci poziom, ważny akurat przy turyście. Czytelnik z Warszawy, Berlina albo z telefonu w roamingu dostaje statyki z węzła bliżej siebie, a serwer aplikacyjny w ogóle nie widzi tych żądań. Przy materiale przewodnikowym, który jest z natury obrazkowy, to największa pojedyncza oszczędność w całym wdrożeniu.
Obrazy zasługują tu na osobne zdanie, bo w portalu miejskim to one stanowią większość przesyłanych bajtów. Redakcja publikuje zdjęcie prosto z aparatu albo od fotoreportera, w rozmiarze wielokrotnie większym niż jakikolwiek widok na stronie. Przetwarzanie przy wgraniu generuje zestaw wariantów dla listy, dla widoku wpisu i dla galerii, a szablon wskazuje przeglądarce, którego wariantu ma użyć przy danej szerokości ekranu. Bez tego czytelnik mobilny pobiera zdjęcie przygotowane pod monitor, płaci za to transferem i czasem, a redakcja nawet nie wie, że tak się dzieje, bo na jej łączu wszystko działa szybko.
Doładowywanie treści i koszt, którego się nie widzi
Listy wiadomości doładowują kolejne pozycje asynchronicznie, przez AJAX i dedykowane punkty końcowe REST, zamiast przeładowywać całą stronę. Wygoda jest oczywista, kosztów są dwa i warto je nazwać, bo w 2012 roku łatwo było je przeoczyć.
Pierwszy dotyczy wyszukiwarki. Treść, która pojawia się dopiero po kliknięciu, bywa dla robota niewidoczna. Dlatego pierwsza porcja listy jest w kodzie strony od razu, a doładowanie obsługuje wyłącznie kolejne strony, do których i tak prowadzi zwykły odnośnik z numerem strony. Nawigacja stronicowa została w kodzie także wtedy, gdy interfejs jej nie pokazuje, bo to ona jest ścieżką dla robota i dla czytelnika bez JavaScriptu.
Drugi koszt dotyczy cache. Punkt końcowy zwracający porcję listy jest osobnym adresem, więc albo ma własną politykę pamięci, albo staje się dziurą, przez którą w szczycie ruchu wszystko idzie do PHP. Odpowiedzi tych punktów są więc cache’owane tak samo jak strony i unieważniane tym samym zdarzeniem publikacji.
Podobnie wyglądała integracja map i galerii. Mapa z zewnętrznego dostawcy to kilkaset kilobajtów skryptu i połączenie do obcego serwera, wykonywane niezależnie od tego, czy ktoś w ogóle na mapę spojrzy. Ładuje się więc dopiero wtedy, gdy odwiedzający dotrze do sekcji z lokalizacją, a do tego czasu w jej miejscu stoi statyczny obraz z adresem. Przy czytelniku mobilnym, który stoi na ulicy i chce sprawdzić jedną rzecz, ta różnica decyduje o tym, czy strona w ogóle zdąży się otworzyć.
Projekt obejmował także warstwę identyfikacji wizualnej, a znak graficzny portalu powstał z myślą o miejscu, w którym będzie żył naprawdę. Logo serwisu informacyjnego spędza większość czasu w pasku nagłówka o wysokości kilkudziesięciu pikseli, na telefonie, obok przycisku menu, i musi być rozpoznawalne również jako ikona w zakładce przeglądarki. To ustawia wymagania odwrotnie niż przy znaku dla materiałów drukowanych: liczy się czytelność w małej skali, zachowanie na jednolitym tle i wariant uproszczony, który nie rozsypuje się przy szerokości kilkunastu pikseli. Znak przygotowany wyłącznie w wersji poziomej, z cienkimi detalami, wygląda dobrze w prezentacji i znika w rzeczywistym nagłówku, więc warianty powstały razem z szablonami, a nie przed nimi.
Migracja archiwum, czyli praca, której nie widać na stronie
Część materiałów pochodziła ze starszych wersji serwisu i trzeba je było przenieść razem z historią. Import treści z archiwalnych zrzutów, w tym z Web Archive, jest zadaniem, które wygląda na godzinę pracy, a zajmuje kilka dni, i zawsze z tych samych powodów.
Stare serwisy miały inne kodowanie znaków, więc polskie znaki diakrytyczne potrafią wrócić z importu jako zlepki bajtów, widoczne dopiero wtedy, gdy ktoś przeczyta konkretny akapit. Stare adresy mają inny kształt niż nowe, więc każdy zaimportowany materiał wymaga przekierowania ze swojego dawnego adresu, inaczej portal traci cały dorobek linków przychodzących i pozycji, które budował latami. Obrazki z archiwum bywają niekompletne, a podpisy pod nimi żyją w treści zamiast w polu opisu.
Kolejność pracy była tu istotna: najpierw mapowanie adresów starych na nowe, potem import treści, na końcu weryfikacja próbki ręcznie, na kopii produkcji. Odwrotna kolejność kończy się drugim importem, bo poprawianie adresów po wejściu na produkcję oznacza, że część ruchu zdążyła trafić w pustkę.
Utrzymanie po starcie
Serwis informacyjny nie ma stanu spoczynku, więc utrzymanie zostało zaplanowane razem z wdrożeniem, nie po nim. Obejmuje aktualizacje silnika, motywu i wtyczek, przegląd logów systemowych, kopie zapasowe z odtworzeniem sprawdzanym na kopii, a nie tylko wykonywanym, oraz drobne zmiany funkcjonalne wynikające z tego, jak redakcja faktycznie pracuje.
Warto nazwać jedną zasadę, która dotyczy wtyczek. Bezpieczeństwo realizujemy metodami programistycznymi i konfiguracją serwera, a nie kolejną wtyczką ochronną. Wtyczka tego typu działa wewnątrz PHP, czyli już po tym, jak żądanie uruchomiło aplikację, a przy tym sama bywa źródłem podatności i obciąża każde żądanie. W portalu, który ma nierówny ruch i musi przeżyć skok w dniu ważnego wydarzenia, jest to zły kompromis. Filtrowanie na wejściu, kontrola uprawnień w kodzie i twarda konfiguracja serwera kosztują mniej i działają wcześniej.
Osobnym elementem utrzymania jest monitorowanie sekcji komentarzy. Moduł dyskusji przy wiadomościach lokalnych jest najbardziej żywą częścią portalu i jednocześnie tą, która najczęściej wymaga interwencji. Redakcja dostała narzędzia moderacyjne i kolejkę wpisów oczekujących, a nie automat, bo decyzja o usunięciu komentarza w sprawie miejskiej jest decyzją redakcyjną, nie techniczną.
Warto dodać jedną obserwację z eksploatacji takich serwisów. Największym ryzykiem portalu miejskiego nie jest awaria, tylko rozjazd między tym, co redakcja chce publikować, a tym, co system jej ułatwia. Jeżeli dodanie wydarzenia wymaga wypełnienia dwudziestu pól, redakcja zacznie publikować wydarzenia jako zwykłe wiadomości, bo tak jest szybciej, i po pół roku kalendarz jest pusty mimo działającego kodu. Dlatego formularze redakcyjne zostały ograniczone do pól, które faktycznie wpływają na wygląd lub filtrowanie, a wszystko, co da się wyliczyć automatycznie, jest wyliczane.
Testy przed startem szły na kopii produkcji, nie na pustej instalacji, i to rozróżnienie ma tu konkretne uzasadnienie. Pusta instalacja ma dziesięć wpisów, więc każda lista mieści się na jednym ekranie, każde zapytanie do bazy jest natychmiastowe, a archiwum nie istnieje. Problemy portalu zaczynają się przy kilku tysiącach materiałów: strona kategorii zaczyna liczyć dłużej, wyszukiwarka wewnętrzna zwraca wyniki posortowane bezużytecznie, a stronicowanie sięga strony dwudziestej, której nikt nigdy nie otworzył przy testach. Kopia produkcji z rzeczywistym archiwum pokazuje to w kilka minut.
Podsumowanie i ustalenia z etapu analizy
Etap analizy zamknął cztery kwestie, które później porządkowały każdą kolejną decyzję. Odbiorca został zdefiniowany podwójnie, jako mieszkaniec i jako turysta, z jawnym przyjęciem, że ci dwaj czytelnicy chcą czegoś innego i że strona główna musi obsłużyć obu. Zakres funkcjonalny objął zarządzanie treścią, kalendarz wydarzeń, mapy lokalizacji, galerie i moduł komentarzy. Harmonogram został podzielony na etapy, dzięki czemu redakcja mogła zacząć wypełniać serwis treścią przed domknięciem ostatnich prac. Punktem odniesienia były wskazane przez klienta portale o silnym nacisku na społeczność lokalną, co przesądziło o tym, że komentarze i wydarzenia stanęły wysoko, a warstwa dekoracyjna nisko.
Z perspektywy kolejnego wdrożenia przenosi się warstwa techniczna i sposób myślenia o cache przy treści redakcyjnej. Nie przenosi się model treści tego projektu, bo powstał pod jedno miasto, jeden zespół redakcyjny i jeden rytm publikacji. Kolejny portal zaczyna się od pytania, kto go czyta i jak często, a dopiero potem od tego, co ma być w bazie.
Często zadawane pytania
Najważniejsze odpowiedzi, które pomagają wdrożyć temat w praktyce.
Jakiego zakresu dotyczył projekt olshtyn.com?
#Jak wyglądał przebieg prac przy olshtyn.com?
#Co było najtrudniejsze technicznie w olshtyn.com?
#Co z projektu olshtyn.com da się wykorzystać przy kolejnym wdrożeniu?
#Potrzebujesz FAQ dopasowanego do branży i rynku? Przygotujemy wersję pod Twoje cele biznesowe.
Porozmawiajmy