The Problem That Hides in Plain Sight
I have diagnosed dozens of performance problems in growing businesses, and more often than I expected, the root cause turns out to be the database. Not the application code. Not the server capacity. The database — specifically, a set of design decisions that made perfect sense at 10,000 records and become structural problems at 500,000.
The difficult thing about database bottlenecks is that they tend to surface gradually and then catastrophically. The application slows down incrementally for months. Teams blame the hosting provider, the network, the new feature that was just released. Then something tips — a busy quarter, a bulk import, a new reporting integration — and what was a slow application becomes an unavailable one.
Here are the five signs I look for first when I am brought in to diagnose a struggling system.
Sign 1: Reports That Take Longer Every Month
If the business intelligence reports that used to run in thirty seconds now take five minutes — and nobody changed the report logic — the database is almost certainly doing more work per query than it should be.
The usual cause is missing indexes on columns used in WHERE clauses, JOIN conditions, or ORDER BY statements. When the table had 10,000 rows, a full table scan was fast enough to be unnoticeable. At 2 million rows, that same scan reads two hundred times more data — and the query time scales accordingly.
This is one of the first things I check. In most relational databases, a single well-placed index can reduce a query from minutes to milliseconds. It is also one of the most under-used performance tools in small and mid-size development teams, because indexing strategies are not always taught alongside the basics of SQL.
A related issue is N+1 query patterns in the application layer — where the code runs one query to get a list of records and then one additional query per record to get related data. At small data volumes this is invisible. At scale it generates thousands of round trips to the database per page load.
Sign 2: Timeouts During Your Busiest Hours
If the application works fine during off-peak hours but throws errors or slows dramatically during your busiest periods, you are likely dealing with connection pool exhaustion or lock contention.
Connection pool exhaustion happens when the number of concurrent database connections exceeds what the pool is configured to handle. Each database connection is a resource. When they are all in use, new requests queue up waiting. If enough requests queue up, they time out. The fix involves both correctly sizing the connection pool and — more importantly — ensuring that connections are released promptly by the application code.
Lock contention is more insidious. It happens when multiple transactions are competing to write to the same rows or tables simultaneously. Long-running transactions hold locks that block other transactions. The result looks like a performance problem but is actually a concurrency design problem. Fixing it requires understanding which operations need to be atomic and redesigning transactions to minimize the duration of locks.
For B2B portals specifically, this pattern is especially common during order processing peaks — which is part of why I prioritize asynchronous patterns in the architecture I describe in my post on B2B portal architecture.
Sign 3: A Schema That Reflects How the Business Used to Work
This is the structural problem that no performance tuning can fully overcome. The database schema was designed for the business at an earlier stage — and the business has evolved in ways the schema was not designed to support.
Common manifestations:
- Tables with hundreds of nullable columns, where 80 percent of rows only populate a handful of them, because the original developer used the same table for multiple entity types
- Relationships that are maintained in application code instead of foreign keys, meaning the database has no way to enforce referential integrity
- Flat tables where hierarchical relationships (products and their variants, customers and their subsidiaries) are approximated with naming conventions rather than proper parent-child structures
- Dates stored as strings, amounts stored without currency metadata, status fields with undocumented magic values
None of these are unsolvable, but they require a structured approach to schema migration rather than a quick patch. My article on database design principles for non-technical founders covers the foundational concepts that prevent these problems from forming in the first place.
Sign 4: You Cannot Answer Basic Business Questions Without a Developer
This is a sign I consider as diagnostic as any technical metric. If your operations team cannot query the business data to answer reasonable questions — "how many orders did we fulfill per region last quarter" — without opening a support ticket for a developer, the data model has a problem.
Usually the problem is one of three things:
The data is spread across too many tables in a way that requires complex joins to answer any single question. This is a model that was normalized for write efficiency but was never designed for read accessibility.
The data lives in multiple systems that were never integrated, so the answer to any cross-system question requires manual extraction and reconciliation from each source.
The business logic is encoded in application code rather than in the data, meaning the database reflects transactions but not their business meaning. The field says "status = 3" but there is no documentation of what status 3 means in the current version of the application.
Fixing this often involves building a reporting layer — a read-optimized data model, a materialized view, or a lightweight data warehouse — separate from the transactional database. This is not a big infrastructure project at the SMB scale; it is often a set of well-designed views and a reporting tool like Metabase or Redash that can query them directly.
Sign 5: Database Size Growth That Outpaces Business Growth
If your database is growing 30 percent faster than your transaction volume, something is accumulating that should not be. Common culprits:
Unbounded logging tables that record every application event and are never pruned. These can reach hundreds of millions of rows with no business value attached to the older records.
Soft-delete patterns without archival — rows that are "deleted" in the application (a deleted_at timestamp is set) but never physically removed from the database. Over time these rows constitute the majority of the table and slow every query that does not filter for them correctly.
Binary data stored in the database (images, documents, PDF attachments) that belongs in object storage. A 5MB attachment stored in a database row is a routine object storage operation being misused as a data persistence strategy.
Redundant copies of data maintained for integration purposes — a pattern that emerged when two systems needed the same data but were never properly integrated. Instead of building the integration, someone copied the data and wrote a scheduled job to keep it in sync. Now both copies drift, both are being queried, and neither is authoritative.
This last pattern is one of the core problems that custom API integrations are designed to eliminate at the architectural level.
What to Do If You Recognize These Signs
The good news is that most database performance problems in growing SMBs are fixable without rebuilding from scratch. A structured assessment typically takes a week and produces a prioritized list of interventions — indexes, query rewrites, schema adjustments, archival policies — ranked by effort and impact.
The less good news is that waiting until the system is in crisis makes the interventions harder and the business risk during the remediation period higher. The best time to address database health is before the problem is visible to customers.
If any of these signs are familiar, let's talk. I can help you understand what you have, what it will cost you if left unaddressed, and what a realistic remediation path looks like.