Back-End

31 jul, 2017

Futura solução de Upload incorporada para o Rails 5.2 (ActiveStorage)

Publicidade

Atualização 11/07/2017: Logo após nosso brainstorm pelo Twitter, DHH e @gauravtiwari se esforçaram e iniciaram um projeto paralelo para adicionar o suporte para o Upload direto para a nuvem imediatamente. É claro que ainda está em desenvolvimento, mas será ótimo que já virá com o suporte.

DHH acabou de anunciar uma nova funcionalidade para o Rails 5.2 que está chegando. É o ActiveStorage.

Ele deve se tornar a solução padrão para suporte ao upload de arquivos. Ele basicamente substitui o Paperclip, ou o Carrierwave, ou algumas funcionalidades de Dragonfly e Shrine (eles fazem muito mais).

Estou escrevendo esse pequeno post não para apresentar a solução, mas para esclarecer algumas críticas que fiz pelo Twitter. Mas o Twitter é uma plataforma terrível para discussões mais aprofundadas, por isso o post.

Os posts originais no Twitter estão aqui.

O processo tem ao menos 3 pontos importantes a serem considerados:

  1. O upload do arquivo, a partir do usuário final para seu controlador Rails.
  2. Um passo, opcional, de transformação do arquivo (que deveria ser utilizado para redimensionar imagens para que sejam distribuídas para as telas de aparelhos com resoluções diferentes)
  3. Retornar para o usuário a estrutura do arquivo como um endereço URL da imagem, por exemplo.

O passo do Upload

A maioria das soluções simples para upload – assim como o ActiveStorage, e alguns antigos, como o Paperclip e a primeira versão do Carrierwave – basicamente configura o formulário HTML e coloca um código HTML simples no campo do arquivo. Isso vai fazer o upload do formulário diretamente para o controlador de ações do Rails, que vai recebe-lo como parâmetro e você vai poder tratar o arquivo binário a partir daqui.

Implementado ingenuamente, ele vai bloquear o MRI durante o upload completo. Se o arquivo for muito grande, ele poderá bloquear qualquer outra chamada que chegue durante esse upload. (Tecnicamente, porque o Rails suporta rotinas MRI e as rotinas MRI teoricamente não são bloqueadas para operações de Entrada e Saída, então não deve ser tão ruim quanto parece).

Felizmente, acredito que ninguém em seu juízo perfeito iria expor os processos MRI diretamente à Internet. Normalmente ficamos por trás de um proxy reverso, como o Haproxy, NGINX, Apache HTTPD, ou algo similar.

E normalmente esses proxies reversos são quem recebe os uploads e somente encaminham a solicitação quando o upload estiver completo. Então, a aplicação MRI/Rails pode continuar respondendo às solicitações durante esse tempo.

Então, se você tem uma infraestrutura personalizada, você tem sorte.

Se você estiver usando algo tipo o Heroku, você basicamente tem problemas. A camada de roteamento do Heroku tem um tempo limite de 30 segundos. Eu considero isso bom porque você não deveria ter solicitações que demorem tanto para serem processadas. Caramba, você não deveria ter uma solicitação sendo respondida em 1 segundo, ainda menos em 30.

Mas o upload de arquivos é a exceção. Um arquivo grande demora para ser transferido para a Internet. E então o limite de tempo derruba e interrompe a transmissão. O usuário tenta novamente e se você tiver uma quantidade suficientemente grande de usuários, você vai começar a preencher a fila HTTP até que o tempo limite comece a cascatear.

Por isso a única solução disponível para o Heroku é fazer o “upload direto” para um serviço como o AWS S3. Você pode adicionar add-on Carrierwave Direct, ou utilizar uma solução completa como o Cloudinary, com sua biblioteca Attachinary para facilitar as coisas. E é isso!

O Active Storage, como está agora, vai funcionar para qualquer instalação personalizada (NGINX + Rails/Puma) mas não vai funcionar com o Heroku e quando a nova funcionalidade estiver pronta também será uma boa escolha para a utilização com o Heroku.

O passo da transformação

Esse pode ser realizado imediatamente após o upload ou imediatamente antes de enviar o arquivo de volta ao usuário.

A primeira opção pode ser feita de maneira síncrona ou assíncrona.

Sincronamente é “ruim” (quero dizer, na ação do controle, porque esse passo tem uma utilização intensa da CPU e é demorado). É basicamente transformar a imagem (utilizando algo tipo Rmagick ou MiniMagick) em outras versões de diferentes tamanhos (miniatura, versão móvel, versão em alta qualidade, etc) e armazenar os caminhos para as diferentes versões no armazenamento.

Assincronamente é adiar essa transformação pesada para o ActiveJob para que algo com o Sidekiq realize o processamento depois. Enquanto isso você pode fornecer ao usuário um espaço reservado se a versão particular ainda não estiver pronta.

Uma advertência é que caso você tenha um armazenamento na nuvem e utilize a transformação assíncrona, você terá muito tráfego, porque você vai gastar tempo fazendo o upload para o armazenamento na nuvem, depois o serviço vai baixar da nuvem para fazer a transformação e vai fazer o upload novamente.

A outra solução é não fazer nenhum processamento e somente realizar sob demanda. É isso que o Cloudinary/ Attachinary faz e você pode realizar as transformações utilizando parâmetros URI. Ele vai realizar a transformação uma vez e armazenar em cache os resultados para utilizações futuras. Um exemplo de URL de transformação de imagem do Cloudinary é:

http://res.cloudinary.com/demo/image/upload/w_400,h_400,c_crop,g_face,r_max/w_200/lady.jpg

Esse também é o resultado da implementação do Refile ou Shrine. Ele adiciona um endpoint em sua aplicação Rails que vai buscar a imagem binária, realizar a transformação de acordo com os parâmetros recebidos na URI e armazenar o resultado em cache antes de encaminhar o binário.

O passo de retornar

Esse é fazer com que sua aplicação Rail envie o arquivo binário armazenado. O arquivo pode estar armazenado localmente (ou através de uma montagem NFS) ou na Internet em qualquer armazenamento baseado na nuvem como o AWS S3 (nesse caso você apenas conecta diretamente ao ponto HTTP).

Quando sua aplicação Rails fornece o arquivo local ela pode enviar apenas um cabeçalho especial para o proxy reverso (NGINX ou Apache -x-SendFile ou x-Accel-redirect que é a diferença entre send_file e send_data no ActionController::DataStreaming, a propósito) e eles vão fornecer o arquivo diretamente, evitando travar o MRI durante a transferência do arquivo.

Se estiver em um armazenamento na nuvem, é ainda mais fácil porque você só vai adicionar o caminho URL para o arquivo diretamente no HTML e não existe processamento.

Você terá algum processamento se fizer o Rails ler o arquivo binário e exibi-lo diretamente (as vezes você vai precisar fazer isso porque pode ter acesso restrito aos arquivos e não quer correr riscos, mesmo utilizando uma URL randômica para o arquivo).

Conclusão

DHH está certo, o Basecamp fornece muitos arquivos e o ActiveStorage (assim como o Paperclip, Carrierwave) funciona bem, desde que você tenha um proxy reverso NGINX apropriado em sua frente e que você tenha adicionado o CDN apropriado para arquivar seus arquivos em cache.

Se você não quiser gerenciar seu próprio armazenamento, você deveria utilizar um armazenamento na nuvem (AWS S3, Google Cloud, Azure, etc). O ActiveStorage ou outras soluções vão receber o arquivo no nível do controlador Rails e você DEVERIA utilizar o ActiveJob para POSTAR seu arquivo binário em seu serviço de nuvem no background – não bloqueando sua aplicação no processo. Mas em compensação, se você utilizar o serviço de transformação assíncrona, você acabará tendo que buscar o arquivo original na nuvem para realizar as transformações.

Você deveria realizar as transformações em suas imagens para enviar o melhor tamanho de imagem de volta ao seu usuário final. Novamente, se fizer isso em sua aplicação, considere, ou o ActiveJob, ou as soluções em tempo real de transformação e armazenamento em cache disponíveis no Refile ou Shrine. Ao menos como está agora, o ActiveStorage não fornece uma solução para transformação de imagens.

A minha recomendação de posts anteriores sobre o assunto continua, se essa é a sua primeira vez ou seu negócio está apenas começando, não tenha tanto trabalho. Utilize uma solução completa como o Cloudinary/ Attachinary. Eles cuidarão de tudo de uma maneira otimizada.

Mas isso NÃO é uma recomendação definitiva. Se você tem instalações personalizadas, e conhece seus requisitos e restrições, uma solução como o ActiveStorage ou Carrierwave, etc., funcionam bem. Sempre existem pontos negativos, e ter tantas partes móveis acrescenta complexidade. A recomendação do Cloudinary é apenas para que você possa começar com o mínimo de partes móveis possível e então seguir para cenários mais complexos depois, caso precise.

Lidar com todas as possíveis combinações de gerenciamento de upload de arquivos não é fácil. E você provavelmente tem pontos de maior preocupação em sua lógica de negócios do que tratar arquivos.

Ah, e repetindo a mesma conversa de sempre, utilize um CDN! Independente da solução, você sempre deve adicionar um CDN para entregar seus recursos (javascripts, styleshets, imagens, etc.).

 

***

Fábio Akita faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela Redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://www.akitaonrails.com/2017/07/07/upcoming-built-in-upload-solution-for-rails-5-2-activestorage