Come ASCIUGARE con le chiamate a un database da eseguire?

4

Sto scrivendo un'app python che indica a un database di eseguire varie istruzioni di elaborazione su tabelle partizionate. L'elaborazione può richiedere un tempo sufficiente per il timeout, quindi accento le mie chiamate al database con try:... except: di blocchi come:

while True:
    try:
        process_one(table, logger, cursor)
    except OperationalError as oe:
        logger.error(oe)
        con, cursor = retry_connection(logger, dbset)
    else:
        break

Dove process_one() è una chiamata a una funzione di PostgreSQL come:

def process_one(table, logger, cursor):
    logger.info('Processing table %s', table)
    cursor.execute('SELECT do_something(%(table)s)', {'table':table})

Ma se ho una sequenza di funzioni di elaborazione da eseguire e non voglio ripetere una procedura in caso di un problema di connessione questo si trasformerebbe in:

while True:
     try:
         process_one(...)
     ....
while True:
     try:
         process_two(...)
     ....

L'ideale di DRY sarebbe avere una funzione che eseguisse il ciclo while True, try, except, break e assuma diverse funzioni di elaborazione come parametro?

def try_process(process, processparams):
   while True:
     try:
         process(processparams)
     except OperationalError as oe:
         ...
    
posta raphael 25.10.2016 - 04:34
fonte

3 risposte

3

Sì, questo design ha un senso. (In base alle informazioni fornite qui).

Supponendo che ogni processo abbia gli stessi identici requisiti di gestione degli errori, avendo una funzione che gestisce l'errore, quindi non devi ripeterlo per ogni processo è un buon progetto.

Si può immaginare un caso in cui in futuro si potrebbe voler aggiungere ulteriore logica di gestione degli errori, e avere questa in una funzione mantiene le cose gestibili anche se si finisce per avere una logica abbastanza complessa che gestisce un sacco di diversi stati di errore.

    
risposta data 25.10.2016 - 08:21
fonte
1

Probabilmente non dovresti avere questa logica dei tentativi. Invece, dovresti risolvere ciò che sta fallendo. Nell'uso normale non dovresti ricevere eccezioni OperationalError. Se le query richiedono troppo tempo per essere eseguite, è necessario aumentare il timeout. Se possibile, dovresti risolvere i motivi per cui il tuo codice non funziona, invece di riprovare.

(Per essere chiari, ci sono casi in cui i ritrati hanno senso: è possibile che tu sia in uno di essi, ma è relativamente raro).

Inoltre, non dovresti più riprovare in un ciclo infinito. Vuoi davvero riprovare un numero di volte e poi rinunciare. Se qualcosa è seriamente sbagliato, non vuoi sederti in un ciclo infinito riprovandolo, vuoi fallire, così qualcuno viene avvisato che c'è un problema.

Supponendo che dovresti riprovare, penso che potresti aver scelto il livello sbagliato di astrazione. Avrai molte funzioni diverse che effettuano chiamate al database che desideri riprovare. Tuttavia, questi chiameranno un piccolo numero di funzioni nel database API, (mustly il metodo execute). Quindi, invece di aggiungere la logica di riprova a ciascuna delle tue funzioni, sarebbe più sensato racchiudere il numero relativamente più piccolo di funzioni di postgre con la logica dei tentativi.

Invece di scrivere un sacco di codice come questo:

def process_one(table, logger, cursor):
    logger.info('Processing table %s', table)
    cursor.execute('SELECT do_something(%(table)s)', {'table':table})

try_process(process_one, table, logger, cursor)

Scrivi una classe cursore di sostituzione:

class Cursor:
    def __init__(self, cursor):
        self._cursor = cursor

    def execute(self, *args):
        for _ in range(NUMBER_OF_RETRIES):
            return self._cursor.execute(*args)
        except OperationError:
            pass # retry

E usa questa classe wrapper al posto del cursore psql. Dal momento che la maggior parte delle affermazioni problematiche passano attraverso la funzione execute, questo gestisce la maggior parte dei casi, evitando di dover inserire la logica dei tentativi ovunque.

    
risposta data 25.10.2016 - 18:09
fonte
0

Sì, sembra a posto, anche se non avresti bisogno di richiedere la funzione process per prendere sempre un singolo parametro:

def try_process(process):
   while True:
     try:
         process()
     except OperationalError as oe:
         ...

E devi chiamarlo con un parametro che usi una lambda:

try_process(lambda: process(processparams))

(probabilmente aggiungerei un limite al numero di tentativi)

    
risposta data 25.10.2016 - 17:29
fonte

Leggi altre domande sui tag