These are chat archives for AlgoLab/pinw

5th
Nov 2014
Gianluca Della Vedova
@gdv
Nov 05 2014 16:35

Un altro punto su cui credo sia necessaria una discussione riguarda i processi della coda di lavoro.

Al momento sto modellando il processo come segue:

  • pinw-fetch.rb, eseguito idealmente ogni 30 secondi, guarda quali job non hanno ancora tutti i download completati e per ogni link non ancora scaricato lancia un processo di download fino ad esaurire gli slot di download disponibili (fa controlli per vedere se ci sono stati altri processi lanciati che sono morti nel frattempo, ...).

Immagino che dovrebbe anche controllare quanti download sono ancora in corso, prima di aggiungerne altri.

  • pinw-dispatch.rb, idealmente una volta al minuto, che fa piu' o meno le stesse cose di prima ma rispetto al dispatch dei job ai server di calcolo.

Prima di buttarmi a fare tutto io ho guardato se c'erano delle gemme che avrebbero potuto aiutarmi ma tutti i sistemi di code di lavoro sono basati su RabbitMQ, REDIS, o simili. Ne ho trovato uno che usava ActiveRecord per la persistenza ma comunque per altri motivi non va bene (ad esempio ha un suo demone che deve essere sempre attivo).

Non mi farei troppi problemi sul fatto di avere un demone, a patto che semplifichi lo sviluppo (e la gestione)

Al momento sono al punto dove so come devono essere modellati i job e come deve essere strutturato lo script per gestire correttamente la concorrenza. Non ci sono cose particolari riguardo come vengono gestiti i processi se non per un dettaglio riguardante sqlite:

In alcuni momenti ho la necessita di eseguire alcuni cambiamenti ad un record in maniera atomica. Sqlite offre le transazioni ma ha il difetto che il locking avviene a livello di file, non a livello di row. Di conseguenza ogni volta che vengono eseguite queste parti di codice blocco l'intero database (blocco anche tutte le letture, mi serve un lock esclusivo). Non si tratta di pezzi di codice particolarmente lenti (sono un paio di letture dal db ed una scrittura) ma vengono eseguiti da ogni processo di download o dispatch dei job e vanno in mutua esclusione con anche i visitatori dell'interfaccia web.

Riesci a spiegarmi perchè di serve un lock esclusivo?

Un workaround attorno a questo problema e' quello di usare due database diversi di sqlite, quindi avere due file diversi e quindi bloccare solo tabelle contenute in uno dei due.
Oppure potresti anche avere 2 db separati: uno slave in sola lettura per le autenticazioni e il recupero dei dati già processati, e un db master per le operazioni di scrittura, creazione/gestione job, modifiche credenziali. Ogni tanto (idealmente alla fine di ogni lock/transazione) il db master viene copiato sullo slave.
E' possibile includere entrambi i db/file in una unica connessione di sqlite ma devo ancora vedere come si fa di preciso con ActiveRecord. In aggiunta credo che questo workaround non si possa esprimere con costrutti database-agnostic, come per il tutte le altre cose fatte fin ora, di fatto complicando leggermente un eventuale cambiamento del dbms sottostante ad una particolare installazione.

Io non vorrei cambiare db in futuro, quindi non ti preoccupare di essere db agnostico.

Siccome non so nel futuro che strada vuole far prendere al progetto, volevo una conferma prima di proseguire con questa parte.
Magari andando avanti mi accorgo che riesco ad evitare le transazioni, pero' il problema del "GSL" (global sqlite lock :) ) rimane sempre.

Capisco. Però rispetto ad un'applicazione web "standard", qui c'è un mix di richieste:

  • poche richieste che richiedono una elaborazione lunga (e varie operazioni su db)
  • più (ma non tante) operazioni di interrogazione del db, dove non si deve scrivere sul db, ma solo leggere
Gianluca Della Vedova
@gdv
Nov 05 2014 16:45
pinw-fetch.rb, eseguito idealmente ogni 30 secondi,
Se non sbaglio, non si riesce a fare partire un cronjob con granularità inferiore al minuto.
Sarebbe quindi meglio eseguire pinw-fetch.rb ogni minuto e pinw-dispatch ogni 2 minuti (oppure entrambi ogni 2 minuti, intervallandoli per diminuire il carico e la probabilità di interferenza)
Adesso bisogna capire come fare il deploy automatico ad ogni push
Gianluca Della Vedova
@gdv
Nov 05 2014 16:51
Riprendo il discorso: a regime vorrei che ogni container docker periodicamente prenda la versione più aggiornata di pinw/pintron. Se viene fatto tramite hook di github (come mi sembra tu proponi) va bene.
Dovrebbero esserci due immagini docker distinte: una per il frontend e l'altra per il server di calcolo
inoltre il dockerfile deve permettere di creare un container che abbia come riferimento un determinato branch, non necessariamente master. In questo modo si può avere un server o un frontend collegato ad un branch develop per testare nuove funzionalità
Il problema della situazione attuale è che ad ogni nuova versione di pinw, bisogna ricreare il container
Btw, vedi tutto a http://149.132.179.130/
Gianluca Della Vedova
@gdv
Nov 05 2014 16:57
Ho visto che la v 1.3 di docker ha un comando docker exec che dovrebbe permettere di eseguire un comando generico (una git pull nel nostro caso), ma su ubuntu 14.10 c'è ancora solo la 1.2
Loris Cro
@kappaloris
Nov 05 2014 17:00

Immagino che dovrebbe anche controllare quanti download sono ancora in corso, prima di aggiungerne altri.

Si, ho omesso certi dettagli, era per dire in termini generali cosa faceva lo script.

Loris Cro
@kappaloris
Nov 05 2014 17:51

Riesci a spiegarmi perchè di serve un lock esclusivo?

Supponiamo di essere nel caso in cui un job richieda 3 download e che questi vengano eseguiti (potenzialmente) in parallelo da tre processi diversi. Volevo fare che alla fine di un download il processo alzi il flag relativo al successo del proprio download, controlli lo stato degli altri flag e, nel caso siano tutti ok, metta il job in attesa di dispatch. Al momento facevo (in una transazione):

  1. lettura del record in un oggetto
  2. nel oggetto: alzo il mio flag, controllo gli altri ed eventualmente alzo anche il flag per attesa di dispatch
  3. commit delle modifiche

totale query: 1 select, 1 update per ogni processo

Senza transazione c'e' la race condition in cui i processi leggono contemporaneamente, nessuno si accorge che gli altri hanno terminato e il job rimane con tutti i download completati ma non accodato per il dispatch.

Per risolvere il problema al momento ho pensato le seguenti possibilita':

  1. lasciare che accada e fare si che pinw-fetch 'porti avanti' i job in questo stato (inefficiente dato che si devono ispezionare piu' oggetti dello strettamente necessario, introduce dei "singhiozzi" nel processamento che fanno percepire il sistema come meno performante dagli utenti, possono portare problemi in casi di picchi di carico (pinw-fetch deve terminare prima che arrivi il turno successivo del cron), ed in generale mi sembra una soluzione inelegante.

  2. far portare avanti lo stato del job da pinw-dispatch: come prima ma peggio perche' ora la gestione del processo non e' piu' divisa chiaramente in due step.

  3. cambiare le operazioni: fare che lo script 1. alzi il flag (update), 2. legga l'oggetto (select), 3. alzi il flag dispatch se serve (update). In questo modo tutto funziona ma abbiamo aumentato il numero di query (in particolare la query 3 puo' avvenire piu' volte) e l'ordine delle operazioni diventa importante rispetto un nuovo livello semantico quindi se qualcuno prende in mano lo script dopo deve stare attento a non rompere o, peggio, "ottimizzare" il codice altrimenti rischia di lasciare dei job in un limbo.

  4. invece di usare un flag a parte per indicare l'attesa di dispatch considerare tutti i flag di download alzati come "attesa dispatch": non e' piu' possibile fare una query su un indice per la selezione di questi job quindi ogni volta il db deve essere "cercato" e forse potrebbe richiedere di aggiungere altre condizioni inefficienti per aggiungere dei meccanismi di "pausa" del processamento del job (non sono sicuro, devo ancora ragionare bene questa parte).

Loris Cro
@kappaloris
Nov 05 2014 17:58
La 3 mi sembra preferibile al lock globale del database, ma con anche la possibilita' di dividere il db in due file non sono sicuro di volerla sia in termini di bug fantasma che potrebbero comparire in sviluppi successivi (e generale complessita' del codice), sia per la nuova race condition che introduce (che non rompe la logica ma che impatta la performance e non e' esattamente banale da individuare).

Io non vorrei cambiare db in futuro, quindi non ti preoccupare di essere db agnostico.

Bene, credo che questo sia un altro punto a favore della soluzione di spezzare il db

Loris Cro
@kappaloris
Nov 05 2014 18:19

Sarebbe quindi meglio eseguire pinw-fetch.rb ogni minuto e pinw-dispatch ogni 2 minuti (oppure entrambi ogni 2 minuti, intervallandoli per diminuire il carico e la probabilità di interferenza)

Non mi ricordavo il livello di dettaglio dei tempi dei cron. I due script non dovrebbero interferire (tolgo il condizionale quando ho finito di scrivere entrambi ma sono abbastanza sicuro). Prima dicevo 30s, 1m per indicare secondo me un rapporto corretto tra le due frequenze di esecuzione, comunque considerazioni sul carico vanno sicuramente fatte.

Loris Cro
@kappaloris
Nov 05 2014 18:28
Rispondo ora al discorso su docker:

a regime vorrei che ogni container docker periodicamente prenda la versione più aggiornata di pinw/pintron. Se viene fatto tramite hook di github (come mi sembra tu proponi) va bene.

Questo lo possiamo fare solo con le "nostre" installazioni dato che queste vanno "registrate" su github. Io suggerivo il metodo per automatizzare il deploy di una nostra installazione.

Loris Cro
@kappaloris
Nov 05 2014 18:38

inoltre il dockerfile deve permettere di creare un container che abbia come riferimento un determinato branch, non necessariamente master. In questo modo si può avere un server o un frontend collegato ad un branch develop per testare nuove funzionalità

si, vero, al momento sono un po' indietro su questo fronte, aspetto di finire i due script dei cron (che sono un po' il nocciolo del mio lavoro di sviluppo credo) e poi strutturo queste cose.

Loris Cro
@kappaloris
Nov 05 2014 18:52

Il problema della situazione attuale è che ad ogni nuova versione di pinw, bisogna ricreare il container

Sul container c'e' sshd attivo, lanciando il container con una opzione particolare e' possibile loggarsi dentro ed eseguire comandi: https://github.com/phusion/passenger-docker#login
Io l'ho usato per debuggare all'inizio cosa non funzionava nel mio Dockerfile.