Merhaba arkadaşlar, bugün sizlere Node.js'te özellikle büyük ölçekli projelerde karşılaştığım bir performans tuzağından ve bunun nasıl üstesinden geldiğimden bahsedeceğim. Uygulamam büyüdükçe, farklı dosyalarda sürekli aynı ağır modülleri require etmeye başladım ve bu durum bellek kullanımında gözle görülür bir artışa, hatta bazen gereksiz CPU yüküne neden oldu. İşte benim kullandığım en temiz çözüm: Singleton Pattern.
Sorun Neydi?
Diyelim ki projenizde ağır bir veritabanı bağlantı havuzu, karmaşık bir konfigürasyon yöneticisi veya büyük bir harici kütüphane (örneğin, bir machine learning modeli) kullanıyorsunuz. Her require('./agirModul') çağrısı, Node.js'in modül önbelleğine güvense de, bazen modülün kendisi her seferinde yeni bir instance oluşturuyor olabilir. Bu da her dosyada aynı bağlantının tekrar tekrar kurulması veya aynı büyük objenin kopyalanması anlamına gelir. İlk fark ettiğimde, basit bir API endpoint'i test ederken bile bellek kullanımının sürekli tırmanışa geçtiğini gördüm.
Çözüm: Singleton Tasarım Kalıbı
Singleton, bir sınıfın veya nesnenin tüm uygulama boyunca yalnızca bir kez örneğinin (instance) oluşturulmasını ve bu tek örneğe global bir erişim noktası sağlanmasını garanti eden bir tasarım kalıbıdır. Node.js'te modül sistemi zaten bir nevi singleton davranışı sergiler, ancak bunu güçlendirmek ve kontrolü tamamen ele almak için pattern'ı açıkça uygulamak çok faydalı.
İşte ağır bir veritabanı bağlantısı için basit bir singleton modülü örneği:
Bu Modülü Nasıl Kullanırım?
Artık projemin herhangi bir dosyasında bu modülü require ettiğimde, her seferinde aynı tek instance'a erişirim. Yeni bağlantı kurulmaz, ağır işlem tekrarlanmaz.
Dikkat Edilmesi Gerekenler
Singleton harika bir araçtır ama sorumsuzca kullanılmamalı. Global state yaratır, bu da unit testleri zorlaştırabilir (çünkü testler birbirini etkileyebilir). Bağımlılıkları gizler ve kodun modülerliğini azaltabilir. Bu nedenle, gerçekten paylaşılması gereken, ağır kaynaklar (DB Connection, Config Manager, Logger, Cache Client) için kullanın. Her küçük utility fonksiyonu için singleton yazmaya gerek yok.
Siz Node.js projelerinizde benzer performans sorunları yaşadınız mı? Modül yüklemelerini optimize etmek için singleton dışında farklı hangi yöntemleri kullanıyorsunuz? Ya da singleton'ın test edilebilirlik konusundaki sıkıntılarını nasıl aşıyorsunuz? Yorumlarda deneyimlerinizi paylaşın!
Diyelim ki projenizde ağır bir veritabanı bağlantı havuzu, karmaşık bir konfigürasyon yöneticisi veya büyük bir harici kütüphane (örneğin, bir machine learning modeli) kullanıyorsunuz. Her require('./agirModul') çağrısı, Node.js'in modül önbelleğine güvense de, bazen modülün kendisi her seferinde yeni bir instance oluşturuyor olabilir. Bu da her dosyada aynı bağlantının tekrar tekrar kurulması veya aynı büyük objenin kopyalanması anlamına gelir. İlk fark ettiğimde, basit bir API endpoint'i test ederken bile bellek kullanımının sürekli tırmanışa geçtiğini gördüm.
Singleton, bir sınıfın veya nesnenin tüm uygulama boyunca yalnızca bir kez örneğinin (instance) oluşturulmasını ve bu tek örneğe global bir erişim noktası sağlanmasını garanti eden bir tasarım kalıbıdır. Node.js'te modül sistemi zaten bir nevi singleton davranışı sergiler, ancak bunu güçlendirmek ve kontrolü tamamen ele almak için pattern'ı açıkça uygulamak çok faydalı.
İşte ağır bir veritabanı bağlantısı için basit bir singleton modülü örneği:
JavaScript:
// dbConnection.js
let connectionInstance = null;
class DatabaseConnection {
constructor() {
if (connectionInstance) {
throw new Error('[COLOR=#E74C3C]DatabaseConnection[/COLOR] sınıfından sadece bir örnek oluşturulabilir!');
}
// Ağır bağlantı kurma işlemleri burada simüle ediliyor
this.connection = this.#connectToDatabase();
connectionInstance = this;
}
#connectToDatabase() {
console.log('Veritabanına bağlanılıyor... Bu işlem maliyetli.');
// Gerçek bağlantı mantığı buraya gelir (mongoose.connect, pg.Pool vb.)
return { host: 'localhost', port: 5432, status: 'connected' };
}
static getInstance() {
if (!connectionInstance) {
connectionInstance = new DatabaseConnection();
}
return connectionInstance;
}
query(sql) {
// Sorgu çalıştırma mantığı
console.log(`Sorgu çalıştırılıyor: ${sql}`);
return { rows: [] };
}
}
// Modülü dışa aktarırken, sınıfın kendisini değil, instance'ı döndüren metodu veya doğrudan instance'ı dışa aktarabiliriz.
// 1. YOL: getInstance metodunu dışa aktarmak
module.exports = DatabaseConnection;
// 2. YOL (DAHA YAYGIN): Hemen instance'ı oluşturup onu dışa aktarmak
// module.exports = DatabaseConnection.getInstance();
Artık projemin herhangi bir dosyasında bu modülü require ettiğimde, her seferinde aynı tek instance'a erişirim. Yeni bağlantı kurulmaz, ağır işlem tekrarlanmaz.
JavaScript:
// userController.js
const DatabaseConnection = require('./dbConnection');
// YOL 1 için kullanım: getInstance() çağrılır
const db = DatabaseConnection.getInstance();
const users = db.query('SELECT FROM users');
// YOL 2 için kullanım (modülde hemen instance export edilmişse):
// const db = require('./dbConnection');
// const users = db.query('SELECT FROM users');
// productController.js
// Aynı modülü burada da require edelim
const db2 = require('./dbConnection').getInstance(); // veya direkt require('./dbConnection')
console.log(db === db2); // true yazdıracak! İkisi de aynı nesne.
Singleton harika bir araçtır ama sorumsuzca kullanılmamalı. Global state yaratır, bu da unit testleri zorlaştırabilir (çünkü testler birbirini etkileyebilir). Bağımlılıkları gizler ve kodun modülerliğini azaltabilir. Bu nedenle, gerçekten paylaşılması gereken, ağır kaynaklar (DB Connection, Config Manager, Logger, Cache Client) için kullanın. Her küçük utility fonksiyonu için singleton yazmaya gerek yok.
Siz Node.js projelerinizde benzer performans sorunları yaşadınız mı? Modül yüklemelerini optimize etmek için singleton dışında farklı hangi yöntemleri kullanıyorsunuz? Ya da singleton'ın test edilebilirlik konusundaki sıkıntılarını nasıl aşıyorsunuz? Yorumlarda deneyimlerinizi paylaşın!