Post by Enrico BianchiPost by mePosso raccontarti quando vuoi una storia di guerra che coinvolge l'uso
di un loschissimo task queue manager noto come "RQ" (Ruby Queue).
Se ti va di condividerla anche qui, penso che sarà letta con interesse da
molti. Tieni presente però che io per partito preso considero schifosi i
linguaggi che implementano l'operatore === ;)
Mi reputo abbastanza abile Python/Perl, ma onestamente non conosco Ruby
per niente. Ma condivido la tua diffidenza, memore di $BrutteCose in php.
Condividerò un racconto della disavventura in fondo a questo post. Ocio
che è diventata più lunga di quanto non sperassi.
Post by Enrico BianchiPost by meSi noti come SQLite *da documentazione* non supporta scritture
concorrenti.
Oddio, in teoria su questo punto la documentazione è discordante. Sempre
in teoria, ed in base ad uno stupido test fatto da me (banalmente, apro
due terminali, sui due terminali apro lo stesso database SQLite,
comincio la transazione su tutti e due i terminali e poi vado di
insert), se abiliti il WAL anche SQLite riesce a gestire le scritture
concorrenti.
Non ho mai usato direttamente SQLite (a parte qualche veloce hack manuale
nella configurazione di Firefox). La mia diffidenza è legata alla
questione RQ. Ma è bello sapere che supporta transazioni: forse il
problema che ho riscontrato è legato al caso specifico di accesso su
filesystem di rete.
Post by Enrico BianchiSolo che, come ho detto prima, non mi fido ad usarlo in questo modo (ho
sistemi pensati e testati giorno per giorno per fare questa cosa, perché
dovrei usare qualcosa che sembra un surrogato?)
Sacrosanto. Ogni problema ha i suoi tool.
La storia di RQ risale a un po' meno di 3 anni fa, quando sono stato
assunto da quella che al tempo era $Startup, ed ora è cresciuta in
$SmallCompany. Ma il gruppo era originariamente una ghenga di ricercatori
ed avanzi di accademia.
Questa gente faceva (allora come oggi) delle cose con Java, e ne
verificava i risultati a colpi di esecuzioni simulate. Per sveltire la
cosa utilizzavano un cluster basato appunto su questo dannato RQ[0], e
c'era un brutto brutto accrocchio che parametrizzava il tutto mandando dei
"batch" di comandi sh (pesanti assunzioni sul layout del filesystem).
C'era poi una interfaccia in ncurses che doveva essere un helper, ma
rendeva il tutto dannatamente complicato. La peggio cosa è che il
concetto di "namespace" non era chiaro allo sviluppatroto. Perciò il tutto
si risolveva con mille mila directory che come nome avevano l'output di
`date +%s`. Questo per dare il contesto.
[0]: https://github.com/pjotrp/rq
Mi fu detto di migliorare l'usabilità di questa cosa, e successivamente di
migrarla dal cluster dell'università ad un cluster analogo sull'azzurro
cloud di Microslot. Scelta che poi ha avuto effetti imprevisti, ma questa
è un'altra storia orrida. Dopo aver deciso che l'interfaccia curses era
una gran caciotta a livello strutturale quanto a livello esteriore, ho
buttato tutto via, e mantenuto solo il gestore di task RQ.
Come menzionato, questo coso usa un bel SQLite su un filesystem condiviso
per gestire la sincronizzazione. Ma uno può naturalmente ignorare questo
fatto, visto che la cosa è celata dietro le quinte. Almeno fino a quando
non ci sbatti il muso. Ci costruisco sopra un layer di astrazione che mi
permette di chiamare il coso da Python (a colpi di subprocess), e
costruisco da zero il resto.
Tutto sembra funzionare per qualche tempo: giorni, settimane... Ma quando
il sistema inizia ad essere usato un po' più seriamente, ed ecco che
alcuni processi tardano a produrre risultati, anzi non finiscono più. SSH
sul worker del caso, ps aux | grep java, il processo ha finito. Uhm... cd
/path/to/results; ls, i dati ci sono. WTF?
Per farla breve, il worker mette una flag nel database per dire che
l'esecuzione è terminata e RQ riporta quest'informazione. Ma in
determinate situazioni il reporting non funziona perché la flag viene
settata, ma poi sovrascritto da una race-condition. Pialla il db e
ripartiamo.
Questa cosa succede una volta, due, tre ...finché decido che non è una
cosa così casuale. E scopro così che esiste in RQ un comando "recover". La
documentazione recita:
it is possible that a hardware failure might corrupt an rq database.
[...] this has been reported only a handful of times, nevertheless,
this command wraps sqlite recovery to get you rolling again
Quindi qui i casi sono due: o chi ha scritto quest'affare *pensa* che sia
un problema hardware ma ha sbagliato a fare i conti (possibile), oppure
questo coso è pensato per avere master e workers *sulla stessa macchina*,
e il tizio che ha messo in piedi il cluster non ha capito una fava
(probabile).
Vado dunque dal CL in questione e gli chiedo se ha visto ancora un
problema analogo. CL risponde: "si, succedeva a volte quel che dici.
Ma ho pensato che qualcuno fosse *stufo di avere il cluster occupato*
e uccidesse i tasks". Così CL semlicemente faceva ripartire i tasks e
si fregiava del ruolo di babysitter, in barba alla legge.
È andata a finire che ho potuto rimpiazzare RQ con qualcosa di più solido
(Python Celery prima, e Pyhton Huey dopo ancora, per questioni atroci
legate ad Azure, che ho accennato prima). Purtroppo sono stato tordo, e
non pur avendo astratto abbastanza bene l'accesso alla task queue, il
design del master è stato vagamente influenzato da come RQ gestisce lo
scheduling.
Ho molto sofferto nel risolvere il debito tecnico, ma ne sono uscito.
Forse non *più forte*, ma di sicuro *meno ona*.
Saluti