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:

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:

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:

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ń:

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:

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:

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:

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ń 🙂

Reklamy

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?

Jak stworzyć własne źródło danych w Google Data Studio?

Google Data Studio jest stosunkowo nową technologią, jednak zyskująca na popularności. Ostatnio zainteresował mnie temat budowania źródeł danych dla GDS i postanowiłem opisać moje doświadczenia związane z tą platformą budując przykładowy connector pobierający dane z API Spotify.

Zacząłem serię postów, którą będę prowadził w serwisie medium.com.

Poniżej lista dotychczas napisanych postów:

Piszemy rozszerzenie do Sublime Text 3

Potrzeba matką wynalazku

Ostatnio pisząc testy jednostkowe zauważyłem, że bardzo często powtarzam schemat „Napisz test” -> „Skopiuj test” -> „Zmień wartość w skopiowanym teście tak, by przetestować warunek odwrotny” -> „Zmień opis na odwrotny”.

W praktyce wygląda to mniej więcej tak:

negate_example.gif

Pomyślałem, że mogę nieco usprawnić ten proces, a przy okazji nauczyć się czegoś nowego – wpadłem więc na pomysł, że stworzę plugin do mojego ulubionego edytora – Sublime Text 3.

Plugin ten ma mieć jedno bardzo proste zadanie, a mianowicie zamienianie angielskich zdań w trzeciej osobie liczby pojedynczej z twierdzących na przeczące i na odwrót.

Ale od czego mam zacząć?

Skoro pomysł już jest, to teraz wystarczy go zrealizować, prawda? Problem jest tylko taki, że nie mam pojęcia, jak pisać pluginy do ST3. Szybkie googlowanko zaprowadziło mnie do tego artykułu. Stamtąd dowiedziałem się, że pluginy są po prostu pythonowymi klasami dziedziczącymi po sublime_plugin.TextCommand i odpalane są przez interpreter Pythona wbudowany w Sublime’a. Nie byłem tym zachwycony, bo nigdy nie pisałem w tym języku, jednak odnalazłem w nim sporo analogii do ruby’ego, a z większością problemów wynikających z nieznajomości API lub składni mogłem się uporać dzięki kochanemu StackOverflow.

Mimo że głównym elementem mojego pluginu są operacje na stringach, potrzebowałem również zapoznać się z podstawami API Sublime’a (Dokumentację można znaleźć tutaj). Interfejs nie jest super intuicyjny, ale do mojego celu wystarczyło zrozumieć kilka konceptów:

  • klasa View – reprezentuje okienko edytora. W momencie, gdy nasz ekran podzielony jest na kilka okien edycji wywołanie self.view w klasie naszego plugina zwróci okienko, w którym aktualnie jest kursor.
  • Klasa Edit – jej instancja przekazywana do każdej komendy (pluginu) w sublimie. Nie posiada ona żadnych metod publicznych i służy grupowaniu zmiar bufora, czyli w praktyce umożliwia użytkownikowi np. cofnięcie lub ponowienie zmian wprowadzonych przez nasz plugin.
  • Klasa Region – reprezentuje wycinek tekstu. Jest to w zasadzie tablica dwóch liczb + kilka pomocniczych metod.

Po zapoznaniu się z tutorialem i podstawami API Sublime’a zacząłem zastanawiać się, jak napisać mój plugin…

Krok 1 – szukamy zdania w linii

Pierwszą rzeczą, którą musi zrobić nasz program będzie znalezienie w linii, w kórej aktualnie znajduje się kursor Regionu obejmującego zdanie w pojedynczych (‚ ‚) lub podwójnych (” „) apostrofach. Aktualna pozycja kursora musi zawierać się w tym regionie. Postanowiłem, że najlepiej do tego sprawdzi się wyrażenie regularne.

Przy budowaniu wyrażeń regularnych bardzo przydatna jest strona https://pythex.org/ – zaoszczędziłem dzięki niej mnóstwo czasu.

Zacząłem od następującego wyrażenia:

(\”.*\”|(\’.*\’))

To wyrażenie jest niezłe, bo pozwala na dopasowanie zarówno do pojedynczych, jak i podwójnych apostrofów ale ma jedną wadę – jest „zachłanne”. Oznacza to, że w wypadku, gdy linia zawiera kilka stringów w apostrofach nasze wyrażenie znajdzie cały ciąg znaków znajdujący się pomiędzy pierwszym i ostatnim apostrofem w danym zdaniu. Czyli zamiast It requires a name” and „It requires a surname wyszuka „It requires a name” and „It requires a surname”. Na szczęście python posiada proste narzędzie, pozwalające to naprawić:

(\”.*?\”|(\’.*?\’))

Teraz wyrażenie działa tak, jakbyśmy tego oczekiwali.

Niestety moja radość nie trwała długo – kolejnym problemem, na który się natknąłem, był fakt, że moje wyrażenie nie radzi sobie z tzw. escaped quotes, czyli z pojedynczym apostrofem poprzedzonym znakiem backslash. Tutaj znowu python daje radę – zgodnie z regular expression cheatsheet ze strony pythex.org aby odfiltrować apostrofy poprzedzone backslashem można wykorzystać negative lookbehind assertion:

(?<!…)

Finalnie wyrażenie wygląda tak:

(\”.*?\”|((?<!\\)\’.*?(?<!\\)\’))

Po zbudowaniu odpowiedniego wyrażenia musiałem już tylko spośród dopasowanych ciągów znaleźć taki, którego region zawiera pozycję kursora:

Krok 2 – negujemy zdanie

Po znalezieniu zdania będziemy chcieli zamienić je na przeczące. Metoda, którą wymyśliłem jest dość naiwna, ale jak dobrze wiemy jeżeli coś jest głupie a działa, to nie jest głupie 🙂

Najpierw przeanalizujmy sobie, jakie są możliwe czasowniki w czasie present simple dla podmiotów w 3. osobie i liczbie pojedynczej i w jaki sposób je zanegować:

  • Czasownik nieregularny „is” -> aby zanegować dodajemy „not”
  • Czasowniki modalne –  „should” -> „shouldn’t”, „must” -> „mustn’t”, „has to” -> „does not have to”
  • Pozostałe czasowniki – czasownik+s -> does not czasownik z wyjątkami:
    • Czasowniki kończące się na -ies negujemy poprzez zamiane –ies na -y (np. flies -> does not fly). Specjalnym przypadkiem są słowa, które w formie bezokolicznikowej kończą się -ies (np. lies -> does not lie).
    • Czasowniki kończące się na -es poprzedzone zgłoskami „ss”, „x”, „ch”, „sh”, „o” negujemy poprzez usunięcie całej końcowki -es (np. goes -> does not go, misses -> does not miss)

Kod klasy SentenceNegator wygląda następująco:

Zasada jest prosta – dla wyżej wymienionych typów czasowników należy wykonać takie kroki:

  • Jeśli zdanie zawiera przeczenie czasownika danego typu zaneguj go i zwróć zdanie pozytywne
  • Jeśli zdanie nie zawiera przeczenia, to znajdź niezanegowany czasownik i spróbuj go zanegować
  • Jeśli zdanie nie zawiera czasownika (nie jest zdaniem) – zwróć wejściowy ciąg znaków

Warto wspomnieć, że takie podejście sprawdzi się tylko dla zdań jednokrotnie złożonych, niezawierających conditionali. Czyli np. „It has a value” zostanie poprawnie zmienione w „It does not have a value”, ale już „It has a value if other value is 0” może dać nieoczekiwany rezultat, czyli: „It has a value if other value is not 0”. W takim przypadku nasz program musiałby znać kontekst zdania, żeby móc je poprawnie zanegować.

Krok 3 – piszemy testy jednostkowe

Pierwszą rzeczą jaką zrobiłem zaraz po zapoznaniu się z API Sublime’a było zadanie sobie pytania „jak mam to przetestować?”. Stwierdziłem, że przy mojej niskiej znajomości Pythona szansa na wprowadzenie błędu przy którejś z kolei zmianie jest całkiem duża, a dobre pokrycie testami da mi pewność, że nic po drodze nie zepsuję (poza tym to po prostu dobra praktyka).

Tutaj z pomocą przyszła mi biblioteka https://github.com/randy3k/UnitTesting. Działa ona w bardzo prosty sposób – symuluje realne użycie naszego pluginu poprzez stworzenie nowego okienka edytora i wykonanie naszej komendy. Po odpaleniu komendy możemy sprawdzić czy tekst w oknie testowym jest zgodny z oczekiwanym.

Plik z zestawem testów należy umieścić w folderze tests naszej paczki.

Przykładowy plik z testem jednostkowym wygląda tak:

Krok 4 – dodajemy CI i pokrycie testami

Po dodaniu testów do projektu pomyślałem, że fajnym usprawnieniem będzie automatyczne uruchamianie testów w którymś z serwisów umożliwiających CI. Okazało się, że w repozytorium https://github.com/randy3k/UnitTesting można znaleźć gotową konfigurację do systemu Travis CI. Integracja z Travis CI odbyła się bezboleśnie – już pierwszy build zaświecił się na zielono.

Zrzut ekranu 2017-10-23 o 22.07.56.png
Wynik buildu w Travis CI

Poza integracją z CI postanowiłem również zintegrować się z codecov – serwisem pokazującym w jakim stopniu nasz projekt jest pokryty testami. Ponownie okazało się to banalne, ponieważ plik .travis.yml zawiera konfigurację, która automatycznie generuje plik z pokryciem testami. Jedyne co musiałem zrobić, to zalogować się w codecov.io i wskazać odpowiednie repozytorium z GitHuba.

Zrzut ekranu 2017-10-23 o 22.16.02.png
Widok projektu w codecov.io

Dzięki dodaniu ciągłej integracji i pokrycia testami mam pewność, że każda linia jest przetestowana, a mój projekt działa jak należy.

Oprócz tego zyskałem też takie czadowe badge, które dodałem do repozytorium w  GitHubie:

Zrzut ekranu 2017-10-23 o 22.20.05.png

Krok 5 – sprawdzamy plugin w akcji

Czas zobaczyć plugin w akcji. Można go przetestować wykorzystując jeden ze sposobów:

  • Na wybranym zdaniu nacisnąć kombinację klawiszy „^ + ⌘ + ⇧ + n” na Macu lub „CTRL + SHIFT + ALT + n” na windowsie/linuxie.
  • Nacisnąć „⌘ + ⇧ + P” i w okienku poleceń wybrać NegateSentence

Poniżej mała prezentacja:

plugin_in_action

Podsumowanie

Napisanie własnego pluginu do edytora, w którym codziennie spędza się po 8 godzin daje sporą satysfakcję, nawet jeżeli jest to super proste narzędzie.

Poza samą satysfakcją takie proste zadanko idealnie sprawdziło się jako poligon służący do nauki Pythona. Dwie pieczenie na jednym ogniu!

Całość projektu można znaleźć pod adresem https://github.com/Bajena/SublimeNegateSentence

Chrome DevTools część 4 – Analiza wydajności aplikacji

Czas na kolejną część cyklu o Chrome DevTools. Dzisiaj skupię się na temacie analizy wydajności aplikacji internetowych.

Samo hasło „wydajność” jest dość ogólne. Wydaje mi się, że o aplikacji webowej można powiedzieć, że jest wydajna, jeśli czas pomiędzy akcją użytkownika, a reakcją aplikacji jest mały (najlepiej max 100ms). Moim zdaniem niezbędne jest też to, by program był stabilny przez cały czas użytkowania. No bo co nam po tym, że aplikacja jest szybka, jeśli po minucie używania wywala się z powodu braku pamięci 🙂

A co wpływa na wydajność od strony technicznej? Są to:

  • Zużycie zasobów
    • Obciążenie procesora
    • Ilość używanej pamięci RAM
    • W przypadku bardziej skomplikowanych graficznie programów będzie to również pamięć karty graficznej
  • Czas odpowiedzi serwera – nawet jeśli mamy super szybki frontend, ale nasz backend jest wolny jak włóż od tyłu, to dla użytkownika aplikacja ciągle będzie niewydajna

Mimo że, tworząc frontend, na szybkość wykonywania zapytań nie mamy wielkiego wpływu, to już na wydajność od strony zużycia zasobów tak. Zobaczmy więc, w jaki sposób, korzystając z DevTools można znaleźć i wyeliminować problemy z nadmiernym zużyciem pamięci RAM czy procesora.

1. Przygotowanie

Przed rozpoczęciem warto przejść do trybu incognito (chyba nie muszę nikomu tłumaczyć jak to zrobić ( ͡° ͜ʖ ͡°) ). Dzięki temu naszych testów nie będą zaśmiecały Chrome’owe rozszerzenia, które potrafią wykonywać jakieś dodatkowe skrypty.

2. Analiza zużycia pamięci

W celu analizy zużycia pamięci wykorzystam przykład wycieku pamięci. Mimo, że jest on dość prosty, to założę się, że część z Was kiedyś się z nim spotkała w takiej czy innej formie.

Program ten po naciśnięciu przycisku Add numbers co 10ms doda 1000 elementów div z losową liczbą i z zakresu 0-10000000 oraz zapisze dodany element w obiekcie numbers[i]. 

Jeżeli wśród tych tysiąca liczb będzie liczba 10000000 program wyświetli alert z napisem SUCCESS, podświetli znaleziony element na czerwono i zakończy losowanie. W przeciwnym razie usunie liczby z DOMu i losuje od początku.

Odpalamy program, wszystko wydaje się być ok, gdyby nie to, że po kilku sekundach komputer zaczyna przymulać. Otwórzmy najpierw menedżer zadań (Opcje -> Więcej narzędzi -> Menedżer zadań) i zobaczmy, co się stało:

zrzut_zaznaczony.png
Menedżer zadań Chrome

Kurdelebele, nasz prosty program zużywa 1.8GB ramu :O Dlaczego? Sprawdźmy to, wykorzystując zakładkę Memory w DevTools. Możemy w tym celu wykorzystać genialny skrót klawiszowy, który ostatnio poznałem – ⌘ + ⇧ + P. Pokaże nam się okienko, dzięki któremu możemy dostać się do większości miejsc w DevTools. My wykorzystamy je do przejścia do zakładki Memory:

Developer_Tools_-_https___wordpress_com_post_mycodeworksiknowwhy_wordpress_com_604

Zakładkę Memory wykorzystamy, aby sprawdzić, na co zużywamy najwięcej pamięci. Użyjemy w tym celu opcji Take heap snapshot, aby wyświetlić zawartość sterty po kilkunastu sekundach pracy naszego programu:

snapshot.png
Snapshot sterty z wyciekiem pamięci

Tym, co rzuca się w oczy jest duża liczba obiektow typu HTMLDivElement. Jak to możliwe, że te div-y ciągle siedzą w pamięci, skoro za każdym razem czyścimy nasz widok z niepotrzebnych elementów za pomocą $(‚#numbers’).html(”) ? Otóż te div-y mimo , że nie są już w drzewie DOM, to ciągle istnieją do nich referencje z obiektu numbers, przez co nie mogą zostać usunięte z pamięci przez JavaScriptowy garbage collector. Możemy to poprawić wprowadzając jedną zmianę do funkcji checkMaxPresent:

Resetując obiekt numbers usuwamy referencje do „martwych” obiektów DOMu i pozwalamy na zwolnienie zajmowanej przez nie pamięci. Zobaczmy, jak teraz wygląda nasz zrzut pamięci:

Zrzut ekranu 2017-09-23 o 16.34.28
Snapshot sterty po poprawce

Udało nam się pozbyć wycieku pamięci – na stercie nie ma już tysięcy obiektów typu HTMLDivElement.

3. Porównanie wydajności – zakładka „Performance”

Przeanalizujmy sobie jeszcze raz powyższy program przed i po usunięciu wycieku (czas w setInterval został zmieniony na 1000ms). Tym razem wykorzystamy do tego zakładkę Performance. Zakładka ta pozwala na bardziej kompleksową analizę całej aplikacji.

performance.png
Analiza przed i po usunięciu wycieku

Na powyższym obrazku widać kiedy procesor był najbardziej obciążony – było to co sekundę, gdy odpalała się funkcja addRandomNumbers. Oprócz tego można zauważyć, że wykres zużycia pamięci dla obydwu przypadków wygląda inaczej – w pierwszym krzywa jest wyraźnie rosnąca. W drugim natomiast po około 10s spada ona niemalże do stanu początkowego. Przybliżmy ten moment i zobaczmy co się stało:

garbacecollection.gif
Wywołanie garbage collectora

Do spadku przyczyniły się akcje DOM GC oraz Major GC, czyli z naszego programu nie wycieka pamięć.

4. Podsumowanie

Zakładka Performance pozwala nam zobaczyć jakie akcje powodują zmiany w zużyciu pamięci przez naszą aplikację na przestrzeni czasu, a zakładka Memory, informuje o tym, na co dokładnie w danej chwili zużywana jest pamięć.

Przykład, który przygotowałem, może i był prosty, ale pokazuje, że użycie tych dwóch narzędzi ułatwia znalezienie często trudnych do odkrycia problemów, jakimi są wycieki pamięci.

 

Chrome DevTools part 3 – Breakpoints

Po prawie miesiącu przerwy wracam z kolejną częścią serii o Chrome DevTools. Tym razem zajmę się sprawą debugowania JavaScriptu. Dla niektórych to może być zaskoczeniem, ale debugowanie aplikacji klasyczną metodą alert(‚dupa’) już dawno przestało być szczytem możliwości 🙂

Chrome pozwala na zatrzymywanie i analizę wykonywania skryptów za pomocą breakpointów. Okazuje się, że breakpointy można ustawiać na kilka różnych sposobów. Dzisiejszy post jest przeglądem tych metod.

Na potrzebę tej części przygotowałem prostą aplikację, która losuje sto liczb i wypisuje je na stronie.

1. Line of code

Najprostsza metoda debugowania – zatrzymuje program zawsze podczas wykonania danej linii. Taki breakpoint można ustawić klikając lewym klawiszem myszy na numer linii.

Ten sam efekt można osiągnąć wpisując debugger w kodzie aplikacji.

Zrzut ekranu 2017-07-04 o 19.48.49

 

 

 

2. Breakpoint warunkowy

Załóżmy, że interesuje nas stan aplikacji jedynie w ostatnim obiegu pętli for. Nie musimy dodawać do naszego kodu dodatkowego warunku rodzaju:

if (i == 99) { debugger; }

Zamiast tego można użyć breakpointu warunkowego. W tym celu należy kliknąć prawym przyciskiem myszy na wybraną linie i podać warunek zatrzymania.

cond
Breakpointy warunkowe

3. DOM breakpoints

Kolejnym sposobem na debugowanie aplikacji są DOM breakpoints. Mogą być one przydatne, gdy np. wiele miejsc w kodzie modyfikuje element drzewa strony. Wtedy zamiast stawiać pojedyncze breakpointy w każdym z tych miejsc możemy dodać DOM breakpoint. 

Zmodyfikujmy nieco kod naszego dema – zamiast pętli użyjemy dwóch timerów dodających liczby do elementu #numbers.

Załóżmy, że interesuje nas, który z timerów dodał konkretną liczbę. Ustawmy w tym celu DOM breakpoint zatrzymujący program, gdy zawartość #numbers ulegnie zmianie (subtree modifications).

dom.gif
DOM breakpoint

Debugger zatrzyma teraz program, gdy jeden z dwóch timerów spróbuje dodać element do #numbers.

Oprócz subtree modifications mamy również do dyspozycji attribute modifications (zatrzymuje program, gdy atrybut elementu ulegnie zmianie) oraz node removal (gdy element zostanie usunięty).

4. XHR breakpoints

Aplikacje JavaScriptowe bardzo często wykonują setki zapytań do zewnętrznych serwisów. Czasem może się to wymknąć spod kontroli i może zdarzyć się, że nasza aplikacja wywołuje nadmierną liczbę zapytań lub pyta zły adres.

Aby zbadać, które miejsce w naszym kodzie wywołuje dane zapytanie możemy zdefiniować XHR breakpoint, który zatrzyma program w momencie, gdy wywołane zostanie zapytanie, którego adres URL zawiera wybraną przez nas frazę.

Zmodyfikujmy nasz skrypt tak, by jeden z timerów pobierał losową liczbę z serwisu random.org za pomocą funkcji fetch (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch):

Dodajmy teraz XHR breakpoint, który zatrzyma program, gdy wykonane zostanie zapytanie do random.org:

xhr.gif
XHR breakpoints

5. Event listener breakpoints

Chrome pozwala na dodawanie breakpointów, które reagują na javascriptowe eventy takie, jak kliknięcie, czy wpisywanie tekstu. Więcej o Event Listenerach można przeczytać poprzedniej części serii.

6. Exception breakpoints

Fajną, choć momentami upierdliwą funkcją są breakpointy wyzwalane w momencie wystąpienia wyjątku. Zmieńmy nasz program tak, by losował tylko 0 lub 1. Gdy wylosowane zostanie 0 rzucony zostanie nieobsłużony wyjątek. Gdy wylosowane zostanie 1, rzucamy i łapiemy wyjątek.

Kliknięcie ikonki pauzy spowoduje zatrzymanie programu, gdy wylosowane zostanie 0. Dodatkowo, jeśl włączymy checkbox Pause on caught exceptions, to program zatrzymany zostanie również wtedy, gdy wylosowana zostanie jedynka.

exc.gif
Exception breakpoints

7. Function breakpoints

Fajną opcją Chrome’a, której wcześniej nie znałem są function breakpoints. Takie breakpointy zatrzymują program, gdy wywołana zostanie dana funkcja. Według dokumentacji DevToolsów aby dodać taki breakpoint należy w kodzie naszej aplikacji dodać linię:

debug(funkcja)

Zmienna funkcja jest identyfikatorem funkcji, którą chcemy śledzić.
Niestety, mi nie udało się odpalić tego breakpointa (ReferenceError) z poziomu kodu i zadziałał dopiero gdy ustawiłem zwykły breakpoint na początku programu i wpisałem w konsoli chrome’a debug(myFunction). 

func
Function breakpoints

Podsumowanie

Mimo, że najczęściej przy debugowaniu używamy najprostszej metody – zwykłego zatrzymania na danej linijce kodu, to uważam, że warto mieć gdzieś z tyłu głowy pozostałe opisane przeze mnie techniki. Być może pozwolą nam zaoszczędzić kiedyś sporo czasu.

W następnej części zobaczymy, jak za pomocą DevTools analizować wydajność naszej aplikacji.

Chrome DevTools część 2 – Event Listeners

Czas na kolejną funkcję DevToolsów, o której zawsze wiedziałem, że jest, ale nigdy jej nie użyłem, czyli Event Listeners.

Prosty przykład

Zakładka event listeners, jak nietrudno się domyślić, pozwala na przeglądanie javascriptowych eventów zbindowanych do danego elementu.

Zacznijmy klasycznie od prostego przypadku. Przygotowałem w tym celu mały przykład:

Po otwarciu zakładki Event Listeners dla wrappera widzimy event handler dodany przez mój kod:

Zrzut ekranu 2017-06-13 o 21.36.48.png
Listener dla wrappera

Zobaczmy teraz co zostanie wyświetlone dla elementu button:

Zrzut ekranu 2017-06-13 o 21.47.28.png
Listenery dla guzika

Co tu się odjaniepawliło? Skąd listener wrappera w listenerach buttona? Okazuje się, że Chrome domyślnie pokazuje listenery nie tylko dla wybranego elementu, ale również dla jego przodków w drzewie. Odzaczenie checkboxa Ancestors spowoduje odfiltrowanie listenerów przodków.

Czas pobrudzić sobie ręce

Wszystko wydaje się proste, a jednak przy próbie podejrzenia event listenerów dla prostego guzika w Emberowej aplikacji zastałem następujący widok:

Zrzut ekranu 2017-06-11 o 21.12.25
Początkowa lista listenerów

Wklejony_obraz_13_06_2017__23_05
Wybrany guzik

Dziwne, skąd tyle listenerów zbindowanych do prostego przycisku? Ok, zobaczmy co stanie się po odznaczeniu Ancestors:

Zrzut ekranu 2017-06-11 o 21.52.21
Lista listenerów po odznaczeniu opcji „Ancestors”

Odznaczenie opcji Ancestors ograniczyło nieco liczbę listenerów, jednak wciąż jest ona dość duża jak na przycisk. Tutaj z pomocą przychodzi checkbox Framework Listeners. Po odznaczeniu tej opcji odfiltrowane zostały listenery dodane przez bibliotekę Ember.js, dzięki czemu w końcu znalazłem konkretny listener zbindowany przez mój własny kod do przycisku.

Zrzut ekranu 2017-06-11 o 21.56.16
Event listener zdefiniowany przez naszą aplikację

Event listener breakpoints

Ok, fajnie, wiem już, że do mojego przycisku przypisany jest event handler. Spróbujmy teraz wykorzystać Event listener breakpoints w zakładce Sources aby sprawdzić handler w akcji. Na rozwijanej liście zaznaczamy mouse -> click, dzięki czemu kliknięcie w dowolny element ze zdefiniowanym handlerem dla eventu click spowoduje wstrzymanie wykonania kodu.

Zgodnie z oczekiwaniami kliknięcie w przycisk spowodowało odpalenie debuggera. Kod zatrzymał się jednak w niezbyt interesującym miejscu – we wrapperze dodanym przez zewnętrzną bibliotekę (new relic). Można sobie z tym poradzić poprzez kliknięcie prawym przyciskiem w oknie debuggera i wybranie opcji Blackbox script.

Zrzut ekranu 2017-06-13 o 23.12.41.png
Dodawanie skryptu do listy „Blackboxed scripts”

Po dodaniu odpowiednich wpisów do listy blackboxed scripts i ponownym kliknięciu w guzik debugger odpalił się już w miejscu, które mnie interesowało:

Wklejony_obraz_13_06_2017__23_22
Breakpoint w poprawnym miejscu

Monitorowanie zdarzeń

W przypadku, gdy nie interesuje nas jak zostało obsłużone pewne zdarzenie, a jedynie czas jego wywołania przydatna może okazać się funkcja monitorEvents. W inspectorze należy zaznaczyć interesujacy element, a następnie wpisać:

monitorEvents($0)

każde zdarzenie, które wystąpi dla zaznaczonego elementu zostanie zalogowane w konsoli:

Zrzut ekranu 2017-06-13 o 23.34.39
Logowanie zdarzeń przy uzyciu monitorEvents

Monitorowanie można ograniczyć też do konkretnych eventów, podając nazwę zdarzenia w drugim parametrze funkcji:

monitorEvents($0, ‚click’)

Monitorowanie można zakończyć używając polecenia:

unmonitorEvents($0)

Bonus

Jeśli korzystamy z biblioteki jQuery, to szczegóły event handlerów dla aktualnie wybranego elementu możemy podejrzeć wpisując

$._data(($0), ‚events’)

Zrzut ekranu 2017-06-13 o 23.42.40.png
Lista event handlerów w jQuery

Czasami ten sposób może okazać się szybszy niż przekopywanie się przez zakładkę Event listeners.

Podsumowanie

W tym poście zaprezentowałem kilka sposobów na analizę zdarzeń za pomocą Chrome DevTools. Dowiedzieliśmy się, jak przeglądać listę event listenerów, ustawiać automatyczne breakpointy dla event listenerów oraz jak włączyć logowanie zdarzeń dla elementów drzewa.

W kolejnym poście serii przeanalizuję temat DOM breakpoints.