Workery i procesy back-endowe

Od kilku dni mędzę się z PDF-generatorem, który by w końcu zadziałał na heroku. U mnie na “dev” nie miałem do tej pory z tym żadnych problemów. Ten sam kod działa jak należy.

Jednak na heroku PDFKit i Prawn powodują Timeout po 30 sekundach. Problem ten dotyczy ładowania nawet jednego najmniejszego i najmniej zajmujcego obrazka z url. Heroku logs mówią o Timeout tylko.

Ponadto nie tylko PDF-generatory. Gdy chcę przy pomocy małeko kawałka kodu z url pobrać obrazek i zapisać go w /tmp to także jest Timeout. Obrazki są na Amazon S3, jako serwer plików dla heroku.

@file_name = name.jpg
@file_directory = directory

Code:
require ‘open-uri’
open(Rails.root+"/tmp/"+@file_name, ‘wb’) do |file|
file << open(@file_directory+@file_name).read
end

I proces trwa aż do Timeout. U mnie na komputerze na dev wszystkie rozwiazania działają poprawnie i “w miarę” szybko.

Chciałem użyć gema: gem ‘delayed_job_active_record’ dla wydzielenia procesu. U mnie na dev działa bez zarzutu wszystko. Na heroku po spuszowaniu samo już dodanie tego gema do Gemfile rozwala mi refineryCMS i nie ładują się style do panelu admina refinery (CO NIE POWINNO MIEĆ ZWIĄZKU, A MA), więc użycie tego raczej odpada.

Nie wiem co mam z tym poczac, kombinowałem jak koń pod górę, ale jetem jeszcze swieży w całym temacie.

Na Heroku dostajesz bardzo mały wycinek maszyny (w sensie dostępnego ramu i czasu procesora). Masz na heroku wykupione procesy workerowe, żeby używać delayed_job tam?

Tak mam wykupiony jedne proces. Ale niestety delayed_job konfliktuje z Refinerycms. Sprawdzałem to komentując ten GEM puszując i odkomentowując i puszując na heroku. Zakomentowany: Refinery działa dobrze. Odkomentowany: refinery się wysypuje.

Dopatrzyłem się kilku rzeczy ponadto:

Dla powyższego przykładu kod działa raz na jakiś czas, zależy to od szczęścia (?). Kilka, kilkanaście razy odświeżę i nie zadziała, Dla powyższego kodu oprócz timeout wyrzuca błąd (którego nie było w przypadku użycia Prawn czy Pdfkit):

OpenURI::HTTPError (503 Service Unavailable):
2012-12-21T16:49:33+00:00 app[web.1]: app/controllers/topdf_controller.rb:23:in block in index' 2012-12-21T16:49:33+00:00 app[web.1]: app/controllers/topdf_controller.rb:22:inindex’

Natomiast co jakiś czas nagle, nie wiadomo skąd zadziała dobrze.

Próbowałeś już wykorzystać “męki” innych ( przykład http://railsgotchas.wordpress.com/2011/10/14/pdfkit-in-rails-3-1-on-heroku/ ) czy próbujesz zrobić to na własną rękę ?

Dzięki bardzo za link! Jak najbardziej próbowałem wykorzystac męki innych. Nabardziej odpowiadał by mi prawn, ponieważ daje większa kontrolę i mam już wszystko w prawn zrobione (co działa na moim komputerze na DEVELOP. ) W ostatecznosci użyję PDFKit do którego mam gotowy html, ale bardzo trudno wskazać jest przejścia pomiędzy stronami i on ucina obrazki w połowie - Treść ma się zmieniać często.

Jest tam w tym poradniku linia:
path = Rails.root.join(“app/assets/images/#{filename}”)
To zadziała jak najbardziej.

ALE!

Amozons3 to jest to inny serwer, więc gdy chcę załadować obrazek ładuję używając URL, co nie działa, jak wyżej pisałem. Natomiast potrzebna jest UNIX-owa ścieżka do pliku/katalogu na Amazonie S3. W przykładzie podano jak to zrobić na podstawie relatywnej ścieżki do pliku na tym samym serwerze, a tu chodzi o inny serwer.

WIĘC
Czy jest jakiś sposób na pobranie obrazka z Amazons3 inaczej niż po przez URL. Np. zrobienia mapowania dysku na Amazon s3? Pobranie używająć ścieszki zaczynajacej się od SLESZA (/) z Amazon na heroku? lub UNIX Path?

Jest s3fs, ale nie ma szans żebyś mógł to sobie uruchomić na Heroku (poza tym jest dość wolne).
To co robi w tej sytuacji większość znanych mi rozwiązań (gem Dragonfly) to po prostu zasysanie pliku z S3 i wrzucanie do /tmp (oczywiście generując nazwę/ścieżkę która zapobiegnie kolizji).

Dzięki wielkie Tomash za podsunięcie pomysłu.

Pogrzebałem dziś trochę w gemach i kodzie źródłowym Refinery i zorientowałem się, że RefineryCMS korzysta z Dradonfly. Ten sposób działa do moich zastosowań także na heroku, a to jest najważniejsze.

Użyłem takiego kodu w kontrolerze (app/controllers/topdf_controller.rb):

[code=ruby]class TopdfController < ApplicationController

def index
app = Dragonfly[:refinery_images]

@product_categories = Refinery::ProductCategories::ProductCategory.all
@macaroni_products = (Refinery::MacaroniProducts::MacaroniProduct.all).group_by(&:category_id).sort

number = 0
@file_names = []

@product_categories.each do |product_category|

  part_macaroni_products = @macaroni_products.detect {|c| c[0] == product_category.id}

  if part_macaroni_products
    part_macaroni_products[1].each do |product|
      number += 1
        uid = product.image.image_uid.to_s
        image = app.fetch(uid)
        name = Rails.root.join('tmp', 'pasta', number.to_s)
        image.to_file(name)
        @file_names[number] = name
    end
  end
end

end[/code]
A w widoku (app/views/topdf/index.pdf.prawn):

[code=ruby]require “open-uri”

prawn_document() do |pdf|
pdf.move_down 10
pdf. fill_color “003366”
pdf.text “Macaroni Product Catalog”, size: 30, :align => :center
pdf. fill_color “000000”
pdf.move_cursor_to 550
number = 0

@product_categories.each do |product_category|
pdf.move_down 40
pdf.start_new_page
pdf. fill_color “003366”
pdf.text product_category.title, :size => 25, :align => :center
pdf. fill_color “000000”
pdf.text (product_category.description).gsub(/<[^>]+>/, “”)

y = pdf.cursor - 20
x = 50

part_macaroni_products = @macaroni_products.detect {|c| c[0] == product_category.id}
if part_macaroni_products
  part_macaroni_products[1].each do |product|
    number += 1
      pdf.bounding_box([x, y], :width => 150, :height => 150) do
        pdf.image @file_names[number], :at => [35, 115], :width => 80, :height => 80
        pdf.move_down 120
        pdf.text product.title.to_s+" "+product.sku.to_s, :align => :center
      end
    x += 150
    if x > 350
      x = 50
      y -= 150
    end
    if y < 200
      pdf.start_new_page
      y = pdf.cursor - 20
    end
  end
end

end
end[/code]
Jeśli ktoś ma ochotę, może napisać co robię niewłaściwego/nieoptymalnego/brzydkiego w tym kodzie. Jestem świeży jeszcze w Railsach i Rubym.

Rerquest dla około 100 produktów i ok. 10 kategorii trwa 10-20 sekund. Została mi kwestia DelayedJob, Który jest przeznaczony do wydzielania dłuższych procesów i tu muszę jakoś rozwiązać konflikt z RegineryCMS.

Jednak niedziała to na HEROKU tak pieknie, jak chcę. Są godziny, gdy działa wszystko ładnie i pieknie. Są godziny gdy nie działa w cale tylko Timeout - nawet gdy wyrzucę 100 produktów i wrzucę tylko jeden dla testu. Nie wiem od czego to zależy.