Το connection pooling είναι μια cache από database connections που σκοπό έχει αυτά που είναι ελεύθερα στην cache αυτή να μπορούν να επαναχρησιμοποιηθούν κάθε φορά που κάποιος ζητάει να κάνει connect στην database.
Η χρησιμότητα του connection pooling είναι αρκετά σημαντική στην εκτέλεση διαδικασιών πάνω στην database από το application καθώς με αυτό τον τρόπο γίνεται αφενός γρηγορότερη επικοινωνία αφετέρου καταναλώνονται λιγότεροι πόροι.
Κάθε φορά που δημιουργείται ένα connection από την εφαρμογή προς την database αυτό αυτόματα μπαίνει στο pool με σκοπό να μπορεί να επαναχρησιμοποιηθεί σε επόμενη κλήση, με την προϋπόθεση βέβαια ότι δεν χρησιμοποιείται ήδη.
Πρέπει να επισημανθεί ότι τα περισσότερα RDBMS υποστηρίζουν το connection pooling. Πέρα όμως από αυτό σημαντικός παράγοντας είναι και ο provider που χρησιμοποιούμε στην εφαρμογή μας για να επικοινωνήσουμε με τον database server. Για παράδειγμα όλοι οι .NET providers έχουν connection pooling.
Σημείωση: Από εδώ και κάτω όταν αναφέρεται ο όρος client μπορεί να είναι ένα client pc αλλά μπορεί να είναι και κάποιος server από τον οποίο γίνονται τα connections στο database server
Για να μπορεί ένα application να συνδεθεί στον database server και την database που υπάρχει σε αυτόν όπως είναι γνωστό θα πρέπει να έχει ένα connection string. Tο connection string μπορεί να έχει μια από τις παρακάτω μορφές ανάλογα με τον τρόπο που έχει επιλεγεί να γίνεται το authentication
Στην περίπτωση που έχει επιλεγεί Windows Authentication το connection string είναι το παρακάτω
Server=<DBServerNameOrAddress>;Database=<DataBaseName>;Trusted_Connection=True;
Ενώ εφόσον έχει επιλεγεί SQL Authentication το connection string διαμορφώνεται ως εξής
Server=<DBServerNameOrAddress>;Database=<DataBaseName>;User Id=<Username>;Password=<Password>;
Application Name
Κάτι που είναι αρκετά χρήσιμο, αλλά κατά 99% κανείς δεν το κάνει, είναι το connection string να περιέχει το Αpplication Νame όπως παρακάτω
Server=<DBServerNameOrAddress>;Database=<DataBaseName>;Trusted_Connection=True;Application Name=<AppName>;
Server=<DBServerNameOrAddress>;Database=<DataBaseName>;User Id=<Username>; Password=<Password>;Application Name=<AppName>;
Αυτό είναι αρκετά χρήσιμο για όλους και ειδικά για τους DBAs καθώς μπορούν αρκετά εύκολα να καταλάβουν από πού έρχεται το συγκεκριμένο connection προς τον database server και την database. Φυσικά θα πρέπει να αναφέρω ότι και χωρίς αυτό μπορούν να εντοπίσουν την συγκεκριμένη πληροφορία, αλλά θα πρέπει να ψάξουν για 20 sec ακόμα, παρόλα αυτά όμως συμβουλεύω και προσωπικά απαιτώ να υπάρχει αυτή η πληροφορία στο connection string.
Από τους βασικούς κανόνες που υπάρχουν όταν γράφουμε εφαρμογές που "μιλάνε" με databases είναι "το connection ανοίγει λίγο πριν το χρειαστούμε και κλείνει αμέσως με την εκτέλεση της διαδικασίας στη database". Αυτός είναι ένας χρυσός κανόνας και όσοι δεν τον χρησιμοποιούν θα πρέπει σοβαρά να το σκεφτούν καθώς χωρίς αυτόν χάνουν αρκετά σε performance και σπαταλούν χωρίς λόγο πόρους τόσο στον database server όσο και στο application και αυτό γιατί κάθε φορά που γίνεται instantiation σε ένα object απαιτούνται αρκετοί πόροι για την λειτουργία αυτή. Το ίδιο συμβαίνει κάθε φορά που γίνεται destroy το object αυτό. Το connection σε ένα database server είναι και αυτό ένα object και ακολουθεί το κανόνα αυτό.
Πως δουλεύει το Connection Pooling
Κάθε φορά που από την εφαρμογή θα ζητηθεί να κάνει μια διαδικασία στο database server συμβαίνει το εξής με σκοπό την εξοικονόμηση πόρων αλλά και την βελτιστοποίηση της απόδοσης
- Αρχικά ελέγχει αν υπάρχει connection pool για την συγκεκριμένη εφαρμογή και για τον χρήστη που εκτελεί αυτή.
- Αν δεν υπάρχει δημιουργεί ένα νέο connection και βάζει αυτό στο pool σαν ενεργό και το δίνει στην εφαρμογή για χρήση.
- Αν υπάρχει pool τότε ελέγχει αν υπάρχει connection που δεν χρησιμοποιείται ήδη και δίνει αυτό για χρήση στην εφαρμογή.
- Αν δεν υπάρχει ελεύθερο connection στο pool δημιουργεί ένα νέο και δίνει αυτό για χρήση στην εφαρμογή ενώ παράλληλα το προσθέτει στο pool σαν ενεργό.
- Κάθε connection που θα σταματήσει να χρησιμοποιείται από την εφαρμογή θα χαρακτηριστεί σαν ανενεργό και θα μπορεί να χρησιμοποιηθεί σε επόμενο request.
Min / Max Pool Size
Ο αριθμός των connections που μπορούν να κρατηθούν στο pool αν δεν έχει ορισθεί explicit στο connection string είναι 100.
Στη περίπτωση που όλα τα connections που μπορεί να δεχθεί το pool είναι ενεργά και απαιτηθεί ένα νέο connection από την εφαρμογή τότε λαμβάνουμε το παρακάτω μήνυμα λάθους
"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached."
Αυτό συμβαίνει σπάνια και όταν συμβαίνει σημαίνει ότι είτε έχει γίνει λάθος στον εκτιμώμενο αριθμό των concurrent users είτε κάτι άλλο έχει προστεθεί στο application χωρίς να γίνει μέριμνα για τα connections, είτε δεν υπάρχει σωστός κώδικας ώστε να κλείνουν τα connections που δεν χρησιμοποιούνται από την εφαρμογή.
Η εύκολη λύση για την επίλυση του προβλήματος αυτού είναι να αλλάξει κανείς τα default settings του connection pooling στο connection string.
Αυτό γίνεται με το να προστεθεί στο connection string το Min Pool Size και το Max Pool Size με το δεύτερο να έχει την μεγαλύτερη σημασία.
Το Min Pool Size ορίζει το αριθμό των connections που θα δημιουργηθούν στο pool με το που θα δημιουργηθεί το πρώτο connection. H default τιμή του είναι μηδέν. Αν για παράδειγμα την κάνουμε δέκα (10) τότε με το που θα δημιουργηθεί το πρώτο connection θα φτιάξει ακόμα 10 που θα είναι ανενεργά και έτοιμα για χρήση.
Το Max Pool Size ορίζει το μέγιστο αριθμό των connections που μπορεί να έχει το pool και όπως ήδη έχω αναφέρει η default τιμή του είναι 100. Αυτό μπορεί να αλλάξει είτε παραπάνω είτε παρακάτω. Συνήθως οι περισσότεροι όταν βγαίνει το παραπάνω μήνυμα πηγαίνουν στο connection string και αυξάνουν τον αριθμό αυτό. Ναι είναι μια γρήγορη λύση που όμως θα πρέπει να γίνει ενδελεχής έρευνα γιατί υπήρχε αυτή η αύξηση.
Πως και πότε γίνεται create το Connection Pool
Έχω παρατηρήσει ότι υπάρχει μια παρανόηση για το πώς δημιουργείται ένα connection pool που πρέπει να σταματήσει.
Έστω ότι έχω ένα desktop application που συνδέεται απευθείας με τον database server και είναι εγκατεστημένο στο client. Όταν ξεκινάει το application αυτό στον client αποκτά ένα process id. Για αυτό το process id και για το connection string που έχει το application δημιουργείται ένα connection pool.
Αν ξεκινήσω ένα δεύτερο instance του application τότε αυτό θα πάρει ένα νέο process id και αυτό θα δημιουργήσει ένα νέο connection pool.
Αυτό σημαίνει ότι θα έχω δύο connection pools που έρχονται από τον ίδιο client.
Αν το application ξεκινήσει σε άλλον client τότε θα έχει ένα process id στον client αυτό και ο συνδυασμός client & process id θα δημιουργήσει ένα νέο connection pool.
Με απλά λόγια κάθε desktop application instance έχει το δικό του connection pool.
Αν έχω ένα Web Application, Web Server, WCF Service, Windows Service ισχύει ο ίδιο συνδυασμός. Με απλά λόγια εάν έχω ένα Web server και σε αυτόν υπάρχει ένα web site τότε έχω ένα connection pool για όλους τους χρήστες που κτυπάνε αυτό όταν το connection string είναι κοινό για όλους. Κοινό connection string σημαίνει ότι όλοι χτυπάνε την βάση με τον ίδιο account που είτε μπορεί να είναι trusted με το account που ξεκινάει το application pool του web site είτε είναι SQL Login. Αν όμως έχει γίνει personalization στον χρήστη που έχει γίνει authentication τότε δημιουργούνται ξεχωριστά connection pools. Το σύνηθες είναι το πρώτο σενάριο.
Αν ένα app χρησιμοποιεί application services για μιλήσει με τον database server τότε από αυτά δημιουργούνται connection pools. Αν από αυτό το app δημιουργούνται και απευθείας connections στο database server τότε έχω ξεχωριστό connection pool, πράγμα που σχεδιαστικά δεν είναι και το καλύτερο.
Ελπίζω με αυτά τα παραδείγματα να ξεκαθάρισε το τοπίο. Αν υπάρχει κάποια ερώτηση σχετικά με αυτό μπορείτε να την υποβάλετε στα σχόλια που ακολουθούν το άρθρο αυτό.
Μετά από όλα αυτά ένας DBA/DB Developer πρέπει να έχει την δυνατότητα να μετράει τα connection pools και τα connections που αυτά έχουν στο SQL Server instance συγκεντρωτικά ώστε να είναι σε θέση να γνωρίζει από πού έρχονται τα connections σε αυτό το instance. Πρέπει επίσης να επισημανθεί ότι αν σε ένα connection pool υπάρχουν Χ connections στον SQL Server θα έχω Χ sessions που θα είναι σε κατάσταση sleeping εφόσον δεν χρησιμοποιούνται.
Performance Monitor Counters
Ένας τρόπος είναι να χρησιμοποιηθεί το Performance Monitor και συγκεκριμένα στο .NET Data Provider for SqlServer counter set να χρησιμοποιηθεί ο counter NumberOfPooledConnections. Αυτός όμως δεν μπορεί να χρησιμοποιηθεί κάπως κεντρικά πχ πάνω στο SQL Server instance. Αυτός ο counter θα πρέπει να μετρηθεί σε επίπεδο client καθώς εκεί υπάρχει το πρωτεύον process id. Αν χρησιμοποιηθεί πάνω στο SQL Server instance θα δείξει μόνο τα process id που έχουν δημιουργηθεί σε αυτό.
Ο συγκεκριμένος μετρητής είναι αρκετά χρήσιμος για ανάλυση σε επίπεδο client όπως επίσης και σε επίπεδο web server / web site αλλά δεν δίνει μια συγκεντρωτική εικόνα στον DBA / DB Dev.
Monitoring from SQL Server
Παρόλα αυτά όμως ένας έμπειρος DBA / DBDev μπορεί να έχει την απόλυτη εικόνα και αυτό γίνεται με ένα απλό query πάνω στο DMV sys.dm_exec_sessions καθώς εκεί υπάρχει και οι πληροφορίες που χρειάζονται και είναι το host_name που δείχνει το client computer name, το host_process_id που δείχνει το process id που έχει δημιουργηθεί (για τα web sites/apps θα πρέπει να δει κάποιος τα process ids που έχουν πάρει από τον IIS), το client_interface_name που δείχνει ότι έχει μπει στο application name στο connection string και τέλος το login_name που χρησιμοποιείται στο connection string. Ένα απλό aggregation και voila.
select host_name,host_process_id, client_interface_name,login_name, count(*) as connectionsinpool
from sys.dm_exec_sessions
group by host_name,host_process_id, client_interface_name,login_name
Τώρα αν o DBA/DBDev είναι μερακλής και γουστάρει αυτό που κάνει μπορεί να προχωρήσει σε βάθος και να δει περισσότερα στοιχεία όπως ip addresses, tcp ports κλπ και αυτό μπορεί να γίνει διαβάζοντας δεδομένα από ένα άλλο DMV το sys.dm_exec_connections. Το οποίο είτε μπορεί να το δει μεμονωμένα είτε συνδυαστικά με τον sys.dm_exec_sessions.
select *
from sys.dm_exec_sessions as s
inner join sys.dm_exec_connections as c
on c.session_id=s.session_id
Φυσικά μπορεί να εφαρμοστεί και where clause ώστε φιλτράρει τα αποτελέσματα του
Από εκεί και πέρα όρεξη να έχει και μπορεί να φτάσει μέχρι και σε επίπεδο statement που κάθε connection εκτελεί. Παρακάτω μερικές τέτοιες παραλλαγές
-- investigate specific process id
select *
from sys.dm_exec_sessions as s
inner join sys.dm_exec_connections as c
on c.session_id=s.session_id
where s.host_process_id=9408
-- show the executed sql statement
select *
from sys.dm_exec_sessions as s
inner join sys.dm_exec_connections as c
cross apply sys.dm_exec_sql_text(c.most_recent_sql_handle)
on c.session_id=s.session_id
/*antonch*/