Skip to content
← Wszystkie wpisy
4 min czytania Dawid Skłodowski

Rails 4.2: ActiveJob, Adequate Record i klucze obce

Release candidate Rails 4.2 jest już dostępny. Spojrzenie na trzy najważniejsze zmiany — ujednolicone API zadań w tle, po cichu duży zysk wydajności w ActiveRecord i klucze obce wreszcie lądujące w migracjach.

Release candidate Rails 4.2 wylądował, a finał niedaleko za nim. To skupione wydanie — trzy sztandarowe funkcje i garść mniejszych — ale każdy nagłówek dotyka czegoś, co programiści obchodzili latami. Uruchamialiśmy bety i oto co 4.2 faktycznie zmienia w sposobie budowania aplikacji Rails.

ActiveJob: jedno API do pracy w tle

Każda nietrywialna aplikacja Rails wypycha wolną pracę w tło — wysyłanie maili, przetwarzanie uploadów, wołanie API stron trzecich. Kłopot zawsze polegał na tym, że biblioteki kolejkowania są niekompatybilne: Sidekiq, Resque, Delayed Job i reszta mają każda własny sposób definiowania i kolejkowania zadania. Zmień backend, a przepisujesz cały kod zadań; napisz gem potrzebujący pracy w tle, a nie możesz wiedzieć, którego backendu używa aplikacja gospodarza.

ActiveJob to naprawia, będąc warstwą adaptera, której Railsom brakowało — jednym, niezależnym od backendu API do deklarowania i kolejkowania zadań:

class ThumbnailJob < ApplicationJob
  queue_as :default

  def perform(upload)
    upload.generate_thumbnail!
  end
end

ThumbnailJob.perform_later(upload)     # zakolejkuj
ThumbnailJob.set(wait: 1.hour).perform_later(upload)   # zakolejkuj, z opóźnieniem

Piszesz zadania raz wobec ActiveJob, a jednolinijkowa konfiguracja wybiera faktyczny backend kolejki:

config.active_job.queue_adapter = :sidekiq

Zamień :sidekiq na :resque albo :delayed_job, a nie zmienia się ani linia kodu zadań. Dwie rzeczy czynią to czymś więcej niż cienkim opakowaniem. Po pierwsze, GlobalID: ActiveJob serializuje obiekty ActiveRecord globalnym identyfikatorem i przeładowuje je w workerze, więc możesz przekazać model do perform_later i otrzymać model (a nie surowe id) w perform — drobna wygoda usuwająca sporo boilerplate’u. Po drugie, integracja z Action Mailer: deliver_later wysyła teraz mail przez ActiveJob, więc „wyślij ten mail w tle” to wreszcie jednolinijkowiec na poziomie frameworka, niezależny od backendu, zamiast kleju per-backend.

Uczciwe zastrzeżenie: ActiveJob to abstrakcja nad backendami, które wciąż różnią się realnie (ponawiania, obsługa martwych zadań, współbieżność). Standaryzuje interfejs, nie każde zachowanie, więc wciąż wybierasz Sidekiqa albo Resque ze względu na ich faktyczne cechy — tylko przestajesz wiązać kod zadań z tym wyborem.

Adequate Record: darmowa wydajność

Najbardziej zuchwale nazwana funkcja jest też tą, do której zdobycia nie robisz nic. „Adequate Record” to znacząca wewnętrzna optymalizacja ActiveRecord cache’ująca wyliczony SQL i prepared statements dla częstych wzorców zapytań. Za pierwszym razem, gdy uruchamiasz konkretny kształt zapytania, AR wykonuje pracę jego zbudowania; kolejne zapytania tego samego kształtu reużywają cache’owany plan i pomijają część tego narzutu.

# DRUGIE i późniejsze wywołania tego kształtu są zauważalnie szybsze,
# bo wygenerowany SQL / prepared statement jest cache'owany
Post.find(1)
user.posts.where(published: true).to_a

Raportowane przyspieszenia na częstych operacjach jak find i ładowanie asocjacji to mniej więcej 2× poprawa, a dostajesz je przez aktualizację — bez zmian kodu, bez nowego API. Dla aplikacji mocno czytających, wykonujących te same kształty zapytań miliony razy, to znacząca, darmowa wygrana. To dokładnie ten rodzaj nieefektownej pracy w rdzeniu, który czyni aktualizację Railsów wartą jej, nawet gdy lista nagłówkowych funkcji wygląda krótko.

Klucze obce w migracjach, nareszcie

Ta jest spóźniona i bardzo mile widziana. Przez lata ActiveRecord pozwalał deklarować asocjacje w modelach, zostawiając bazę bez faktycznego ograniczenia klucza obcego — więc integralność referencyjna była egzekwowana (w najlepszym razie) przez kod aplikacji i całkiem nieobecna na poziomie bazy. Usuń rekord, na który wskazywał inny wiersz, a baza wzruszy ramionami; zostawałeś z sierotami i wiszącymi referencjami, które jakaś walidacja miała powstrzymać, ale przy współbieżności lub zabłąkanym skrypcie nie powstrzymała.

4.2 wnosi klucze obce do DSL migracji jako pierwszoklasowych obywateli:

class AddForeignKeys < ActiveRecord::Migration
  def change
    add_foreign_key :comments, :posts
    add_foreign_key :comments, :users, on_delete: :cascade
  end
end

# albo inline przy tworzeniu tabeli:
create_table :comments do |t|
  t.references :post, foreign_key: true
  t.references :user, foreign_key: true
end

Teraz baza gwarantuje, że komentarz nie może wskazywać na nieistniejący post, a on_delete: :cascade pozwala zepchnąć reguły usuwania tam, gdzie ich miejsce. To baza robiąca robotę, w której jest wyjątkowo dobra — egzekwowanie niezmienników, które muszą zachodzić bez względu na to, jaki kod, skrypt czy wyścig próbuje je złamać. Długo dodawaliśmy je ręcznie w surowych migracjach SQL; posiadanie ich w DSL oznacza, że są teraz łatwą, domyślną ścieżką, a więcej aplikacji dostanie integralność, którą powinny były mieć od początku. (Postgres i MySQL to dostają; wsparcie SQLite jest ograniczone, jak zwykle.)

Mniejsze dodatki

Jeszcze kilka wartych odnotowania: Web Console daje ci interaktywną sesję irb wprost w przeglądarce na stronie błędu (i przez console w widoku) — naprawdę użyteczne wsparcie debugowania, choć takie, które trzymaj zdecydowanie z dala od produkcji. render zyskuje możliwość renderowania szablonów spoza własnych ścieżek widoków kontrolera. I jest zwykła runda usprawnień ActiveSupport oraz porządków z deprecjacjami, gdy framework dalej się sprząta.

Werdykt

4.2 to wysoko wartościowa, niskotarciowa aktualizacja. ActiveJob to standaryzacja, której przetwarzanie w tle zawsze potrzebowało, i od razu czyni twój kod zadań przenośniejszym, a mailery łatwiejszymi; Adequate Record wręcza ci realną poprawę wydajności za cenę bundle update; a klucze obce zamykają lukę poprawności, która po cichu gryzła aplikacje Rails przez dekadę. Nic z tego nie wymaga przepisywania czegokolwiek — wdrażaj ActiveJob, dotykając swoich zadań, dodawaj klucze obce w następnej migracji i ciesz się przyspieszeniem za darmo. To Rails dojrzewający w najlepszy sposób: nie goniący nowości, lecz naprawiający rzeczy, które doświadczone zespoły nauczyły się obchodzić.