Orange światłowód 8 Gb/s — pierwsze wrażenia

Od początku grudnia korzystam z najszybszego wariantu usługi FTTH od Orange czyli 8 Gb/s prędkości pobierania danych i 1 Gb/s prędkości wysyłania. Jak to działa? Wedle testu prędkości w Liveboksie lepiej niż idealnie:

Pomiar prędkości łącza w Liveboksie

Wedle speedtest.pl trochę gorzej:

speedtest.pl z serwerami Orange

A po zmianie rekomendowanych serwerów na inne niż orange’owskie prędkość pobierania nawet nie dobija do 2 Gb/s:

speedtest.pl z serwerami spoza sieci Orange

Tyle teorii. W praktyce przy pobieraniu nie czuję żadnej różnicy w porównaniu do prędkości 2 Gb/s, którą miałem wcześniej. W codziennym użytkowaniu trudno nawet przebić pułap 1 Gb/s na pojedynczym urządzeniu, czasami przy aktualizacji Debiana, jeśli akurat trafią się spore paczki z różnych repozytoriów, to sumaryczna prędkość pobierania przekracza 1 Gb/s. Ponad 1 Gb/s udaje mi się też uzyskać przy pobieraniu dużych plików z One Drive’a. No i oczywiście przy p2p można uzyskać prędkości rzędu kilku gigabitów, choć biorąc pod uwagę, że wielkość plików ISO to przeważnie od 4 do 7,5 GiB to pobieranie kończy się zanim na dobre się rozpędzi.

Sporą różnicę odczuwam przy wysyłaniu danych w świat. Przy wysyłaniu dużych obrazów dockerowych do registry albo kopii zapasowych danych bez problemu uzyskuję maksymalną prędkość wysyłania 1 Gb/s. W porównaniu do 600 Mb/s, które miałem wcześniej wysyłanie jest wyraźnie szybsze i trwa krócej.

Czy jestem zadowolony z tej usługi? Tak, bo po pierwsze dla mnie prędkość wysyłania danych jest ważniejsza niż prędkość pobierania. Różnica pomiędzy 600 Mb/s a 1000 Mb/s jest duża i bez problemu uzyskuję maksymalne transfery. Po drugie przy obecnych promocjach w Orange opcja 8 Gb/s była tańsza niż oferta na przedłużenie opcji 2 Gb/s, którą otrzymałem. Jeśli jednak za dwa lata opcja 8 Gb/s będzie wyraźnie droższa niż 2 Gb/s to pewnie wybiorę wolniejszą, bo w moich zastosowaniach jest wystarczająca. A może za dwa lata doczekam się np. symetrycznego łącza 2 Gb/s lub choćby 1 Gb/s? Widziałem, że Play ma ofertę z symetrycznym łączem 1 Gb/s, więc kto wie…

Mellanox ConnectX-3 nie działa z Debianem?

Oczywiście, że działa i to bez żadnych kombinacji. Poniżej opiszę dlaczego u mnie nie zadziałało i jak to naprawić.

Tytułem wprowadzenia, robię przymiarki do przyspieszenia sieci domowej do 10 Gb/s. Do komputera stacjonarnego kupiłem kartę Mellanox ConnectX-3 (MCX311A-XCAT) na PCI Express. Jakież było moje zdziwienie, kiedy po zainstalowaniu karty w komputerze i uruchomieniu Debiana po karcie nie było ani śladu. Nie to, że nie została rozpoznana czy nie został załadowany moduł jądra. Po prostu pustka, ani lspci ani dmesg nie pokazały żadnej informacji związanej z kartą. Oczywiście przeszedłem standardową procedurę od wyjęcia i ponownego włożenia karty do grzebanie w opcjach UEFI — na próżno.

Przełomem okazał się ten wątek na forum STH, a konkretnie informacja o zależności pomiędzy gniazdami M2 i slotami PCI Express. Sprawdziłem instrukcję do mojej płyty głównej MSI MAG B550 Tomahawk, a tam jak byk stoi, że w wypadku obsadzenia obu gniazd M2 trzeci slot PCI Express jest nieaktywny.

Po wyjęciu dysku z drugiego gniazda M2 i uruchomieniu Debiana wszystko było na swoim miejscu. Polecenie lspci -k pokazało:

04:00.0 Ethernet controller: Mellanox Technologies MT27500 Family [ConnectX-3]
	Subsystem: Mellanox Technologies ConnectX-3 10 GbE Single Port SFP+ Adapter
	Kernel driver in use: mlx4_core
	Kernel modules: mlx4_core

W ustawieniach systemu w zakładce Sieć pojawiło się nowe połączenie 10 000 Mb/s.

Podsumowując, karta sieciowa Mellanox ConnectX-3 działa z Debianem 12 bez żadnych kombinacji.

Inicjalizacja nowego projektu Symfony

W dokumentacji Symfony napisane jest, żeby do inicjalizacji nowego projektu użyć polecenia symfony new albo composer create-project. Jeśli podobnie jak ja nie lubicie instalować PHP z przyległościami lokalnie to można to zrobić z użyciem Dockera.

docker run --rm --interactive --tty --volume $PWD:/app --user $(id -u):$(id -g) composer create-project symfony/skeleton my_project_directory

Elvis w statku kosmicznym

Czy widzicie Elvisa w statku kosmicznym?

usort(
  $data,
  fn(array $a, array $b) => $a['label'] <=> $b['label'] ?: $a['id'] <=> $b['id']
)

Gdzie jest Elvis? Elvis tudzież „Elvis operator” to potoczna nazwa operatora warunkowego ?: który zwraca pierwsze wyrażenie, którego wartość jest prawdziwa. Z kolei statek kosmiczny (ang. „spaceship operator”) to potoczna nazwa operatora <=> który dokonuje trójstronnego porównania wartości.

Powyższy kawałek kodu sortuję tablicę $data po kluczu label, a w wypadku gdy wartości label są identyczne dodatkowo po kluczu id. Dzięki Elvisowi i statkom kosmicznym kod jest bardzo zwięzły.

pg_restore w Dockerze

Taka sytuacja: mamy bazę danych PostgreSQL działającą w Dockerze. Chcemy wgrać dane z kopii zapasowej, ale nie chcemy wrzucać pliku z bazą do kontenera postgresa. Jak to zrobić?

Możemy obok wystartować drugi kontener, w którym plik z bazą udostępnimy przez bind mount i w tym kontenerze uruchomimy pg_restore, który będzie wyrzucać dane na standardowe wyjście, które rurą przekierujemy do właściwego kontenera z bazą. Uff, brzmi skomplikowanie, ale samo polecenie mieści się w jednej linijce:

docker run -v "$(PWD)":/backup postgres:9.4 pg_restore --clean --no-owner --no-privileges /backup/dump.sql | docker-compose exec -T db psql -U dbuser -d dbname

Powyższy przykład zakłada, że kontener Postgresa został uruchomiony przez docker-compose, co najczęściej będzie miało miejsce na lokalnym środowisku developerskim. Jeśli projekt działa w roju (swarm) to polecenie można zmodifikować następująco:

docker run -v "$PWD":/backup postgres:9.4 pg_restore --clean --no-owner --no-privileges /backup/dump.sql | docker exec -i project_db.1.st5j3pz7rre153z7ficb59c9n psql -U dbuser -d dbname

AB testów wydajnościowych

Żeby przetestować wydajność aplikację webowej od A do Z trzeba się nieźle nagimnastykować. Jeśli jednak wystarczą powierzchowne testy, dajmy na to od A do B, to jest na to prosty sposób: ab czyli Apache Benchmark.

Apache Benchmark dobrze się sprawdzi np. w takich scenariuszach:

  • sprawdzenie czy refaktoring konkretnej końcówki API przyniósł wzrost wydajności,
  • testowanie różnych ustawień serwera,
  • obciążenie aplikacji celem sprawdzenia czy autoskalowanie działa.

W poniższych przykładach podaję tylko te argumenty ab, które są istotne dla danego przykładu. W prawdziwych zastosowaniach trzeba do nich dodać np. liczbę zapytań i liczbę równoczesnych zapytań.

Pomijanie loadbalancera

Obecnie serwery aplikacji są częstokroć schowane za loadbalancerem. Testując aplikację najczęściej chcemy uderzać bezpośrednio w nią. W tym celu należy wysłać nagłówek Host zawierający domenę aplikacji, a w URL-u podać serwer z aplikacją (domena lub IP):

ab -H "Host: api.example.org" http://10.1.1.10/endpoint

Autoryzacja

Tu również najczęściej wystarczy podać jeden nagłówek, a konkretnie Authorization:

ab -H "Authorization: Bearer wygenerowany-token" https://api.example.org/endpoint

Formularz

Żeby wysłać formularz metodą POST należy przygotować plik z zawartością formularza. Klucze i wartości powinny być rozdzielone znakiem równości, a kolejne krotki oddzielone znakiem &. Należy pamiętać o kodowaniu znaków niealfanumerycznych. Przykład:

parametr1=wartosc&parametr2=wartosc%20ze%20spacjami&parametr=dodatkowy

Uwaga: wiele edytorów automatycznie wstawia znak nowej linii na końcu każdego wiersza, co ab zinterpretuje jako część ostatniej wartości, co może prowadzić do trudnych do wykrycia błędów. Można to obejść na końcu umieszczając dodatkowy parametr.

Tak przygotowany plik należy podać jako argument dla ab wraz z kodowaniem:

ab -p form-data.txt -T 'application/x-www-form-urlencoded’ https://api.example.org/endpoint

PUT

Poniżej przykład testowania końcówki metodą PUT:

ab -m PUT -H 'Content-Length: 0' https://api.example.org/endpoint/id

Testowana końcówka nie wymaga przesłania danych innych niż te z URL-a, więc niezbędny jest nagłówek Content-Length z wartością 0, bez niego aplikacja rzucała błąd mówiący o niemożliwości sparsowania żądania.

Docker

A co jeśli na maszynce nie ma ab? Jeśli jest Docker to można zrobić:

docker run cmd.cat/ab ab -n 10000 -c 10 https://api.example.org/endpoint

Duplicity i B2 Cloud Storage

Ostatnie wydarzenia w OVH zainspirowały mnie do audytu mechanizmów kopii bezpieczeństwa. Dla moich projektów webowych audyt wypadł pomyślnie, jednak dane z domowych komputerów są backupowane tylko na inny komputer, który także znajduje się w mieszkaniu. W razie pożaru lub kradzieży mógłbym stracić dane oryginalne i ich kopie bezpieczeństwa.

Postanowiłem robić kopie do innej lokalizacji. Na domowych komputerach mam sporo zdjęć i filmów, a to setki gigabajtów, więc koszt przechowywania kopii ma znaczenie. Pod tym względem ciekawie wypada oferta B2 Cloud Storage.

Instalacja Duplicity z wymaganymi zależnościami w Debianie 11 jest banalna:

apt install duplicity python3-b2sdk

Niestety w Debianie 10 to nie działa, brakuje bibliotek dla B2, co objawia się błędem jak poniżej:

BackendException: B2 backend requires B2 Python APIs (pip install b2)

Doinstalowanie ich pip-em nie pomaga. Metodą prób, błędów i szukania znalazłem działające rozwiązanie na forum – należy zarówno Duplicity jak i SDK dla B2 zainstalować przez pip-a:

apt install build-essential python3-dev gettext librsync-dev
pip3 install duplicity
pip3 install b2sdk

Cyberpunk 2077

Ja bym siebie tam nie umieścił, bo do powstania Cyberpunka 2077 nie przyczyniłem się w najmniejszym stopniu, ale skoro już zostałem wymieniony w napisach to się pochwalę.

Odpowiadając na niezadane pytanie: napisałem kilkaset linijek kodu w aplikacji odpowiedzialnej za przyznawanie cyfrowych nagród w grze.

O wiele większym wyzwaniem i powodem do dumy było obsłużenie istnego tsunami ruchu wygenerowanego podczas premiery Cyberpunka 2077, ale to temat na inną notkę.

Zmienna liczba argumentów w konstruktorze

W PHP nie raz zachodzi potrzeba przechowywania w obiekcie tablicy obiektów określonego typu, np. w kolekcjach, managerach strategii, itp. Jeśli lista obiektów jest przekazywana w konstruktorze to warto sprawdzić czy są właściwego typu. Przykładowy konstruktor może wyglądać tak:

/**
 * @param Item[] $items
 * @throws InvalidArgumentException
 */
public function __construct(array $items)
{
    foreach ($items as $item) {
        if (!$item instanceof Item) {
            throw new InvalidArgumentException('Collection accepts only instances of Item');
        }
    }
    $this->items = $items;
}

Ten sam efekt można osiągnąć prościej dzięki zastosowaniu operatora … (trzy kropki):

public function __construct(Item ...$items)
{
    $this->items = $items;
}