DEV Community

Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

Database Connection Pooling for Side Projects: Optimization in 3 Steps

Introduction: Why Am I Getting Into Connection Pooling?

Recently, in a small side project I developed, I felt the need to make an optimization regarding database performance. Since this project is usually accessed by single users or only occasionally, it was a topic I hadn't paid much attention to at first. However, with increasing requests, I realized the overhead of establishing a new database connection on every single request. This situation can lead to a serious performance bottleneck, especially in applications that query frequently.

Although establishing a connection seems like a simple operation, it actually involves steps like TCP handshake, authentication, and authorization. Especially in high-traffic applications, repeating these steps for every request unnecessarily consumes server resources and prolongs response times. This is exactly where database connection pooling comes into play. In this article, we will examine in detail why connection pooling is important specifically for side projects, how it is implemented, and how we can optimize it in three steps.

What is Connection Pooling and Why is It Important?

Connection pooling is a technique where database connections are pre-created and maintained in a pool, allowing the application to reuse these connections when requested. When a query is finished, the connection is not closed; instead, it is returned to the pool and made ready for the next request. This approach significantly improves performance, especially in applications that require frequent database access.

ℹ️ Key Benefits of Connection Pooling

  • Performance Boost: Eliminates the cost of establishing new connections.
  • Resource Efficiency: Enables more effective use of server resources (CPU, memory).
  • Scalability: Helps the application handle more concurrent requests.
  • Connection Management: Protects the database from being overloaded by limiting the maximum number of connections.

If you ask why we should pay attention to this in side projects, the answer is simple: before saying "it's good enough," we need to see that we can make big differences even with small optimizations. In a financial calculator application I developed, I was initially calling connection.close() manually on every query. However, in a scenario where multiple users tried to generate reports at the same time, I noticed the system slowed down. At a moment with about 50 active users, a connection time of about 100ms for each request led to total delays of several seconds. After enabling connection pooling, this time dropped to an average of 20ms. This is an optimization that should not be ignored, even for small and medium-sized projects.

Step 1: Choosing the Right Connection Pool Library

There are various connection pooling libraries available for many different databases and languages. When making a choice, it is important to consider the database, programming language, and framework your project uses. For example, if you are working with Python, you can use the built-in connection pooling features of psycopg2 or the advanced pooling mechanisms provided by ORMs like SQLAlchemy.

In the Java world, high-performance libraries like HikariCP are highly popular. For Node.js, the Pool module of node-postgres or ORMs like sequelize can be preferred. The important thing is that the library is well-documented, actively developed, and offers features suitable for your project's needs.

💡 Things to Consider When Choosing a Library

  • Performance: How fast the library provides and manages connections.
  • Configuration Options: Settings like minimum/maximum connection count, connection timeout.
  • Error Handling: How it handles connection errors and its retry mechanisms.
  • Supported Databases: Compatibility with the database your project uses.
  • Active Development and Community Support: Whether there is a community where you can find help when you run into issues.

Since I usually work with PostgreSQL in my own side projects, I use SQLAlchemy's pooling features. This is ideal for me both in terms of its compatibility with the Python ecosystem and the flexibility it offers. For example, in a financial analysis tool, instead of connecting to the database for every single user request, I automate this process using SQLAlchemy's default connection pool. This pool starts with 10 connections by default and can go up to a maximum of 20 connections. This setting allows the application to serve multiple users simultaneously while keeping the load on the database under control.

Step 2: Configuring Basic Connection Pool Settings

After choosing the right library, the next step is to configure the basic connection pool settings. These settings help you strike the right balance between performance and resource utilization. The most critical settings are:

  • Minimum Connection Count (Min Pool Size): The minimum number of connections that will always be kept in the pool. This number of connections is created when the application starts. Even during low-traffic times, this number ensures fast responses to initial requests during sudden traffic spikes.
  • Maximum Connection Count (Max Pool Size): The maximum number of connections that can be kept in the pool. This setting is crucial for preventing database overload and protecting server resources.
  • Connection Timeout (Acquire Timeout): If all connections in the pool are busy and a new connection is requested, this is the duration the application will wait for a new connection. If this time is exceeded, a connection error is thrown.
  • Connection Lifetime (Max Lifetime): Determines how long a connection can remain active in the pool. This helps periodically replace old or problematic connections.

When determining these settings, you should consider your project's typical traffic load, your database capacity, and your server resources. For example, for a very low-traffic side project, setting max_pool_size to a low value like 2-3 might be sufficient. However, for a more intensive application, you may need to increase this value to 10-20 or even higher.

To give an example, I was using PostgreSQL in the backend of an Android application I developed earlier. This backend managed users' task lists. Initially, I had set max_pool_size to 5. However, as the application became popular, users started experiencing delays during peak hours. When I increased max_pool_size to 15 and set connection_timeout to 30 seconds, this problem was largely resolved. However, while making this adjustment, I was also carefully monitoring the CPU and memory usage of my database server. When I increased max_pool_size to 20, CPU usage reached up to 80%, so 15 remained the sweet spot.

# SQLAlchemy ile temel connection pool yapılandırması örneği
from sqlalchemy import create_engine

# PostgreSQL bağlantı URL'si
DATABASE_URL = "postgresql://user:password@host:port/database"

# Engine oluştururken pooling ayarlarını yapılandırıyoruz
engine = create_engine(
    DATABASE_URL,
    pool_size=10,           # Maksimum bağlantı sayısı
    max_overflow=5,         # pool_size aşıldığında ek olarak açılabilecek bağlantı sayısı
    pool_timeout=30,        # Bağlantı beklenirken maksimum bekleme süresi (saniye)
    pool_recycle=1800       # Bağlantıların yeniden kullanılmadan önce geri dönüştürüleceği süre (saniye) - 30 dakika
)

# Artık bu 'engine' nesnesini kullanarak veritabanı işlemlerini yapabilirsiniz.
# SQLAlchemy otomatik olarak connection pool'unu kullanacaktır.
Enter fullscreen mode Exit fullscreen mode

Step 3: Advanced Tuning and Tips

After making the basic adjustments, there are some advanced techniques and tips to further optimize your connection pool's performance. These come into play especially as your project grows or when you encounter more specific performance issues.

  • Connection Health Checks: Some pooling libraries offer the ability to check if connections in the pool are still valid. This prevents using an invalid connection during network outages or database restarts. For example, in PostgreSQL, this check can be done with the pg_isready command or a simple SELECT 1 query.
  • Closing Idle Connections (Idle Connection Timeout): Automatically closing connections that have not been used for a long time helps free up resources. This is especially important when you set max_pool_size high.
  • Transaction Management: It is important not to keep connections busy unnecessarily during long-running or complex transactions. Try to perform short-lived and atomic operations as much as possible.
  • Database-Level Settings: Connection pooling is important not only on the application side but also on the database side. In PostgreSQL, the max_connections parameter determines how many concurrent connections the server can accept. You should align this value with your application-side max_pool_size. Otherwise, even if the application fills the connection pool, the database might reject new connections.

In the financial calculator application I mentioned in the previous section, I used the pool_recycle setting. This setting ensures that connections are recreated after a certain period (e.g., 30 minutes). This helps prevent potential issues that might arise due to temporary network drops or minor database-side changes, especially in long-running applications. Thanks to this setting, I did not experience any connection-related problems even in a system running uninterrupted for 3 days.

# PostgreSQL'de max_connections ayarı örneği (postgresql.conf dosyasında)
# Bu değer, sunucunun kaynaklarına göre ayarlanmalıdır.
# Uygulama tarafındaki max_pool_size'dan daha yüksek olmalıdır.
max_connections = 100
Enter fullscreen mode Exit fullscreen mode

⚠️ Things to Watch Out For

  • Excessively High Max Pool Size: Opening more connections than your database server can handle can degrade performance and even bring the server to a crash point.
  • Too Low Connection Timeout: Closing connections too quickly can degrade performance by increasing the need to re-establish connections.
  • Forgotten Connections: Always ensure connections are properly closed or returned to the pool using `try...finally` blocks or `with` statements. ORMs usually do this automatically.

Conclusion: Small Optimizations, Big Impacts

Side projects can often be the areas where we spend the least time or allocate the fewest resources. However, even these projects can be made much more efficient with the right optimization techniques. Database connection pooling is at the forefront of these optimizations. Reusing pre-created connections instead of establishing a new connection on every request both increases application performance and allows us to use server resources more efficiently.

In this article, we discussed what connection pooling is, why it is important, and how it can be optimized with the right library selection, basic settings, and advanced tuning tips. Based on my experience in my own projects, I can say that this simple yet effective technique will make a difference even in your small projects. Remember, instead of saying "it's good enough," you can make your systems more robust and performant with small steps.

In my next post, I will probably talk about a topic related to system security or perhaps the architecture of a side product I developed.

Top comments (0)