Newsgroup: it.comp.lang.delphi
Titolo: Ottimizzazione server TCP

Salve,
sto implementando un server TCP con Indy 9 che accetta connessioni su 
una porta fa delle query con firebird e poi chiude la connessione.
Tutto funziona, ma dato che il numero di connessioni sono molte volevo 
sapere se ci sono dei particolari accorgimenti da fare o da tenere in 
considerazione.
Sfruttando l'architettura multithread del TIdTcpServer ad ogni 
connessione apro un'istanza di un datamodule con tutte le query, session 
e database. In questo modo ogni istanza del datamodule (e quindi ad ogni 
nuova connessione) mi alloca circa un mega di ram... beh poco male si 
puň carrozzare il server con qualche Gb di ram e si imposta un limite 
max di connessioni contemporanee. Volevo sapere se questo approccio č 
quello giusto dato che ogni connessione deve avere la sua sessione con 
firebird.
Grazie in anticipo

 e database. 
Non va bene.  Lato server devi implementare una classe che faccia il
connection pooling (con "connection" mi riferisco alla connessione al db),
che allochi cioč un tot di connessioni aperte, le blocchi/sblocchi
logicamente, alla richiesta di un thread TCP.
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

 
Ho letto un po' in giro e se non ho capito male mi conviene fare 
qualcosa del genere (descrivendo in linee generali)
Creo:
- n. 1 DataModule con la connessione al database creato una sola volta 
all'avvio del server
- una lista di DataModule con le query e una transazione che puntano al 
database del primo DataModule, con una proprietà che ne indica se questo 
datamodule è in uso o no
Come funzionamento faccio così:
- Ad ogni nuova connessione si scorre la lista dei DataModule per vedere 
quale è "libero" e si imposta libero := false;
- Alla fine di una connessione si fa il commit della relativa 
transazione e si imposta libero := true;
In questo modo si dovrebbe eliminare il problema di creare una 
connessione al database per ogni connessione in entrata.
Il procedimento è corretto?
Fabrizio

Il procedimento è corretto?
Nì.
Mi spiego: il problema con le applicazioni multi-thread (e un server
TCP mediamente complesso *è* multi-thread) è quello di dover
condividere le risorse e quindi sincronizzare i thread nell'accesso ad
esse.  Implementando le cose come proponi, la tua risorsa, in questo
caso, è la tua UNICA connessione al database, da condividere tra più
thread mediante sincronizzazione.  Questo significa far procedere più
thread in fila indiana, nell'accesso al database ed è una cosa
assolutamente inadeguata (benché non errata logicamente), e perché non
si sfruttano appieno le potenzialità del DBMS, e perché si va incontro
a un degrado di performance notevole. Immagina 100 client che debbano
aspettarsi per poter fare una select. L'alternativa, invece, è quella
di usare un "connection pool", ovvero un set di connessioni
preimpostate (il cui numero massimo lo stabilisci a priori in base
all'entità del traffico da gestire).  Le N connessioni vengono
allocate all'inizio, aperte verso il db e segnate dal connection pool
manager come logicamente libere.  Ogni volta che un thread richiede
l'accesso al db, invoca un GetConnection() verso il connection pool
manager, il quale accede alla sua lista interna, la scorre alla
ricerca di una connessione logicamente libera, la segna come
logicamente bloccata e la restituisce al thread.  Quando il thread
termina le sue operazioni, invocherà un ReleaseConnection() verso il
connection pool manager, il quale accederà alla sua lista interna e
sbloccherà logicamente la connessione, rendendola nuovamente
disponibile per altri thread che ne abbiano bisogno.  Bada che la
connessione non viene *mai* chiusa e riaperta, ma è *sempre* aperta
verso il db.  Se allochi, inizialmente, un pool di 10 connessioni,
dovrai avere un traffico *veramente* enorme per sforare questo numero
(e ovviamente assumendo che i thread utilizzino il db efficientemente,
con transazioni più brevi possibili).  Naturalmente l'accesso alla
lista delle connessioni, per il blocco/sblocco da parte del connection
pool manager deve avvenire in modo serializzato, per evitare che due
thread ricevano la stessa connessione (in Delphi, ad esempio, per
memorizzare la lista delle TCustomConnection, ho usato un
TThreadList).  Ci sono poi delle tecniche di allargamento del pool e
di restringimento.  Per esempio, se guardacaso *tutte* le connessioni
sono "bloccate" e un thread ha bisogno di una connessione, si può:
1. mettere il thread in attesa che una connessione si sblocchi;
2. allargare il pool ad N = N+1 connessioni, allocando e aprendone una
nuova e fornire la N+1-esima al thread;
ADO utilizza la prima tecnica, per esempio.
Si possono prevedere tecniche di restringimento del pool (una sorta di
garbage collection) che chiuda e deallochi le connessioni non
utilizzate da un tot di tempo.  Se questo connessioni sono n, allora
il pool decrescerebbe a N = N-n connessioni attive.

 il pool decrescerebbe a N = N-n connessioni attive.
Intanto ti ringrazio per la delucidazione, in ogni caso avevo scritto il 
messaggio avendo in mente le mie necessità e non ho chiarito molto a 
riguardo :-(
L'idea di connection pooling, penso, va bene nelle situazioni in cui il 
client si comporta come un utente che naviga su un sito, cioè fa la 
query legge i risultati (con il suo tempo) ed eventualmente fa altre 
operazioni. In questo modo quando si deve effettuare una query si chiede 
al connection pool manager quale è la risorsa (connessione - 
transazione) libera e ovviamente ce ne saranno più delle effettive 
connessioni.
Effettivamente in questo modo c'è un gran bel vantaggio!! Durante tutto 
il tempo in cui non c'è scambio dati con un client il server può 
lavorare con altre connessioni.
Purtroppo le connessione con cui ho a che fare sono continue e senza 
tempi morti (se leviamo la fase di login in cui il server deve fare la 
query di controllo). In pratica il client deve salvare in sequenza una 
serie di dati sul db, inoltre devo fare il commit sul database solo 
quando tutto è stato inviato correttamente. Anche se potessi spezzettare 
uno scarico dati in due o tre transazioni non vedo un grosso vantaggio, 
perché quando un client chiude la prima transazione può anche non 
ritrovare la risorsa libera e quindi deve aspettare. Considerando che i 
client sono degli hardware che si collegano a internet con il modem alla 
fantastica velocità di 9600 bps, tanto vale che si finisce di servire il 
client iniziato, almeno l'utilizzatore dell'hardware paga meno in 
bolletta ;-)
Mi sa che utilizzerò un set di connessioni sempre attive al database che 
verrano gestite da una classe e ogni client che si connetterà prenderà 
la prima connessione libera. In questo modo elimino tutta la fase di 
avvio connessione con il db per ogni client che si connette.
Cmq ti ringrazio ancora per avermi fatto conoscere il Connection pooling 
perché mi servirà a breve per altre cose :-)

 avvio connessione con il db per ogni client che si connette.
Cioè il connection pooling ?


 Cioè il connection pooling ?
Si, anche se per come la vedevo io l'enorme vantaggio del connection 
pooling si ha quando ci sono dei tempi morti nella trasmissione dati 
cosa che io non ho, quindi l'unico vantaggio si ha solo 
nell'eliminazione dei tempi di connessione al db.

 pooling si ha quando ci sono dei tempi morti nella trasmissione dati 
No, l'enorme vantaggio del "connection pooling" è di non far proliferare
senza controllo le connessioni al db, riutilizzandole.  Le connessioni al
DB possono anche essere continuamente impiegate, non c'entrano i tempi
morti.
Il cp va sicuramente bene anche nel tuo caso (se non è così, devi rivedere
l'applicazione, perché c'è qualcosa che non va).


 
Il problema è il client!! Quest'hardware può fra le altre cose:
1) mandare il numero di record
2) mandare i suoi dati sempre dall'inizio del suo archivio
3) cancellare tutto l'archivio
Purtroppo non può cancellare dall'archivio un singolo record... o tutto 
o niente :-(
Quindi se mi manda i dati e qualcosa va male devo farmi rimandare tutto 
dall'inizio... solo quando manda tutti i record si può cancellare sul 
client e fare il commit sul database, quindi lato server non saprei come 
ottimizzare le transazioni