2012-11-27 7 views
8

potrzebujemy wyeksportować plik CSV zawierający dane z modelu z Django admin, który działa na Heroku. Dlatego stworzyliśmy akcję, w której stworzyliśmy plik CSV i zwróciliśmy go w odpowiedzi. To działało dobrze, dopóki nasz klient nie zaczął eksportować ogromnych zestawów danych, a my mamy do czynienia z 30-sekundowym czasem oczekiwania pracownika sieci.Eksport CSV w strumieniu (z administratora Django na Heroku)

Aby ominąć ten problem, pomyśleliśmy o streamowaniu CSV do klienta, zamiast budowania go najpierw w pamięci i wysyłania go w jednym kawałku. Trigger był tą informacją:

Cedr obsługuje odpowiedzi z długim odpytywaniem i przesyłaniem strumieniowym. Twoja aplikacja ma początkowe 30-sekundowe okno , aby odpowiedzieć pojedynczym bajtem z powrotem do klienta. Po wysłaniu każdego bajtu (otrzymanego od> klienta lub wysłanego przez twoją aplikację) zresetujesz trwające 55 sekund okno. Jeśli żadne dane nie zostaną> wysłane podczas 55-sekundowego okna, twoje połączenie zostanie zakończone.

dlatego Wdrożyliśmy coś, co wygląda tak, aby go przetestować:

import cStringIO as StringIO 
import csv, time 

def csv(request): 
    csvfile = StringIO.StringIO() 
    csvwriter = csv.writer(csvfile) 

def read_and_flush(): 
    csvfile.seek(0) 
    data = csvfile.read() 
    csvfile.seek(0) 
    csvfile.truncate() 
    return data 

def data(): 
    for i in xrange(100000): 
     csvwriter.writerow([i,"a","b","c"]) 
     time.sleep(1) 
     data = read_and_flush() 
     yield data 

response = HttpResponse(data(), mimetype="text/csv") 
response["Content-Disposition"] = "attachment; filename=test.csv" 
return response 

Nagłówek HTTP pobrania wygląda następująco (od FireBug):

HTTP/1.1 200 OK 
Cache-Control: max-age=0 
Content-Disposition: attachment; filename=jobentity-job2.csv 
Content-Type: text/csv 
Date: Tue, 27 Nov 2012 13:56:42 GMT 
Expires: Tue, 27 Nov 2012 13:56:41 GMT 
Last-Modified: Tue, 27 Nov 2012 13:56:41 GMT 
Server: gunicorn/0.14.6 
Vary: Cookie 
Transfer-Encoding: chunked 
Connection: keep-alive 

„Transfer kodowania : chunked "wskazywałby, że Cedar faktycznie strumieniuje treści, o których sądzimy.

Problem jest, że pobieranie z csv jest nadal przerwane po 30 sekund z tych linii w dzienniku Heroku:

2012-11-27T13:00:24+00:00 app[web.1]: DEBUG: exporting tasks in csv-stream for job id: 56, 
2012-11-27T13:00:54+00:00 app[web.1]: 2012-11-27 13:00:54 [2] [CRITICAL] WORKER TIMEOUT (pid:5) 
2012-11-27T13:00:54+00:00 heroku[router]: at=info method=POST path=/admin/jobentity/ host=myapp.herokuapp.com fwd= dyno=web.1 queue=0 wait=0ms connect=2ms service=29480ms status=200 bytes=51092 
2012-11-27T13:00:54+00:00 app[web.1]: 2012-11-27 13:00:54 [2] [CRITICAL] WORKER TIMEOUT (pid:5) 
2012-11-27T13:00:54+00:00 app[web.1]: 2012-11-27 13:00:54 [12] [INFO] Booting worker with pid: 12 

ten powinien pracować koncepcyjnie, prawda? Czy jest coś, co przegapiliśmy?

Naprawdę doceniamy Twoją pomoc. Tom

Odpowiedz

6

Znalazłem rozwiązanie problemu. To nie jest limit czasu Heroku, ponieważ w przeciwnym razie w dzienniku Heroku byłby limit czasu H12 (dzięki Caio z Heroku, aby to wskazać).

Problem stanowił domyślny limit czasu Gunicorn, który wynosi 30 sekund. Po dodaniu - timeout 600 do Procfile (na linii Gunicorn) problem zniknął.

Procfile teraz wygląda tak:

web: gunicorn myapp.wsgi -b 0.0.0.0:$PORT --timeout 600 
celeryd: python manage.py celeryd -E -B --loglevel=INFO 
0

To raczej nie jest problem twojego skryptu, ale problem z 30-sekundowym opóźnieniem w webowym domyślnym przekroczeniu czasu Heroku. Można to przeczytać: https://devcenter.heroku.com/articles/request-timeout i zgodnie z tym dokumentem - przenieś eksport CSV do procesu w tle.

+0

Ale nie powinno się okno 30 sekund czasu oczekiwania zostać przedłużony, ponieważ strumieniowo zawartość, zamiast czekać, aż CSV został stworzony w pamięci? Tak więc w tym 30-sekundowym oknie są przesyłane bajty, co pozwoli uniknąć przekroczenia limitu czasu zgodnie z poniższym: Cedar obsługuje funkcje HTTP 1.1, takie jak odpowiedzi z długim odpytywaniem i przesyłaniem strumieniowym. Aplikacja ma początkowe 30-sekundowe okno odpowiadające jednemu bajtowi z powrotem do klienta. Jednak każdy bajt transmitowany następnie resetuje ruchome okno 55 sekund. – Tom

+0

Czy to możliwe, że Django ma wewnętrzny limit czasu podczas wysyłania odpowiedzi? – Tom

+0

Twoje żądanie internetowe działa przez ponad 30 sekund - to fakt, a Heroku ma 30-sekundowy domyślny limit czasu dla dowolnego żądania internetowego w jego konfiguracji serwera http. Przypuszczam, że twoje próby emulacji sesji keepalive nie zakończą się sukcesem - powinieneś rozważyć przeniesienie długiego przetwarzania plików do procesu/demona w tle. – moonsly

Powiązane problemy