Can Small Services Survive? Limits of Polling and Criteria for Introducing Queues
When processing tasks through database polling in small services, throughput drops sharply and latency increases. This article analyzes the limits that polling-based processing must endure and presents concrete criteria for when to introduce a queue system. It covers how to recognize system saturation rather than just 'traffic growth', and the operational checkpoints to keep in mind after adoption.
Published by DevInsight.
Database polling processes tasks, but at some point you hit a situation where CPU has headroom while response times keep getting slower. Reduce polling interval from 1 second to 100ms and the database connection count grows exponentially until the connection pool breaks. This article analyzes the limits that polling-based processing must endure and presents concrete criteria for when to introduce a message queue system like Redis.
The textbook answer for when polling works
In a small service, polling can be the answer. Periodically fetching and processing tasks with WHERE status = 'pending' LIMIT 100 queries works without any complex infrastructure. You don't need to provision a separate system like Redis or RabbitMQ, and deployment is just a few lines of code added to the existing application.
The problem is how far this approach can go. With a 1-second polling interval, maximum latency is 1 second. Cutting the interval to 100ms brings average latency down to 50ms, but the database now receives 10 queries per second. If 10 workers poll simultaneously, that's 100 additional polling queries per second hitting the database.
At this level, even a small service can sustain the database. PostgreSQL's connection limit is typically 100~200, but with proper connection pool tuning it can handle up to 500. The trouble starts when you shorten the polling interval further. What about 10ms polling? The database receives 100 queries per second. With 10 workers, that's 1,000. You've already hit the connection limit ceiling.
Signals that the system is entering saturation
The symptoms are simple. Response times keep getting slower while CPU utilization remains low. Memory is still sufficient. The database has reached its connection limit, yet the database's own load metrics don't look particularly heavy. A common mistake in this situation is to shorten the polling interval even more.
Shortening the polling interval causes counterproductive effects. Database lock contention worsens and connection pool exhaustion begins. If polling queries start failing, workers have to wait until the next polling cycle, creating a vicious cycle that further increases latency.
The most reliable way to detect actual saturation is to measure connection pool wait time. If requesting a database connection takes 10ms, that's normal. But if it takes 100ms or 500ms, that's the saturation signal of the polling system.
The list of responsibilities that queue adoption brings
Introducing a message queue like Redis brings new operational burdens. What was once a single polling query now has additional concerns. The number of variables to consider grows rapidly: queue clustering, failover, memory management, message expiration policies, and more.
Redis operations are trickier than they seem. With a single instance there's little room for problems, but if it fails the entire system goes down. Even with replicas, replication lag occurs and consistency issues arise when slaves separate from the master. Even Redis's AOF (Append Only File) setting alone can cause a 2~3x performance difference.
However, the biggest advantage of a queue system is the fundamental resolution of polling latency. Workers don't need to poll; they can start processing immediately as soon as a new task arrives. Average processing time drops to 12ms and system throughput improves by 510x.
Infrastructure determines cost
Polling system costs are simple: just the application server and database server costs. But introducing a queue means additional costs for Redis instances, monitoring agents, backup systems, and more.
Looking at AWS prices: if the database can be handled on a t3.micro ($0.023/hour), Redis can be handled on cache.t3.micro ($0.024/hour). But in real operations, you need at least two Redis nodes for HA, plus additional Lambda functions and CloudWatch costs for backup and monitoring.
A bigger issue is operational cost. In a polling system, managing the database was enough. Introducing Redis requires a Redis specialist and knowledge of backup strategies, failure recovery procedures, and performance tuning. In a small team, this cost becomes particularly heavy.
Both late adoption and premature adoption can fail
Common mistakes come in two forms. One is introducing a queue when the polling system can still handle it, and the other is rushing to introduce a queue once the system has already saturated.
Introducing a queue too early increases operational burden and wastes budget. Development becomes more complex and deployment pipelines need to be refined. On the other hand, rushing adoption leads to a chaotic state where existing polling code and the queue system coexist. During migration, you bear the burden of running both systems simultaneously.
Criteria for adoption timing
The moment to consider introducing a queue in a polling system is clear.
First criterion: connection pool wait time exceeds 50ms. This means polling queries are overloading the database.
Second criterion: polling query ratio exceeds 30% of all database queries. This indicates database resources are being excessively consumed by polling.
Third criterion: average latency exceeds 100ms and concurrent throughput is at saturation. This means you've reached the lower bound of polling latency.
If any one of these three criteria is met, you should consider queue adoption. Conversely, if none are satisfied, maintaining the polling system is the wiser choice.
Migration strategy
Once you've decided to adopt a queue, you don't need to migrate everything at once. A gradual approach is safer.
Start by moving low-load tasks to the queue first. For example, moving auxiliary tasks like notification delivery lets you gain operational experience with the queue system. Gradually expand to major business logic over time.
During migration, you must have a fallback mechanism. If the queue system has issues, you need the ability to roll back to the polling system immediately. This can be implemented with feature flags or routing logic.
Operational checkpoints are straightforward: monitor Redis memory usage, queued message count, worker processing speed, and failure rate. Pay special attention to messages that wait too long in the queue, as this indicates the worker's processing limit has been reached and requires immediate response.
In the end, it comes down to trade-offs
Polling and queues each have their own limitations. Polling is simple and intuitive, but has clear scaling limits. Queues offer high throughput but increase operational complexity.
For developers of small services, the most important thing is judgment at the right time. If polling can still handle it, it's better not to add unnecessary complexity. But once you recognize that system metrics are hitting their limits, don't hesitate and move to a queue.
The key is securing evidence that polling hasn't yet gone 'sufficiently wrong'. By comprehensively analyzing multiple metrics—database connection count, polling query ratio, average latency, worker CPU usage—you can get closer to the right answer.
Comments
Loading comments.
Good Follow-up Reads
Posts connected to the topic you just read.
1년 동안 DB 테이블로 큐 대신 쓰면서 내가 놓친 것들
DB 폴링 기반 작업 처리로 버틸 수 있는 한계와 Redis·메시지 큐 도입을 결정해야 하는 실전 신호, 그리고 큐를 도입할 때 따라오는 운영 부담을 사례 중심으로 정리한다. DB 테이블 하나로 작업 큐를 대신한 지 1년이 지났다. 처음엔 "언젠가 Redis나 RabbitMQ를 도입해야지"라고 생각했지만, 그 언젠가는 오지 않았다.
Don't Trust Webhooks, Verify with Signatures
Webhook receivers must be designed with distrust of external requests. Since requests can be unstable — with retries, processing delays, and even forgery attempts — signature verification, idempotent processing, retry ID tracking, and queuing for delays are all essential for stable ingestion. Particularly, retries arriving without idempotency keys lead to duplicate processing; when delays grow too long, immediately switch to queue locking.
당신의 RLS 정책은 조용히 거짓말을 하고 있다
Supabase에서 RLS를 활성화한 순간 쿼리는 에러 없이 빈 배열을 반환하기 시작한다. service_role 키와 anon 키를 혼동할 때 벌어지는 일, SQL 에디터 테스트가 주는 환상, auth.uid()가 null을 뱉는 이유 등 실제 운영에서 마주치는 RLS 실수 패턴을 진단 쿼리와 함께 파헤친다.
Previous post
Don't Trust Webhooks, Verify with Signatures
Next post
A Week Facing 2,731 Type Errors: The Reality Between Flipping strict On and Off
DevInsight Digest
Keep every new article in one calm feed.
Follow the full publication feed without promotional alerts.