5 sty 20184 min czytania
Dawid Karczewski
Senior full stack developer i CTO w Ideamotive.
Widoki zmaterializowane nie są czymś powszechnie używanym w aplikacjach Ruby on Rails. Ostatnio jednak spróbowałem ich użyć i wyniki okazały się bardzo satysfakcjonujące.
W tym studium przypadku chciałbym przedstawić prostą aplikację wykorzystującą Ruby 2.4.1, Rails 5.1.4, PostgreSQL 10 oraz gem scenic.
Załóżmy, że Twoją aplikację szczególnie interesuje połączone zestawienie zapisów pogodowych i lokalizacji miast, ale nie chcesz wpisywać tego zapytania za każdym razem, gdy go potrzebujesz. Możesz utworzyć nad zapytaniem widok, który nadaje mu nazwę, dzięki czemu odwołasz się do niego jak do zwykłej tabeli:
Szerokie korzystanie z widoków to kluczowy element dobrego projektowania baz danych SQL. Widoki pozwalają ukryć za spójnymi interfejsami szczegóły struktury tabel, która może się zmieniać wraz z rozwojem aplikacji.
Widoków można używać niemal wszędzie tam, gdzie da się użyć prawdziwej tabeli. Budowanie widoków na innych widokach nie jest niczym niezwykłym.
To po prostu bardzo wygodny sposób na to, by nie pisać skomplikowanych zapytań. Nie pomaga jednak w wydajności – skomplikowane zapytanie i tak wykonuje się za każdym razem.
Na szczęście widoki można zmaterializować. Zajrzyjmy ponownie do dokumentacji PostgreSQL:
Widoki zmaterializowane w PostgreSQL korzystają z systemu reguł tak samo jak zwykłe widoki, ale zapisują wyniki w formie zbliżonej do tabeli. Główne różnice między:CREATEMATERIALIZED VIEWmymatview ASSELECT* FROMmytab;
oraz:CREATETABLEmymatview ASSELECT* FROMmytab;
polegają na tym, że widoku zmaterializowanego nie da się później bezpośrednio aktualizować, a zapytanie użyte do jego utworzenia jest przechowywane dokładnie tak samo jak zapytanie zwykłego widoku, dzięki czemu świeże dane można wygenerować dla widoku zmaterializowanego poleceniem:REFRESH MATERIALIZED VIEWmymatview;
Mówiąc prosto, wyniki zapytania widoku są przechowywane w bazie danych – tak jak w każdej innej tabeli. Jedyna różnica polega na tym, że nie możemy aktualizować widoku bezpośrednio, ale można go odświeżyć rekordami z tabel źródłowych.
Dzięki temu zamiast wykonywać kosztowne zapytania, możemy korzystać z danych ułożonych w prostszy sposób w widoku zmaterializowanym.
Tutaj znajdziesz bardzo prostą aplikację, którą stworzyłem na potrzeby tego wpisu. To przykład rzeczywistego problemu, na jaki się natknąłem, sprowadzonego w repozytorium do samej istoty.
Pomysł jest prosty. Użytkownicy mają uprawnienia. Uprawnienia nie są jednak przypisywane bezpośrednio do użytkowników. Użytkownicy należą do grup, a grupy mają wiele uprawnień.
Jest 5 modeli:
Bardzo prawdopodobne, że widziałeś już podobny wzorzec – grupowanie rekordów o podobnym przeznaczeniu. Nie ma w tym projekcie nic skomplikowanego. Pobranie informacji może jednak wymagać kilku joinów SQL.
Żeby mieć jakieś dane do pracy i sprawdzania wyników, przygotowałem plik seeds.rb. Tworzy on wszystkie uprawnienia oraz 100 użytkowników, z których każdy należy do 3 grup uprawnień. Grup jest 10, a każda z nich ma 5 uprawnień.
Wystarczy uruchomić rails db:seed i baza wypełni się danymi.
Załóżmy, że chcemy sprawdzić, czy dany użytkownik ma uprawnienia :a i :b jednocześnie:
Przekłada się to na następujące zapytanie SQL:
Co dostaniemy z EXPLAIN?
Jak widać, większość skanów korzysta z indeksów, więc niewiele możemy zrobić, żeby przyspieszyć.
Napisałem prosty benchmark jako zadanie rake. Na zaseedowanych danych sprawdzam 1000 razy, czy użytkownik ma 1 uprawnienie, potem 2 losowe uprawnienia, 3, 4 i w końcu 5.
rake benchmark_permissions
user system total real
joins - 1 permission 1.750000 0.050000 1.800000 ( 2.266476)
joins - 2 permissions 1.840000 0.060000 1.900000 ( 3.427442)
joins - 3 permissions 1.860000 0.070000 1.930000 ( 3.497956)
joins - 4 permissions 1.880000 0.060000 1.940000 ( 3.568906)
No dobrze, niewiele nam to mówi. Czy da się to przyspieszyć?
Tu robi się ciekawie. Ludzie z thoughtbot stworzyli bardzo fajny gem o nazwie scenic. Ułatwia zarządzanie widokami bazodanowymi w Rails. Polecam Ci go wypróbować.
Dla naszego problemu moglibyśmy stworzyć takie rozwiązanie:
rails generate scenic:view permissions_check_results
Generuje to dwa pliki. Pierwszy z nich to db/views/permissions_check_results_v01.sql, który wypełniłem poniższym kodem:
Co się tu dzieje? Tworzymy prosty widok z dwiema kolumnami: user_idi permission_name (zwróć uwagę, że używamy enuma – permission_name jest przechowywane jako liczba całkowita).
| uder_id | permission_name |
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
W powyższym przykładzie użytkownik o id 1 ma dwa uprawnienia: 1 i 2, a użytkownik o id 2 ma tylko jedno uprawnienie: 3. Zamiast łączyć wiele tabel i sprawdzać uprawnienia przez grupy, mamy teraz bardzo prostą, dwukolumnową tabelę (widok).
Drugi plik wygenerowany przez zadanie scenic to migracja bazy danych: db/migrate/[TIMESTAMP]_create_permissions_check_results.rb
Ważne jest dodanie opcji materialized: true do migracji. Musimy też stworzyć model.
Czas porównać nasze wcześniejsze wyniki benchmarku z nowymi. Korzystając z kodu z początku artykułu i dodając kilka nowych linii:
rake benchmark_permissions
user system total real
joins - 1 permission 1.840000 0.050000 1.890000 ( 2.328792)
joins - 2 permissions 1.860000 0.070000 1.930000 ( 3.452119)
joins - 3 permissions 1.880000 0.060000 1.940000 ( 3.519821)
joins - 4 permissions 1.950000 0.070000 2.020000 ( 3.664984)
view - 1 permission 0.780000 0.060000 0.840000 ( 1.272834)
view - 2 permissions 0.770000 0.050000 0.820000 ( 1.310658)
view - 3 permissions 0.780000 0.060000 0.840000 ( 1.291651)
view - 4 permissions 0.800000 0.050000 0.850000 ( 1.329006)
Nowe wyniki są 3 razy szybsze. To całkiem niezły rezultat. Część z Was może mieć wątpliwości:
Hej, przecież to tylko odczyt. A co z aktualizowaniem rekordów w bazie?
Masz rację – nie wspomniałem o tym ani tego nie testowałem. Powód jest prosty – nie przejmuję się tym. Uprawnienia w moim przypadku aktualizują się bardzo rzadko. Z drugiej strony muszę je pobierać bardzo często. Znacznie bardziej zależy mi na czasie odczytu niż zapisu.
Jeśli musisz pisać dużo joinów, żeby wyciągnąć informacje z bazy danych, rozważ stworzenie widoku zmaterializowanego.
Dawid jest full stack developerem. Tworzy aplikacje w Ruby on Rails i React Native od pierwszego pomysłu aż po wdrożenie. Buduje dla naszych klientów rozwiązania, które pomagają im rosnąć.
Zobacz wszystkie wpisy autoraPopularne artykuły
21 inspirujących projektów UI aplikacji mobilnych na 2023 rok
Michał Pruciak 7 min czytania
MedTech vs HealthTech vs BioTech: jakie są różnice?
Michał Pruciak 7 min czytania
10 biznesowych zastosowań sieci neuronowych (z przykładami!)
Michał Pruciak 4 min czytania
10 przykładów dobrych praktyk w web designie na 2023 rok
Adam Kozłowski 7 min czytania
28 znanych aplikacji webowych napisanych w PHP
Dawid Karczewski 14 min czytania
Jakich usług programistycznych szukasz?
Dołącz do Ideamotive Talent. Pracuj przy międzynarodowych projektach, zarabiaj i rozwijaj karierę na własnych zasadach.
Dane rejestrowe:
Firma
Usługi
Najczęściej poszukiwani specjaliści
Ocena 4,8 / 5,0 od klientów z różnych branż i lokalizacji.