Szukasz najlepszych specjalistów od oprogramowania? Znajdziesz ich w kilka kliknięć.

Asynchroniczne przetwarzanie obrazów w Ruby on Rails z Shrine

3 gru 20203 min czytania

Piotr Wald 

Inżynier backendu doświadczony w budowaniu API w Ruby.

Jako programiści Ruby on Rails często stajemy przed potrzebą wgrywania i przechowywania obrazów dostarczanych przez użytkowników. 


Często obrazy te trzeba przeskalować i zapisać w wielu wersjach: na mobile, na web, jako miniatury, obrazy OG oraz inne niestandardowe formaty potrzebne klientowi.

 

Do jakiego problemu to prowadzi? Przetwarzanie obrazów to operacja obciążająca obliczeniowo i opóźnienia nieuchronnie wzrosną.

 

Rozwiązanie? Sidekiq + Shrine!

Więc… co można z tym zrobić? Jeśli jesteś taki jak ja, chcesz zjeść ciastko i mieć ciastko. 

 

W tym wpisie przejdziemy przez to, jak uzyskać szybkie czasy odpowiedzi, generując jednocześnie wiele wersji obrazu. 

 

Rozwiązanie jest proste. Użyjemy Sidekiqa, by zaplanować zadanie przetwarzania obrazu i natychmiast zwrócić odpowiedź ze statusem 200.

 

Do wgrywania obrazów wykorzystamy Shrine. To nowoczesna biblioteka o wtyczkowej budowie, alternatywa dla gemów takich jak Carrierwave czy Paperclip. Bardzo ją lubię i polecam używać jej w projektach - ma naprawdę świetną dokumentację, czego nie sposób przecenić.

 

Jak to działa w praktyce?

Przewodnik krok po kroku

Całą aplikację zobaczysz na moim Githubie.

rails new shrine-uploader-api --api --database=postgresql && cd shrine-uploader-api && rails db:create

Dodaj do swojego Gemfile'a:

 

 

I uruchom:

bundle

Skorzystajmy z generatora zasobów z Rails CLI. Stworzymy tabelę uploads z kolumną image_data, niezbędną do działania Shrine:

rails g resource Upload image_data:jsonb && rails db:migrate

Najpierw musimy skonfigurować Shrine. 

 

Zadeklarujemy 2 rodzaje magazynów: 

  • „cache” na surowe obrazy 
  • „store” na obrazy przetworzone. 

W tej sekcji zadeklarujemy też wtyczki do integracji z ActiveRecord, generowania wariantów (derivatives) i przetwarzania w tle. Ostatnia część rejestruje callback wywoływany przy „promote event”. Zdarzenie promote uruchamia się po zapisaniu naszego rekordu „Upload” w bazie danych.

 

 

Następnie dodajmy uploader Shrine ze specyfikacją wariantów, które chcemy generować. Przetwarzaniem obrazów zajmuje się pod spodem imagemagick:

 

 

 

Podłączmy uploader do naszego modelu. Do obrazów można teraz sięgać przez wirtualne pole image:

 

 

 

Musimy zaimplementować PromoteJob, którego użyliśmy w naszym inicjalizatorze. Zadanie to trafi do Redisa przy zdarzeniu promocji i zostanie później wykonane w Sidekiqu. Oto jego kod:

 

 

 

Na koniec zostaje zaimplementowanie „create action” w UploadsControllerze.

 

 

 

I gotowe! Kod mamy z głowy!

 

Uruchommy teraz serwer Redis, którego Sidekiq używa do zapisywania i pobierania zadań. Ja użyję oficjalnego obrazu dockerowego, ale możesz uruchomić Redisa ze swojego systemu, jeśli wolisz :)

docker run --name my-redis -d --publish 6379:6379 redis

I uruchom sidekiq:

bundle exec sidekiq

Uruchom serwer Rails w drugiej karcie terminala:

rails s

I gotowe! Nasza aplikacja działa :) 

 

Mamy endpoint http://localhost:3000/uploads, na który możemy wysyłać obrazy - zostaną przetworzone zgodnie z naszą specyfikacją. 

 

A teraz to przetestujmy!

 

Najpierw potrzebujemy obrazu do wgrania. Może być dowolny. W moim przypadku to plik, który nazwałem example.png

curl -X POST -i -F [email protected] localhost:3000/uploads

Celem jest uzyskanie odpowiedzi 200, a następnie czterech obrazów w folderze public/uploads: oryginału i 3 kopii przeskalowanych zgodnie z regułami z ImageUplaoder.

 

W logach Sidekiqa zobaczysz też, że PromoteJob został zaplanowany i pomyślnie wykonany.

 

Sprawdźmy teraz, jak wygląda kwestia wydajności.

 

Najpierw mała sztuczka, by uzyskać czas odpowiedzi z curla. Stworzę plik curl-format.txt o poniższej zawartości i użyję go później jako argumentu.

time_total:  %{time_total}sNasz obraz wyślemy w postaci binarnej, używając flagi -F [email protected].

 

 

15 ms, nieźle ;) 

 

Przeprowadziłem kilka testów bez Sidekiqa do asynchronicznego przetwarzania obrazów - zajmowały około 120 ms. To 8 razy wolniej niż z przetwarzaniem w tle!

Podsumowanie

Bądźmy szczerzy. Nie każda aplikacja w Rails potrzebuje przetwarzania obrazów w tle.

 

Są jednak sytuacje, gdy to naprawdę dobry pomysł. Wyobraź sobie budowę API, do którego co sekundę trafia wiele obrazów. Albo załóżmy, że robisz bardzo złożone transformacje obrazów i trwa to znacznie dłużej niż w naszym przykładzie.

 

W takich przypadkach opóźnienia mogą stać się odczuwalne, a ta prosta sztuczka, którą opisałem powyżej, z pewnością pomoże Ci utrzymać świetną wydajność aplikacji.

 

Miłego kodowania!

Piotr Wald 

Piotr jest inżynierem backendu doświadczonym w budowaniu API w Ruby. Jako zwolennik najlepszych praktyk inżynierskich dąży do czystego i wydajnego kodu.

Zobacz wszystkie wpisy autora
im_ebook_cover_template 4 (2)

Wybór Ruby on Rails do Twojego kolejnego projektu webowego

Kompletny przewodnik biznesowy

Czytaj teraz
Newsletter 9-1
Newsletter Ideamotive
Twój dwutygodniowy przegląd najgorętszych newsów technologicznych

Szukasz świetnych projektów jako programista Ruby on Rails?

Dołącz do Ideamotive Talent. Pracuj przy międzynarodowych projektach, zarabiaj i rozwijaj karierę na własnych zasadach.