Co oznacza błąd „invalid byte sequence in UTF-8” w Ruby i jak go naprawić?

Jeśli trafiłeś na tą stronę to prawdopodobnie zmagasz się z tym samym problemem, co ja. Z tego posta dowiesz się trochę o tym, czym są kodowania znaków, skąd biorą się nieprawidłowe sekwencje bajtów i jak je naprawić.

Post można przeczytać pod tym adresem:

https://dev.to/bajena/solving-invalid-byte-sequence-in-utf-8-errors-in-ruby-1f27

Sparse fieldsets: Przyspieszamy JSON API

Zgodnie ze zwyczajem z ostatnich kilku miesięcy chciałbym zaprosić do przeczytania mojego najnowszego posta na Medium. Artykuł pokazuje jak w prosty sposób przyspieszyć aplikację wykorzystującą JSON API za pomocą mało znanej (mimo że opisanej w specyfikacji) funkcji „sparse fieldsets”.

Post można przeczytać pod tym adresem: https://medium.com/@bajena3/decrease-load-on-your-json-apis-by-using-sparse-fieldsets-3e2c9491dc16

Google Apps Scripts – równoległe zapytania HTTP i obsługa błędów

W moim najnowszym wpisie na Medium można przeczytać o tym, w jaki sposób przyspieszyłem moje aplikację bazujące na Google Apps Scripts wykonując wiele zapytań HTTP jednocześnie oraz jak poradziłem sobie z problemem obsługi błędów w sytuacji, gdy część zapytań zwraca błędne rezultaty.

Post można przeczytać pod tym adresem:
https://medium.com/@bajena3/google-apps-scripts-parallel-http-requests-with-retries-5a24feaf61d8

Boost wydajności w Ruby on Rails dzięki technice batchingu

W tym poście chciałbym przybliżyć pojęcie batchingu i pokazać jak w prosty sposób wykorzystać batch loading w Railsowych aplikacjach, aby zmniejszyć liczbę zapytań do bazy danych.

Problem N+1 zapytań

Ruby on Rails to framework, który zyskał ogromną popularność dzięki temu, że umożliwia bardzo szybkie prototypowanie aplikacji. Dosłownie w przeciągu kilku godzin możemy napisać MVP naszego produktu. Wszystko działa jak należy, klient jest zadowolony więc dostajemy zlecenia na kolejne ficzery i usprawnienia. Z zapałem dodajemy kolejne tabele do bazy danych, akcje w kontrolerach i widoki, jednak w pewnym momencie zaczynamy zauważać, że aplikacja zaczyna mulić przy każdym przeładowaniu strony🙀.

Bardzo często okazuje się, że przyczyna problemu leży w nieoptymalnym sposobie, w jaki pobieramy dane z bazy danych.
Przykładowo, załóżmy, że nasza aplikacja ma dwie tabele: users i posts i wykonamy następujący kod:

posts = Post.where(id: [1, 2, 3])
# SELECT * FROM posts WHERE id IN (1, 2, 3)
users = posts.map { |post| post.user }
# SELECT * FROM users WHERE id = 1
# SELECT * FROM users WHERE id = 2
# SELECT * FROM users WHERE id = 3
view raw gistfile1.rb hosted with ❤ by GitHub

ActiveRecord wykonało jedno zapytanie do bazy danych aby pobrać listę postów, a następnie kolejne 3 zapytania, aby dla każdego z postów pobrać autora. Baza danych została obciążona aż czterema zapytaniami, mimo, że ten sam efekt można było osiągnąć za pomocą dwóch – jednego w celu pobrania postów i kolejnego po użytkowników, znając ich IDki.

Ten problem jest nazywany problemem N+1 zapytań (N+1 queries problem).

Typowe metody rozwiązywania problemu N+1 zapytań w Rails

W Railsach do walki z problemem N+1 zapytań często wykorzystuje się następujące metody:

Metoda „includes”

Użycie metody „includes” spowoduje automatyczne załadowanie rekordów dla wskazanej w argumencie relacji (w tym wypadku jest to „user”).

To rozwiązanie sprawdza się bardzo dobrze w przypadku prostych akcji, jednak jej użycie może prowadzić do załadowania zbędnych danych (np. kiedy potrzebujemy użytkowników tylko dla postów z id 1,2). Z tego powodu warto pamiętać, że includes nie jest tzw. golden bullet i używać go rozsądnie 🙂

Poniżej przykład użycia metody:

posts = Post.where(id: [1, 2, 3]).includes(:user)
# SELECT * FROM posts WHERE id IN (1, 2, 3)
# SELECT * FROM users WHERE id IN (1, 2, 3)
users = posts.map { |post| post.user }
view raw gistfile1.rb hosted with ❤ by GitHub

Metoda „joins”

Jeżeli rekordy relacji są nam potrzebne jedynie w celu przefiltrowania danych dobrym pomysłem może być użycie metody „joins”, będącej po prostu Railsowym odpowiednikiem SQL-owego JOIN-a.

Poniższy przykład pokazuje wykorzystanie rekordów z tabeli „users” bez konieczności ładowania danych do pamięci:

Post.joins(:users).where(users: { country_code: 'PL' }).map { |post| post.title }
# SELECT "posts".* FROM "posts" INNER JOIN "users" ON "posts"."user_id" = "users"."id" WHERE "users"."country_code" = $1
view raw gist.rb hosted with ❤ by GitHub

Wadą zarówno metody „joins”, jak „includes” jest to, że mogą one generować nieelastyczny kod odpowiedzialny za ładowanie danych, który uzależniony będzie od tego, jak wyglądają np. nasze widoki.

Przykładowo, chcąc wyświetlić listę użytkowników z postami i komentarzami do każdego z nich musimy zadeklarować chęć załadowania za jednym zamachem wszystkich relacji. W razie potrzeby zmiany widoków będziemy musieli przerabiać też kod odpowiedzialny za ładowanie danych.

Batching to the rescue!

Mimo, że wyżej wymienione są użyteczne w niektórych scenariuszach, to często w „prawdziwym świecie” okazują się niewystarczające, z racji tego, że mogą powodować ładowanie zbędnych danych lub generowanie kodu spaghetti.

Alternatywą dla tych rozwiązań jest tytułowy „batching” – technika, która rozprzestrzeniła się w dużej mierze dzięki Facebookowi i ich standardzie GraphQL.

„Batching” można przedstawić jako proces składający się z trzech kroków:

  1. Etap zbierania danych do załadowania – aplikacja gromadzi ID rekordów wymaganych np. do wyrenderowania widoku. Nawiązując do poprzednich przykładów mogą to być ID autorów poszczególnych postów.
  2. Etap ładowania i cache’owania – po zgromadzeniu potrzebnych ID rekordów odpowiednie dane są ładowane za pomocą jednego zapytania i cache’owane w pamięci, w celu uniknięcia ponownego zapytania do bazy danych.
  3. Etap dystrybucji danych – odpowiednie rekordy są przekazane do miejsc w kodzie, które zgłosiły po nie potrzebę.

Dużą zaletą takiego podejścia jest to, że pozwala załadować dokładnie te dane, które są nam potrzebne w danym momencie i zwalnia nas z przymusu deklaracji wszystkich potrzebnych relacji w jednym miejscu.

Dodatkową korzyścią z poznania tej techniki jest fakt, że znajduje zastosowanie nie tylko w SQL-owych bazach danych, ale również bazach NO-SQL (np. Mongo, Cassandra). Co więcej, oprócz optymalizacji zapytań do bazy danych można jej też użyć np. do ograniczenia liczby zapytań HTTP do zewnętrznych serwisów.

Batching w Ruby

Osobom zainteresowanym wykorzystaniem tej techniki w aplikacjach napisanych w Rubym polecam zapoznanie się z gemem Batch Loader. Na początku koncept klasy BatchLoader może wydać się nieco nieintuicyjny, ale gwarantuję, że widok malejących logów zapytań do bazy danych będzie wart wysiłku 🙂

Przyjrzyjmy się, w jaki sposób BatchLoader pozwala wyeliminować problem N+1 zapytań:

# app/models/post.rb
def user_lazy
BatchLoader.for(user_id).batch do |user_ids|
User.where(id: user_ids)
end
end
posts = Post.where(id: [1, 2, 3]) # SELECT * FROM posts WHERE id IN (1, 2, 3)
users = posts.map { |post| post.user_lazy }
users.each { |user| puts "#{user.name}" } # SELECT * FROM users WHERE id IN (1, 2, 3)
view raw user_lazy.rb hosted with ❤ by GitHub

Wywołanie metody „user_lazy” na każdym z postów pozwoliło Batch Loaderowi na zgromadzenie ID wszystkich potrzebnych użytkowników.
Odpytanie bazy danych zostało odroczone, dzięki leniwej ewaluacji, aż do momentu, w którym atrybuty poszczególnych użytkowników są niezbędne do wyświetlenia. Dopiero wtedy BatchLoader pobiera za jednym zamachem trzy rekordy z tabeli „users”.

Oprócz zapobiegania N+1 zapytaniom BatchLoader posiada również cache, który zapobiega ponownemu załadowaniu rekordów, które zostały załadowane wcześniej:

users = posts.map { |post| post.user_lazy }
users.each { |user| puts "#{user.name}" } # SELECT * FROM users WHERE id IN (1, 2, 3)
users.each { |user| puts "#{user.address}" } # Pobierze użytkowników z cache'u
view raw cache.rb hosted with ❤ by GitHub

Batching w Rails-owym REST API

BatchLoader wydał mi się bardzo użytecznym narzędziem, dlatego postanowiłem wykorzystać go w celu optymalizacji Railsowej aplikacji wystawiającej dość rozbudowane JSON API renderowane za pomocą popularnej biblioteki ActiveModel::Serializers.

Pomysł okazał się trafiony i aplikacja dostała wyraźnego kopa, jednak przy każdym kolejnym użyciu BatchLoadera zaczeła przeszkadzać mi ilość boilerplate code’u, czyli powtarzalnego kodu niezbędnego do dodania nowego batch loadera.

Wiele z serializerów powtarzało podobne schematy, jak na przykład ten:

class PostSerializer < ActiveModel::Serializer
attributes :id, :title
belongs_to :user
def self.lazy_user(post)
BatchLoader.for(post.user_id).batch do |user_ids|
User.where(id: user_ids)
end
end
def author
self.class.lazy_user(object)
end
end
view raw serializer.rb hosted with ❤ by GitHub

Eliminowanie powtarzającego się kodu doprowadziło do tego, że postanowiłem opakować całość w postaci gema i udostępnić społeczności jako ams_lazy_relationships (https://github.com/Bajena/ams_lazy_relationships/).

Gem ten jest rozszerzeniem do ActiveModel::Serializers i umożliwia korzystanie z batchingu w serializerach za pomocą metod lazy_has_many/lazy_has_one/lazy_belongs_to.

Przykładowo, wcześniejszy serializer zmodyfikowany tak, by korzystać z ams_lazy_relationships będzie wyglądał następująco:

class BaseSerializer < ActiveModel::Serializer
include AmsLazyRelationships::Core
end
class PostSerializer < BaseSerializer
attributes :id, :title
lazy_belongs_to :user
end

Tym prostym sposobem możemy uniknąć N+1 zapytań podczas serializacji nawet bardzo skomplikowanych obiektów.

Gem działa out-of-the-box z ActiveRecord-owymi SQL-owymi relacjami, ale można go użyć również do batch loadingu danych z różnych źródeł (ja np. korzystam do ładowania danych z Cassandry i MySQL-a).

Więcej przykładów i dokładniejsze objaśnienia można znaleźć w README gema albo w tym poście.

Podsumowanie

Mam nadzieję, że mój post zachęci kogoś z czytelników do zgłębienia idei batchingu. Myślę że warto ten koncept, znany głównie w kręgu ludzi zajmujących się technologią GraphQL, zaadaptować również w aplikacjach wykorzystujących stare dobre REST API, czy server-side rendering. Zapraszam do komentowania i zadawania pytań 🙂

Siedzenie czy stanie? Przemyślenia o szukaniu mniejszego zła w pracy programisty.

Jestem mega szczęśliwy, że zacząłeś/aś czytać ten post, ale teraz zatrzymaj się proszę na chwilę i sprawdź, jak wygląda Twoja pozycja podczas lektury.
Jeśli jest (a stawiam dużo PLN, że jest) jedną z wariacji poniższego zdjęcia, to znajdujesz się w dobrym miejscu.

Podobny obraz
Fajna ta pozycja, taka niezbyt ergonomiczna

Posypując głowę popiołem, muszę przyznać, że moja pozycja podczas pracy przy komputerze przez przez kilka ładnych lat wyglądała tak, jak u tego Pana ☝️.

Plecy nie wysiadły mi do tej pory prawdopodobnie z dwóch powodów:

  1. Dzięki temu, że zawsze byłem związany ze sportem, co wzmocniło moje tzw. mięśnie głębokie (ang. core), utrzymujące ciało w akceptowalnej formie.
  2. Mimo wszystko spędziłem jeszcze za mało czasu garbiąc się nad laptopem. Co prawda, jest to średni argument, bo z jakiegoś powodu niektórzy moi rówieśnicy już skarżą się na bóle podczas siedzenia w pracy.

Pewnie zastanawiasz się, dlaczego ten koleś w ogóle rozwodzi się nad problemem postawy, skoro nie bolą go plecy. Zainteresował mnie ten temat właśnie dlatego, że jako programista jestem w  grupie ryzyka i wolę, cytując reklamę, „przeciwdziałać niż leczyć”.

Okazało się, że temat jest trochę bardziej złożony, niż mogło by się wydawać, dlatego postanowiłem opisać mój proces dążenia do zdrowego stanowiska pracy.

Z tego miejsca od razu chciałbym zaznaczyć, że nie jestem fizjoterapeutą ani specem od BHP i wypowiadam się z perspektywy sprawnej i zdrowej osoby, dlatego przefiltruj tę wiedzę przez swoją własną sytuację.

Ewangelizacja Starretta i początek pracy na stojąco

Rok temu w ramach prezentu urodzinowego dostałem od narzeczonej (wtedy w sumie jeszcze nie narzeczonej) książkę Kelly’ego Starretta pt. „Bądź sprawny jak lampart„. Dotyka ona głównie tematu treningu funkcjonalnego, który jest mi dość bliski, więc wciągnąłem ją w ekspresowym tempie, z wypiekami na twarzy. Kiedy odkryłem, że Starrett ma na swoim koncie jeszcze inne pozycje, szybko się w nie zaopatrzyłem.

Jedną z tych książek był „Skazany na biurko„. W telegraficznym skrócie, Kelly pisze, że „sitting is the new smoking”, punktuje negatywne skutki długotrwałego siedzenia, nawołuje do pracy całkowicie na stojąco i słowami zrozumiałymi dla zwykłego śmiertelnika (takiego jak ja) tłumaczy, jak przystosować swoje biurko do pracy w pozycji stojącej. Osobom, które z różnych przyczyn nie mogą zmienić trybu pracy, jest też poświęcony rozdział o tym, jak minimalizować skutki siedzenia.

Kelly Starrett – Skazany na biurko

Nie chciałbym tutaj streszczać czy recenzować całej książki. Powiem tylko, że sposób w jaki jest napisana i treści w niej zawarte zachęciły mnie, żeby na własnej skórze spróbować pracy na stojąco.

Oprócz oczywistego powodu, jakim jest szansa na uniknięcie typowych problemów z kręgosłupem za parę lat, zmotywował mnie fakt, że siedząc po 8+h godzin dziennie marnuję w dużym stopniu wysiłek, jaki wkładam w codziennie rozciąganie (👉 ROMWOD.com) i pracę nad mobilnością na treningach w CrossFit Ursynów.

Nie lubię tracić czasu, więc od razu zacząłem od przejrzenia sieci w poszukiwaniu natchnienia. Pan Gugle zasypał mnie ofertami regulowanych biurek do stania i siedzenia, co nie do końca mi odpowiadało, bo nie miałem ochoty robić dużych zmian w biurze i wydawać zbyt wiele pieniędzy na nowy mebel. Chciałem po prostu przetestować przez pewien czas nowe rozwiązanie.

Najprostszym pomysłem było oczywiście podłożenie pudeł pod monitor i klawiaturę, ale nie chciałem zastawać takiego widoku wchodząc rano do biura:

Mocno prowizoryczne biurko stojące

Kuszącą opcją było Elevo Convert, czyli konstrukcja zamieniająca zwykłe biurko w regulowalne biurko stojąco-siedzące. Niestety, z racji wysokiej ceny, również odpadło w pierwszym podejściu.

Nakładka na biurko do pracy stojącej Elevo Convert
Elevo Convert – dość drogie, przenośne rozwiązanie

Idealnym rozwiązaniem na początek okazał się produkt firmy Standidesk – ActiveStand, który jest po prostu nakładką na zwykłe biurko, wykonaną z wytrzymałego kartonu. Miłym atutem ActiveStanda jest jego design, który pozwala zapomnieć o tym, że mamy do czynienia ze zwykłym płatem kartonu.
Przed kupnem przez chwilę naszła mnie wątpliwość, czy warto wydawać 60 zł na kawałek kartonu. Ostatecznie jednak zdecydowałem się na zakup, bo mimo wszystko była to najtańsza opcja, która jednocześnie nie wyglądała jak g.

Mój ActiveStand w akcji

Wrażenia z pracy wyłącznie na stojąco

Z racji tego, że ActiveStand zabiera sporo miejsca na blacie, moje biurko przekształciło się w stanowisko wyłącznie stojące.
Mając w pamięci ewangelizację Starretta o negatywnych skutkach siedzenia i zaletach stania stwierdziłem, że to nawet dobrze, że nie będę mógł zmieniać pozycji więc zacząłem pracować po 8h na stojąco.

Po około miesiącu pracy w ten sposób, mogę podsumować moje doświadczenia następującymi spostrzeżeniami:

Plusy

  • Pod koniec dnia czułem się dużo mniej znużony, niż po całym dniu siedzenia
  • Ciągły ruch wymuszony staniem daje pozytywny rodzaj zmęczenia. Dodatkowo dla osób mniej aktywnych fizycznie taki ruch to kalorie spalone za darmo (NEAT)
  • Stanie wymusza robienie częstszych przerw na lekkie rozciąganie i odpoczynek
  • Podczas stania można masować sobie podeszwę stopy piłką do Lacrosse’a/tenisa. Dzięki temu mam darmowe ćwiczenia mobility zerowym nakładem czasu 😀

Minusy

  • Nawet dość aktywna osoba, taka jak ja, jest zmęczona fizycznie po 8h stania.
  • Uczucie „ciężkich” nóg – obawiam się, że na dłuższą metę mógłbym mieć problemy z żylakami
  • Zmęczenie odcinka lędźwiowego kręgosłupa – podobnie jak siedzenie, długotrwałe stanie powodowało u mnie dyskomfort w dolnych partiach kręgosłupa.
  • Ból pięt – po kilku dniach na stojąco pięty potrafiły mnie boleć na tyle, że chodzenie zaczynało sprawiać mi ból. Nie pomogło tutaj stanie na miękkim podłożu ani praca w butach z miękką podeszwą – ból po pewnym czasie zawsze przychodził.
  • Kiedy praca mnie zbytnio pochłania, mam tendencję do ignorowania przerw mimo zmęczenia. W przypadku stania szybko płaci się cenę za takie zaniedbanie w postaci bólu nóg i pleców.

Ostatecznie zdecydowałem, że zrezygnuję z pracy wyłącznie stojącej. Wydaje mi się, że moje decyzja była spowodowana w dużej mierze tym, że nie umiałem trzymać dyscypliny i grzecznie co pół godziny robić przerwę na rozciąganie i rozluźnienie. Czasami po prostu praca idzie za dobrze, żeby ją przerywać i „marnować” czas na takie błahostki 😉

Rozwiązanie: stanio-siedzenie

Mimo że „sitting is the new smoking”, to jak się okazało, samo stanie też nie jest w moim przypadku rozwiązaniem. Z tego powodu wróciłem myślami do pomysłu zainstalowania w moim biurze regulowanego biurka do stania i siedzenia.

Przeglądając różne rozwiązania moją uwagę przykuły te meble:

  • Wspomniane wcześniej Elevo Convert – ostatecznie jednak zarzuciłem ten pomysł z racji wysokiej ceny i zbyt niskiej wysokości, jaką można ustawić.
  • IKEA Skarsta – biureczko na korbkę w przystępnej cenie ok. 700zł. Ostatecznie również odpadło, bo stwierdziłem, że będę zbyt leniwy, żeby co pół godziny wykonywać ok. 90 obrotów korbką. Poza tym z recenzji na YT dowiedziałem się, że mechanizm wyrabia się dość szybko, a nie miałem zamiaru naprawiać biurka niedługo po zakupie.
  • IKEA Bekant – kolejne biurko od IKEI, tym razem elektryczne, w cenie ok 1950zł. Wiadomo, IKEA to IKEA – banalny montaż i obsługa.
  • Elevo Grande – Elektryczne biurko od Elevo w cenie ok. 2000zł. Estetyczny design, dobre opinie i wbudowany programator wysokości z timerem.

Ostatecznie mój wybór padł na Elevo Grande. Po montażu biurka, który zajął ok. 2h, i pobawieniu się mechanizmem podnoszącym blat, zabrałem się do normalnej pracy.

Elevo Grande w wybranym przeze mnie wariancie

Z biurka korzystam już od około miesiąca w trybie 30 minut stania na 30 minut siedzenia i muszę przyznać, że takie ustawienie sprawdza się idealnie. Kiedy dodamy do tego wbudowany alarm przypominający o zmianie pozycji i guziki umożliwiające automatyczną zmianę wysokości do zapamiętanych wcześniej poziomów, otrzymujemy rozwiązanie, które eliminuje zarówno wady długotrwałego siedzenia, jak i stania.

fotel
Moje biurko w akcji

Podsumowując, mogę wymienić następujące (subiektywne) zalety biurka Elevo i ogółem zmiany trybu pracy na stojąco-siedzący:

  • Koniec z jakimkolwiek bólem pleców i stóp, czy uczuciem ciężkich nóg.
  • Timer wbudowany w biurko bezlitośnie przypomina mi o zmianie pozycji głośnym pikaniem. Niby można sobie ustawić alarm w telefonie/komputerze, ale ta funkcja wbudowana w biurko sprawdza się w moim przypadku doskonale.
  • Zapamiętywanie wysokości pozwala za każdym razem w kilka sekund przejść do pozycji idealnie dostosowanej pod siebie, bez żadnego dostrajania, które wybija z rytmu podczas pracy.
  • Biurko po prostu ładnie się prezentuje 😛

Podsumowanie i bonusowy tip

Po przetestowaniu obydwu skrajnych wariantów, stwierdzam, jak to zwykle bywa, że ideał leży gdzieś pośrodku i w moim przypadku najlepiej zdaje egzamin regularna zmiana trybu pracy z siedzącego na stojący.

Gorąco polecam zainteresowanie się tematem optymalizacji miejsca pracy. Gwarantuję, że inwestycja czasu na zdobywanie wiedzy i pewnej kwoty na sprzęt szybko zwróci się w postaci lepszego samopoczucia i efektywności.

Chciałbym jeszcze na koniec zaznaczyć, że nawet najlepsze biurko stojące nie pomoże Ci, jeżeli będziesz stał skrzywiony jak sowiecki sierp. Jeżeli mimo kupna nowego biurka ciągle czujesz, że z twoim kręgosłupem nie jest najlepiej, to zacznij zwracać uwagę na to  JAK stoisz. Jeżeli to możliwe spróbuj też wpleść w swój dzień ćwiczenia wzmacniające mięśnie głębokie – na przykład dowolne wariacje ćwiczenia plank.

Jako bonus podrzucam tip, który podpatrzyłem od Miraska i stosuję juz od ponad tygodnia – co 30 minut, zmieniając pozycję, wykonuję serię 10 pompek (Mirek używa kettlebella i robi nimi martwe ciągi i wymachy). Dzięki temu jestem w stanie wykonać praktycznie zerowym wysiłkiem około 100 pompek dziennie. To jest dopiero optymalizacja!
P.S. Jeżeli chcesz wyrobić STALOWE MUSKUŁY, możesz robić 20 pompek po każdej nieudanej kompilacji kodu.

Jeżeli czytasz ten post mając już jakieś doświadczenie z pracą na stojąco, to koniecznie napisz w komentarzu o tym, czy:

  • Pracujesz wyłącznie na stojąco?
  • Jeżeli nie, to jak często zmieniasz pozycję?
  • Z jakiego sprzętu korzystasz?
  • Masz jakieś dodatkowe tipy?