Progettazione DAO per scrivere grandi file XML sul database

0

Attualmente sto lavorando su un'applicazione JavaEE (Spring, Hibernate). Devo mettere un grande file XML (più di 1 gigabyte) su un database relazionale (Postgres).

L'applicazione non utilizza l'elaborazione in batch. Ho effettuato alcune ricerche ma non ho trovato alcuna soluzione per la progettazione del livello DAO: se utilizzo una sola transazione, il server non risponderà a nessuna richiesta finché non avrà completato l'inserimento delle righe per riempire uno schema di database complesso ( un numero enorme di righe: l'ordine delle righe aggiunte è di migliaia (per ogni tabella)). Quindi, usare 1 transazione non è una buona idea. Posso dividere il file XML in base ai dati dei tag: ogni tag verrà inserito su una riga. L'idea è di usare il multithreading per gestire le transazioni (ogni transazione inserisce un numero definito di righe). È una buona idea? Ho trovato difficoltà a sapere come definire il numero necessario di transazioni per mantenere una buona risposta temporale dell'applicazione. Cerco anche come gestire il fallimento di alcune transazioni. Ad esempio, se solo 3 transazioni scrivono oltre 1000000 fallire, dovrei riprovare tutte le transazioni?

Durante la ricerca, trovo che l'elaborazione in batch come il batch di primavera gestisce i record del database e il fallimento delle transazioni. Ma nella mia applicazione, non abbiamo usato l'elaborazione in batch.

Purtroppo, non posso cambiare il database nel database Nsql o aggiungere il framework Spring Batch al progetto.

N.B: Non posso aggirare Spring e Hibernate in questo progetto, ma sono aperto a qualsiasi suggerimento anche per curiosità.

    
posta amekki 24.12.2015 - 09:17
fonte

2 risposte

1

Il tuo problema e gli stack tecnologici sono molto simili a un progetto a cui sto lavorando come architetto di applicazioni in questo momento, quindi ti darò il mio miglior consiglio su come procedere date le informazioni e i vincoli che hai fornito.

I tuoi istinti sono corretti che la scelta migliore per questo progetto sarebbe quella di utilizzare Spring Batch o qualcosa di simile ad esso. Quello che stai facendo in modo efficace è esattamente quale sia l'elaborazione in batch e i tuoi tentativi di introdurre il multi-threading e di lavorare per evitare di esaurire la memoria durante l'elaborazione sono facilmente gestibili in Spring Batch. Dal mio punto di vista sembra che il tuo cliente abbia un'applicazione mal progettata per le funzionalità previste, e ti è stato chiesto di ripulire il caos ma non a scapito di una riscrittura.

Quindi non sto dicendo che devi usare Spring Batch ma voglio darti un po 'di contesto sul motivo per cui Spring Batch è la scelta migliore. Questo ti aiuterà a progettare il tuo approccio in modo appropriato.

Lettori, processori e scrittori

L'idea alla base dei lettori è quella di leggere in un sottoinsieme dei dati da elaborare. Questo in genere può essere fatto, tuttavia stai leggendo il file XML ora. Il tuo lettore tiene traccia di dove si trova nella posizione del file. Sta creando oggetti per il processore.

Il processore eseguirà qualsiasi logica aziendale o di integrazione che potresti avere.

Il writer può utilizzare uno strumento come Hibernate per scrivere singoli record sul database relazionale.

Chunking e transazioni

Una porzione di dati è solo un sottoinsieme di oggetti dati letti, elaborati e scritti in un'unica transazione contigua. Se la transazione viene completata fino in fondo, è chiaro che è possibile eseguire il commit nel database. Nell'eventualità di un'eccezione, è necessario definire il comportamento dell'eccezione nel punto in cui si esegue il rollback della transazione a livello di database e si registra correttamente quale blocco di record non è stato completato correttamente. Forse, come parte di questo comportamento di rollback, si desidera includere un comportamento degli eventi di notifica per inviare un'email a un gruppo di supporto per esaminare il problema. Utilizzare il framework delle transazioni tramite Spring + JTA è l'approccio migliore.

Realisticamente però non puoi avere una discussione su cosa fare quando c'è un'eccezione senza considerare i tuoi requisiti di business (o come sospetto, forse la mancanza di requisiti di business da parte del tuo cliente qui). La definizione di ciò che accade quando alcuni record non vengono elaborati non è qualcosa che possiamo dirti, è qualcosa che deve essere affrontato nei tuoi requisiti aziendali, oppure è un vuoto.

Indipendentemente dal modo in cui ti avvicini a cosa fare nel tuo comportamento di rollback, 1 GB di dati per un singolo file è troppo per una singola transazione e sarebbe inutile buttar via tutta l'elaborazione che è stata inserita in quel file a causa di ciò che potrebbe ammonta a un carattere imprevisto in alcuni record arbitrari.

  • Vuoi ridurre i dati di input a una dimensione ragionevole in modo tale che sia disponibile memoria sufficiente per tutti i file attualmente in elaborazione allo stesso tempo.
  • Vuoi che il tuo blocco venga singolarmente trasformato in modo tale che una volta completato non dovrai più rivedere questi record
  • Si desidera elaborare questi file un blocco alla volta all'inizio e solo dopo aver raggiunto le metriche prestazionali desiderate se si considera un approccio multi-thread o distribuito.
  • Si desidera registrare quale frammento si sta attualmente elaborando nel database in una sorta di tabella di metadati e, se un blocco non riesce, nel comportamento di rollback delle eccezioni si desidera aggiornare nel database il chunk non è riuscito nel processo.
  • Se un blocco non riesce su un file, è necessario interrompere completamente l'elaborazione fino a quando il problema non viene identificato e risolto. Potrebbe trattarsi di un coinvolgimento umano, quindi è probabilmente necessario prendere in considerazione una funzionalità di supporto per riavviare un lavoro fallito nel punto in cui era stato interrotto.

Prestazioni e ridimensionamento

Questo mi è di grande aiuto in quanto non so da dove provenga il file, come viene invocato il processo di elaborazione file e quali sono i requisiti non funzionali relativi alle prestazioni. Il mio consiglio qui ovviamente è che la scommessa sicura è quella di elaborare come singole transazioni in un unico thread per iniziare. Il multi-threading o addirittura l'introduzione dell'elaborazione parallela e del calcolo distribuito qui potrebbero essere potenzialmente molto complicati se si sta tentando di eseguire il rollover. Framework come Spring Batch ti aiutano a gestirlo se ne hai bisogno, ma ci sono buone probabilità che non lo farai se il client non offrisse severi requisiti di performance. Le tue preoccupazioni circa il deadlocking del database e il mantenimento dei vincoli di memoria sul tuo server sono alleviati gestendole in un unico thread.

    
risposta data 03.01.2016 - 04:18
fonte
2

Ciò che Spring Batch farà per te è disaccoppiare le attività di lettura, scrittura e business logic del tuo problema preservando la causalità. Non introduce alcuna forma di batch di transazioni del database, si sarebbe comunque agganciato (anche se l'astrazione di Reader lo renderebbe un po 'più semplice). Quindi, se tutto ciò che si vuole fare è eseguire i propri inserimenti in lotti efficienti di SQL, Spring Batch non fornisce nulla, in realtà, ma un framework opinato che non si conosce.

Quando arrivi subito ad esso, se stai attraversando un documento XML e costruisci oggetti Hibernate da esso, ciò che conta davvero per il database è:

  • coerenza causale, che il multi-threading incontrollato di piccole transazioni potrebbe violare
  • tempo minimo di applicazione trascorso all'interno di una transazione di database (che significa non aprire una transazione finché tutti i dati non sono disponibili e non eseguire la business logic mentre la transazione è attiva)
  • la possibilità di controllare la dimensione di un commit (in modo da non commettere 1 record alla volta OPPURE commettere tutto in una volta)

Per la massima efficienza CON coerenza e SENZA quadro, ciò di cui hai bisogno è un minimo di due thread:

  • Uno che sta leggendo e creando oggetti, il più velocemente possibile
  • Uno che scrive oggetti su un database, quanti ne sono disponibili e nell'ordine in cui sono stati letti

Un modo semplice per fornire questo comportamento con Hibernate coinvolto:

  • Avere un thread Reader-Creator che legge il file XML e crea oggetti Hibernate, ma non li mantiene. Invece, li memorizza su un BlockingQueue (BQ) limitato.
  • Avere un secondo thread Persister-Writer che condivide il BQ con il primo thread. In un ciclo, esegue il polling per un nuovo oggetto da aggiungere al BQ. Quando viene scoperto, viene mantenuto utilizzando un nuovo EntityManager (EM), insieme a N-1 elementi aggiuntivi attualmente disponibili (utilizzando drainTo, che non blocca). Quindi, flush viene richiamato sull'EM e continuiamo il ciclo successivo (eliminando il vecchio EM nel processo).

In questo schema, la coda che collega i due thread è generalmente chiamata "Mailbox" e ciò che stiamo facendo è un'approssimazione economica della programmazione del modello di attore. Ma alla fine:

  • sei protetto da problemi di memoria ridimensionando correttamente il BlockingQueue, poiché il primo thread bloccherà se diventa pieno e il secondo thread getterà via tutto lo stato a cui si preoccupa su ciascun ciclo
  • la coerenza causale è garantita perché tutti gli oggetti verranno mantenuti e scaricati nel database nell'ordine in cui vengono letti
  • Le prestazioni del database
  • vengono migliorate perché gli oggetti N vengono svuotati alla volta anziché 1 o ∞ (puoi sperimentare per un valore ottimale di N o usare solo 1000)
  • Non sono coinvolti nuovi framework - anzi, questo è probabilmente inferiore a 50 LoC

Avrai bisogno di un modo per assicurarti che il secondo thread si interrompa dopo che il primo thread ha terminato il lavoro e l'ultimo pezzo di lavoro nel BQ è stato eseguito, altrimenti uscirai dal programma prima che tutto il contenuto sia stato salvato . Una tecnica comune per questo è creare un messaggio "pillola avvelenata" - qualcosa che non assomiglia ai tipi di messaggi solitamente ricevuti dal Persistore-Scrittore che lo fanno svuotare gli oggetti rimanenti e uscire dal suo ciclo.

    
risposta data 03.01.2016 - 09:15
fonte

Leggi altre domande sui tag