Connection Pooling Nedir?

demironW47
GoTurkiye
Published in
4 min readSep 14, 2023

Bir backend yazdığınızı düşünün ve sadece bir veritabanı kullanıyorsunuz ve açtığınız veritabanı bağlantısını kapatmadınız, böyle bir senaryoda başınıza neler gelebilir? Anlık olarak kaç kişiye hizmet verebilirsiniz?

src

Bu gün anlatmak istediğim konu connection pooling, ama öncesinde soruya cevap vermem gerekirse; Aldığınız her request’te db’ye gitmek için bir connection açtığınızı varsayarsak ve bunları kapatmadığınızı biliyoruz, bir süre sonra db’de boşta port kalmayacağı için sisteminiz yanıt veremez hale gelebilir. (senaryolardan sadece bir tanesi)

Connection Pooling Nedir?

Yukarıda oluşabilecek olan problemi çözmeye yönelik bir stratejidir. Sadece veritabanı bağlantısı olarak düşünmeyin, bu herhangi bir tcp bağlantısı olabilir. Bu yöntemde oluşturulan tcp bağlantıları sonlandırılmak yerine daha sonra kullanmak için saklanır, ihtiyaç duyulması halinde yeniden oluşturmak yerine havuzdan boş bir tcp bağlantısı kullanılır. Peki neden sadece bir tane bağlantı kullanmıyoruz? Bunun cevabı basit, birden fazla istek geldiğinde bunları aynı anda veritabanına iletebilmek için birden fazla bağlantıya ihtiyaç duyarız. Sürekli yeni bağlantı oluşturmanın bir dezavantajı da sürekli 3-way handshake işleminin gerçekleşmesidir, bu da kodunuzu yavaşlatacağından daha yavaş response üretmenize neden olacaktır.

src

Bunu go’da implemente etmeden önce sisteminize gelen trafik ve veritabanı sorgularınızın yanıt süresi üzerinden, havuzda kaç tane connection tutacağınızı hesaplayabilirsiniz. Örnek olarak 20ms süren bir sql sorgusundan bir saniyede 50 tane sorgu çalıştırılabilir.

Case

Go’ dilinin standart kütüphanesi içerisindeki database/sql paketi sayesinde bağlantı havuzunu çok kolay bir şekilde yönetebiliriz.

import (
"database/sql"
"log"
"time"
)

func connectWithStandartLibrary() *sql.DB {

db, err := sql.Open("mysql", "user:pass@tcp(localhost:3306)/test_db")
if err != nil {
log.Fatal(err)
}
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
db.SetConnMaxIdleTime(time.Minute * 3)
db.SetConnMaxLifetime(time.Hour)

return db
}

database/sql paketini kullanarak yukarıdaki gibi 4 tane metodu çağırabiliriz.

  • SetMaxOpenConns: Çalıştırdığınız projenin aynı anda açık olabilecek maksimum bağlantı sayısını belirmek için vardır. Veritabanındaki konfigürasyonlardan ötürü maksimum bağlantı sayısını belirtmek isteyebilirsiniz.
  • SetMaxIdleConns: Anlık yük altında oluşturulan ve daha sonra görevlerini tamamlayıp beklemeye alınacak olan maksimum bağlantı sayısını belirler.
  • SetConnMaxIdleTime: Beklemeye alınan bağlantıların ne kadar süre içerisinde kullanılmaması durumunda kapatılacaklarını ifade eder.
  • SetConnMaxLifeTime: Açılan bir bağlantının ne kadar süre sonra kapatılacağının bilgisini ifade etmek için kullanılıyor.

Benim örneğimde anlık olarak maksimum 10 tane bağlantı açılabiliyor, açılan bu bağlantılardan görevini tamamlamasına rağmen 5 tanesi kapatılmak yerine, tekrar kullanılma ihtiyacı doğabileceği için connection pool’a alınıyor. Buradaki ömürleri ise 3 dakika, 3 dakikanın sonunda eğer kullanılmamışlarsa bu bağlantılar kapatılıyor. Bir bağlantının sürekli kullanılması halindeyse 1 saatin sonunda o da kapatılarak başka bir bağlantı yerini alıyor. Bu yapıyı gorm kullanarak da yapabilirsiniz.

import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
"time"
)

func connectWithGorm() *gorm.DB {
db, err := gorm.Open(mysql.Open("user:pass@tcp(localhost:3306)/test_db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}

database, err := db.DB()
if err != nil {
log.Fatal(err)
}
database.SetMaxOpenConns(10)
database.SetMaxIdleConns(5)
database.SetConnMaxIdleTime(time.Minute * 3)
database.SetConnMaxLifetime(time.Hour)
return db
}

gorm standart kütüphanede bulunmadığı için bu bağımlılığı bilgisayarınıza yüklemeniz gerekiyor bunun için aşağıdaki komutu kullanabilirsiniz.

go get gorm.io/driver/mysql
go get gorm.io/gorm

Kodu test etmek için /point, /status ve /start endpointlerini tanımlıyorum. /point veritabanında bir select sorgusu çalıştırıyor. /status ise açık, boşta ve kapalı olan bağlantıların sayısını log’a basıyor. Son olarak /start a istek attığınızda paralel bir şekilde /point adresine istek atıyor (100 adet).

package main

import (
"encoding/json"
"gorm.io/gorm"
"log"
"net/http"
)

var DB *gorm.DB

func main() {

DB = connectWithGorm()
http.HandleFunc("/point", point)
http.HandleFunc("/status", status)
http.HandleFunc("/start", start)

err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}

func point(w http.ResponseWriter, r *http.Request) {
result := map[string]interface{}{}
DB.Table("exchange_models").Where("exchange_id = ?", "btc").Take(&result)
d, err := DB.DB()
if err != nil {
log.Fatal(err)
}
log.Println("ACTIVE CONNECTIONS: ", d.Stats().OpenConnections)
err = json.NewEncoder(w).Encode(result)
if err != nil {
log.Println(err)
}
}

func status(w http.ResponseWriter, r *http.Request) {
database, err := DB.DB()
if err != nil {
log.Fatal(err)
}

log.Println(
"Open: ", database.Stats().OpenConnections,
" Idle: ", database.Stats().Idle,
" Dead: ", database.Stats().MaxIdleTimeClosed,
)
}

func start(w http.ResponseWriter, r *http.Request) {
i := 0
for i < 100 {
go func() {
response, err := http.NewRequest(http.MethodPost, "http://localhost:8080/point", nil)
if err != nil {
log.Println("err: ", err)
}
client := &http.Client{}
resp, err := client.Do(response)
if err != nil {
log.Println(err)
}
defer resp.Body.Close()
}()
i++
}

}

ve programın ürettiği çıktı.

Yukarıdan aşağıya doğru bakıldığında aktif bağlantı sayısının azaldığını görülüyor, /start ın işlemi bitince /status’a gidiyorum ve sonuç olarak log’a 5 tane açık bağlantı olduğunu ve bunların boşta olduğunu basıyor. Konfigürasyonu yaparken maksimum bağlantı sayısının 10 olmasını ve bunlardan 5 tanesinin connection pool’da 3 dakikalığına saklanmasını istemiştik. Aradan 3 dakika geçtikten sonra tekrardan /status’a gidiyorum ve bu sefer açık olan ve boşta olan bir bağlantı olmadığını ve 5 tane bağlantının kapatıldığını görüyoruz.

Log’da renklendirilen kısım gorm tarafında otomatik olarak setlenen bir log, benim veritabanı sunucumun sorguları çok yavaş işlediğini söylüyor. kodlara erişmek için github repoma göz atabilirsiniz.

May the code be with you…

--

--