2012-11-27 13 views
8

abbiamo la necessità di esportare un file CSV comprendente i dati dal modello da admin Django che gira su Heroku. Pertanto abbiamo creato un'azione in cui abbiamo creato il CSV e lo abbiamo restituito nella risposta. Questo ha funzionato fino a quando il nostro cliente ha iniziato a esportare enormi serie di dati e ci siamo imbattuti nel timeout di 30 secondi del web worker.Esportazione CSV in Stream (da Django admin su Heroku)

Per ovviare a questo problema abbiamo pensato di eseguire lo streaming di csv al client invece di crearlo prima in memoria e inviarlo in un unico pezzo. Trigger era questa informazione:

Cedar supporta il polling lungo e le risposte di streaming. La tua app ha una finestra iniziale di 30 secondi per rispondere con un singolo byte al client. Dopo ogni byte inviato (ricevuto da> il client o inviato dall'applicazione) si resetta una finestra di 55 secondi a rotazione. Se nessun dato è> inviato durante la finestra di 55 secondi, la connessione verrà interrotta.

Abbiamo quindi implementato qualcosa di simile a questo per provarlo:

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 

L'header HTTP del download si presenta così (da 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-Encoding : Chunked "indica che Cedar sta effettivamente trasmettendo il contenuto in chunkwise che indoviniamo.

Il problema è che il download del CSV è ancora interrotta dopo 30 secondi con queste righe nel registro 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 

Questo dovrebbe funzionare concettualmente, giusto? C'è qualcosa che ci è mancato?

Abbiamo davvero apprezzato il vostro aiuto. Tom

risposta

6

Ho trovato la soluzione al problema. Non è un timeout di Heroku perché altrimenti ci sarebbe un timeout H12 nel registro di Heroku (grazie a Caio of Heroku per segnalarlo).

Il problema era il timeout predefinito di Gunicorn che è di 30 secondi. Dopo aver aggiunto --timeout 600 al Procfile (sulla linea di Gunicorn) il problema era sparito.

Il Procfile ora assomiglia a questo:

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

Questo non è il problema del tuo script, ma il problema del timeout di Heroku per la richiesta web di 30 secondi. Si può leggere questo: https://devcenter.heroku.com/articles/request-timeout e in base a questo documento - spostare l'esportazione CSV in processo in background.

+0

Ma non dovrebbe la finestra di 30 secondi di timeout essere esteso perché abbiamo contenuto streaming, invece di aspettare fino a quando il csv è stato creato in memoria? Quindi ci sono byte trasmessi in questa finestra di 30 secondi e questo dovrebbe evitare il timeout in questo modo: Cedar supporta le caratteristiche HTTP 1.1 come polling a lungo e le risposte di streaming. Un'applicazione ha una finestra iniziale di 30 secondi per rispondere con un singolo byte al client. Tuttavia, ogni byte trasmesso successivamente ripristina una finestra di 55 secondi a rotazione. – Tom

+0

Potrebbe darsi che Django abbia un timeout interno durante l'invio di una risposta? – Tom

+0

Hai la tua richiesta web che funziona per più di 30 secondi - questo è un dato di fatto, e Heroku ha un timeout predefinito di 30 secondi per qualsiasi richiesta web nella sua configurazione del server http. Suppongo che il tentativo di emulare la sessione keepalive non abbia esito positivo: è meglio prendere in considerazione lo spostamento dell'elaborazione di file lunghi in processo/daemon in background. – moonsly