
A customer opens a dashboard that used to appear in less than a second.
Eighteen months later, the same screen takes four seconds for the company's largest accounts. The frontend has barely changed, the API servers are not overloaded, and adding another application instance makes no noticeable difference.
A trace eventually shows why.
The API spends about 200 milliseconds doing application work and almost three seconds waiting for the database.
The dashboard was never really an application-server performance problem. As customer data accumulated, one query began reading far more rows, another started sorting a much larger result set, and several smaller lookups multiplied the amount of database work required for each request.
This is why database performance has such a direct effect on application speed: for many user actions, the application cannot respond until the database finishes its part of the work.
A slow query can therefore become a slow API, which becomes a slow screen, which users simply experience as "the app is slow."
For Levelworks, database performance sits squarely within Performant App Health, but it is also closely connected to Observable. Improving database performance starts with being able to see whether a request is spending its time in application code, waiting for data, waiting for a lock, or waiting for a database resource to become available.
Consider a typical request:
User clicks "Open Account"
↓
API authenticates request
↓
Application loads account
↓
Database retrieves transactions
↓
Database calculates balances
↓
Application formats response
↓
Screen renders
If the database portion takes 2.5 seconds, optimizing 50 milliseconds from the application layer will barely change what the user experiences.
Database performance matters especially when an application performs several database operations sequentially.
A request might spend:
Operation
Time
Authentication
70 ms
Customer lookup
90 ms
Order query
850 ms
Aggregation query
1,100 ms
Application processing
120 ms
Serialization
80 ms
The database accounts for almost two seconds of a roughly 2.3-second response.
That is why database optimization should begin with request-level evidence rather than an assumption that "the database seems busy."
[Internal Link: “Why Is My App Slow? 10 Common Causes of Poor Application Performance”]
SQL describes the result an application wants, but the database still has to determine how to retrieve that result efficiently.
Depending on the query and available indexes, it may scan rows, join tables, sort intermediate results, aggregate values or write temporary data before returning anything.
Microsoft's SQL Server query-processing documentation explains that the database query optimizer considers different ways of accessing tables and executing joins, filtering, aggregation and sorting before choosing an execution plan. The plan selected depends partly on the schema, indexes and statistics describing the data.
That means two SQL statements producing identical results can require very different amounts of work.
Consider:
SELECT *
FROM orders
WHERE customer_id = 48392;
If customer_id is appropriately indexed and the result contains relatively few rows, retrieval may be inexpensive.
Without a useful index, the database may need to inspect a much larger portion of the table.
At 50,000 orders, the difference may barely matter. At 100 million, it can dominate the application's response time.
The key question is not merely whether a query returns the correct data.
It is:
How much work does the database perform to find that data?
Indexes allow databases to locate certain records without examining the entire underlying dataset.
Used well, they can dramatically reduce the work required for frequently executed filters, joins and lookups.
SQL Server's index design guidance recommends considering indexes on columns frequently used in query predicates and joins, while also warning against indiscriminately adding indexes because unnecessary indexes consume storage and make writes more expensive.
That trade-off matters.
An application with a read-heavy customer search may benefit greatly from the right index.
An application with many writes can be harmed by dozens of unnecessary indexes because every insert or update may need to maintain them.
The answer is therefore not "index everything."
Use actual workload evidence to identify where an index can remove expensive scans or sorts, and verify that queries actually use it.
Not every database bottleneck appears as a single query taking several seconds.
Sometimes the problem is query count.
Suppose an API retrieves 100 orders and then fetches the customer separately for each order.
The application performs:
That is 101 database round trips for one request.
Even if every customer lookup requires only 10 milliseconds, the accumulated cost becomes significant.
This pattern is often called an N+1 query problem, and it frequently appears through object-relational mapping layers when application code retrieves related data one record at a time.
When a request is slow, therefore, inspect both:
How long does the slowest query take?
and
How many queries does the request execute?
Ten reasonably efficient queries may still be unnecessary if one query or better data-loading strategy could perform the same work.
The issue is not SQL elegance. Each database round trip introduces execution, connection and network overhead that contributes to user-visible latency.
One of the most important facts about database performance is that code does not need to change for performance to deteriorate.
The data can change instead.
Imagine a reporting query written when the largest customer had 20,000 transactions.
Three years later, major customers have several million.
The SQL is identical, but the database now has to work with:
This is one reason an application that felt fast at launch can become slower gradually.
Performance assumptions expire as the dataset grows.
A particularly useful clue is when:
When that happens, test queries using representative data volumes rather than a development database containing a few hundred perfect records.
Relational databases generally use statistics about stored data to estimate which execution strategy will be efficient.
As data distribution changes, the best plan can change too.
PostgreSQL's current ANALYZE documentation explains that collected statistics are used by the query planner to choose efficient execution plans. PostgreSQL normally updates these statistics automatically through its maintenance processes, but the quality and freshness of planner information can influence execution choices.
Microsoft similarly notes that SQL Server's optimizer relies on distribution statistics to estimate the cost of different access strategies; outdated statistics can lead to less appropriate plan choices.
This produces a performance problem that can initially look confusing.
The query has not changed.
The application release has not changed.
Yet execution suddenly becomes much slower because the database is now choosing a different way to perform the work.
This is why query-plan inspection matters.
PostgreSQL provides EXPLAIN and EXPLAIN ANALYZE specifically for understanding how a query is executed and how much work its plan performs.
Do not optimize complex queries by reading SQL text alone. Inspect what the database actually does.
A query does not have to consume large amounts of CPU to be slow.
It may simply be waiting.
Databases use locking and other concurrency controls to protect data when multiple transactions interact with the same records.
PostgreSQL's locking documentation explains that conflicting locks can cause one process to wait until another transaction releases the required lock.
Imagine one transaction:
Another request needs to update the same account.
Its SQL may normally take 20 milliseconds, but it cannot execute immediately because it is waiting for the first transaction.
From the application user's perspective, the second request simply takes six seconds.
This is why query duration alone can be misleading.
You need to know whether time is being spent:
Google Cloud's Query Insights exposes database load using dimensions such as CPU, I/O waits and lock waits, precisely because "slow" can represent several fundamentally different database conditions.
Long-running transactions deserve particular scrutiny because they can extend the period during which other work must wait.
Applications need connections to communicate with a relational database.
Creating a fresh connection for every operation adds overhead, which is why applications commonly use connection pools.
Google Cloud's database connection guidance describes a connection pool as a reusable cache of database connections intended to reduce connection latency and improve resource use.
But pools introduce their own performance boundary.
Suppose an application can maintain 50 usable database connections.
During a traffic spike, 200 requests arrive and all need the database.
Some requests execute immediately. Others wait for a connection to become available before they can even submit SQL.
Application monitoring may show high API latency while individual query timings look reasonably normal.
That discrepancy is a clue.
Monitor:
Do not automatically increase pool size.
Opening substantially more connections can simply move congestion into the database itself, increasing contention and resource usage.
The application pool and database capacity need to be sized as one system.
Even well-written queries need resources.
If the database reaches its practical limits for CPU, memory, storage I/O or another constrained resource, query latency can increase across large parts of the application.
AWS's current CloudWatch Database Insights measures database load through session activity and allows teams to break that load down by waits, SQL statements, hosts and users.
That distinction is valuable because high database load can mean different things.
If sessions are primarily waiting for CPU, adding or optimizing CPU-intensive workloads may matter.
If they are waiting on I/O, the investigation is different.
If lock waits dominate, adding a larger database instance may do very little because the bottleneck is concurrency rather than raw hardware.
Resource monitoring should therefore answer:
What are active database sessions doing or waiting for?
not simply:
Is CPU high?
Application queries sometimes request considerably more data than the user needs.
For example:
SELECT *
FROM transactions
WHERE account_id = ?;
may return years of transaction history even though the screen initially displays only the most recent 50 records.
That creates cost at several layers.
The database retrieves and transfers more rows. The application deserializes more data, memory use increases, network transfer grows, and the frontend may then process information it never displays.
Reducing unnecessary rows and columns can therefore improve performance beyond the database itself.
Typical opportunities include:
This is another reason database performance cannot be viewed in isolation. The database workload is partly created by how application code asks for data.
A customer-facing application may share its database with:
One large report may scan millions of rows or consume significant I/O while ordinary users are trying to perform small transactional requests.
Suddenly, simple screens become slower even though their own SQL has not changed.
Look for correlations between application latency and:
If workloads with very different characteristics compete for the same constrained database resources, the solution may involve scheduling, workload isolation, replicas, architectural separation or query improvements.
The right option depends on the workload. The important diagnostic point is that the query making the application slow may not be the query belonging to that application request.
Do not begin a performance project by tuning random queries.
Trace one slow user workflow from end to end.
For example:
User request 3,200 ms
Application processing 180 ms
Waiting for DB connection 320 ms
Database queries 2,450 ms
External dependency 110 ms
Other 140 ms
Now the database deserves attention.
Next, break database time down further.
Signal
What it may indicate
One query dominates execution time
Query/index/plan problem
Hundreds of small queries
N+1 or excessive round trips
Query fast alone but slow under load
Contention or resource saturation
High lock wait
Long/conflicting transactions
Long connection wait
Pool saturation
Slowness grows with customer data
Data-volume/query-scaling problem
Sudden change without code release
Plan/statistics/workload change
High database load from another job
Workload contention
Google Cloud Query Insights and AWS Database Insights both emphasize this workload-oriented approach: identify the SQL or wait category creating database load, then work backward toward the application path responsible for it.
[Internal Link: “Application Performance Optimization: A Practical Guide”]
Increasing database resources can be the right answer.
If an efficiently designed workload genuinely needs more CPU, memory, storage throughput or read capacity, scaling the database is reasonable.
But additional hardware can also hide inefficient behaviour.
A larger instance does not necessarily solve:
The danger is creating a recurring cycle:
performance falls → database grows → workload expands → performance falls again
Before scaling, determine which resource is constrained and why.
If one poorly designed query is consuming most database capacity, fixing that query may create far more headroom than increasing the instance size.
A practical database-performance investigation usually works best in this sequence:
Begin with the screen, API or business action users experience as slow.
Use application traces or timing to establish how much of the workflow is actually spent accessing the database.
Look for expensive queries, excessive call counts, connection waiting, lock waiting and resource contention.
Understand how the database retrieves and processes the data rather than guessing from the SQL.
Determine whether the workload has outgrown assumptions that were valid when the code was written.
That may involve a query change, an index, fewer round trips, shorter transactions, improved connection handling or another specific correction.
A query becoming 80% faster matters only if it meaningfully improves the application workflow that justified the work.
Keep slow-query, database-load and application-latency visibility in place so the same bottleneck does not quietly return as traffic or data grows.
It is tempting to treat the database as infrastructure sitting underneath the application.
Users do not experience that separation.
If a search request waits for SQL, they experience slow search. If checkout is blocked behind a database lock, they experience slow checkout. If connection saturation adds two seconds before a query begins, they experience a slow application.
That is why Levelworks treats database performance as part of maintaining Performant App Health rather than as an isolated DBA concern.
Observable App Health strengthens it: traces should connect slow user journeys to database calls, while database monitoring should distinguish execution time from connection, CPU, I/O and lock waits.
[Internal Link: “Caching Explained: When It Makes Your App Faster and When It Doesn’t”]
The most productive database-performance question is therefore not:
“Is our database fast?”
It is:
“For the workflows our users care about, how much time are we spending asking for data, what work is the database doing during that time, and how will that behaviour change as our application grows?”
When those questions are measurable, the database stops being a mysterious component blamed whenever the application feels slow. It becomes another part of the request path whose performance can be understood, maintained and improved deliberately.