If your SEO system generates pages from structured data, the queue is the control point that keeps the process from turning into a pile of half-finished records. It decides what gets generated next, what is eligible to run, what should wait, and what should be retried or abandoned.
A content generation queue is not just a list of jobs. It is a state machine with rules around eligibility, locking, ordering, and recovery. If you design it like a simple task table, you will eventually run into duplicate work, stuck jobs, unclear priorities, and pages that look “done” in one service but never moved forward in another.
Start with the queue’s job
The queue should answer four questions for the system:
- What page should be generated next?
- Is it safe to generate now?
- Who or what owns the job right now?
- What happens if the job fails or stalls?
That means the queue needs to store both content intent and execution state. The topic selection system decides what should exist. The queue decides what gets processed now.
If you are already thinking about the broader publishing path, this queue is one component in an automated SEO publishing pipeline and usually sits downstream from opportunity selection, eligibility checks, and content modeling.
A practical schema
You do not need a giant schema, but you do need enough fields to support ordering, deduplication, and observability.
A typical job record might include:
idcontent_typeorpage_typeentity_idor source record referencestatuspriorityreservation_tokenreserved_byreserved_atattempt_countmax_attemptsavailable_atcreated_atupdated_atstarted_atfinished_atfailed_atlast_errorpayloadmetadataparent_job_idorrelationship_id
The exact shape depends on your architecture, but the important distinction is between identity, state, scheduling, and diagnostics.
- Identity tells you what the job is for.
- State tells you where it is in the workflow.
- Scheduling tells you when it can run.
- Diagnostics tell you what happened when it did run.
If your content model is still being designed, it is worth aligning the queue with the fields and relationships in your CMS or internal store. That is usually easier if the page structure is already explicit, as described in how to model SEO content in a CMS.
Statuses should be explicit, not implied
A queue usually breaks when teams rely on timestamps alone to infer state. A record with started_at and no finished_at might mean the job is running, failed, stuck, or never actually acquired.
Use a finite set of statuses, such as:
pendingeligiblereservedgeneratingvalidatingready_for_reviewapprovedpublishedfaileddead_letteredcanceled
You may not need every one of those states, but each status should have a clear transition rule. For example, a job should only move from pending to eligible once all inputs are present and the page is allowed to exist. It should move from eligible to reserved only when a worker has acquired the job atomically.
The most common mistake is to collapse “reserved,” “running,” and “in progress” into one vague state. That makes recovery difficult. If a worker crashes after reserving a job but before starting generation, you need to know whether the lock expired or whether the job was actually processed.
Reservations are more important than locks
A reservation is a time-bounded claim on a job. A lock can be part of the implementation, but the business concept is the reservation.
A good reservation system should store:
- who reserved the job
- when the reservation started
- when it expires
- whether it was renewed
- whether the job is still healthy
The queue should support atomic acquisition, usually with a transaction or compare-and-swap style update. The worker asks for the next eligible job, and the system changes it from eligible to reserved in one operation.
That matters because SEO content generation systems often have more than one worker: one may be generating, another validating, another enriching metadata, and a separate service may be monitoring for stalled jobs. Without explicit reservations, two workers can process the same page and produce conflicting artifacts.
For larger systems, this is closely related to how you handle internal workflows for review and QA. If human approval exists anywhere in the pipeline, the queue should preserve ownership and state transitions cleanly. The mechanics are similar to the review flows discussed in human review workflows for AI-generated SEO content.
Priorities should reflect business rules, not urgency theater
Priority works best when it encodes a few meaningful distinctions, not a dozen hand-tuned exceptions.
Useful priority inputs often include:
- business value of the page type
- freshness requirements
- contractual or launch deadlines
- query demand or opportunity score
- dependency order
- operational risk
A page with high revenue potential may be more valuable than a generic supporting page, but it may also depend on upstream data that is not ready. In that case, “high priority” should not override eligibility. A queue that processes the wrong job first is still wrong, even if it is technically fast.
Keep priority separate from eligibility. A page can be high priority and still be blocked. The queue should be able to say, “this is important, but not runnable yet.”
If you need to support multiple content families, add a second ordering dimension such as lane or queue_group. For example, you may want to process new pages, refreshes, and corrective re-generations in different queues so one class of work does not starve the others.
Retries need backoff and a failure boundary
Retry logic is where a lot of content systems quietly become unstable. If a generator fails because of a missing field, immediate retries waste cycles. If it fails because of a transient API error, you do want another attempt.
The queue should distinguish between retryable and non-retryable failures.
A practical approach:
- retry transient infrastructure failures
- fail fast on validation errors or missing required inputs
- use exponential backoff or scheduled retry windows
- cap the total number of attempts
- move exhausted jobs to a dead-letter state with the error preserved
Store enough diagnostic data to make failure modes visible:
- error class
- validation rule that failed
- upstream dependency missing
- response snippet or checksum where safe
- attempt history
If retries are blind, you get queue churn instead of throughput. If they are too conservative, you lose recoverability. The right balance depends on whether the job is expensive to generate, whether the inputs are deterministic, and whether a failure indicates a real data problem or a temporary system issue.
Timestamps should explain the lifecycle
Timestamps are not decorative. They let you reconstruct what happened and detect stuck jobs.
At minimum, track:
created_atavailable_atreserved_atstarted_atfinished_atfailed_atupdated_at
Optional but useful fields include:
last_heartbeat_atnext_retry_atpublished_atreviewed_at
These timestamps let you answer questions like:
- How long do jobs sit before being picked up?
- Which page types are slow to generate?
- Are validation failures increasing?
- Are some workers reserving jobs and then disappearing?
- Is review the bottleneck rather than generation?
You should also be careful not to overload one timestamp with multiple meanings. updated_at is not a substitute for lifecycle timestamps. It tells you the row changed; it does not tell you what stage changed.
Article relationships belong in the queue too
Content generation is rarely isolated. A generated page may belong to a cluster, depend on a parent page, inherit a template, or trigger sibling pages. The queue should preserve those relationships so the system can reason about order and completeness.
Common relationship patterns include:
- parent-child pages in a hierarchy
- sibling pages that should publish together
- dependency chains where one page requires another to exist first
- refresh jobs linked to original publish jobs
- correction jobs tied to a failed validation or human edit
That relationship data is useful for scheduling and for later debugging. If a page is generated but never linked from its parent, you may be looking at a queue problem, not an indexing problem. If you need a way to detect those failures after publication, the queue should align with the same source-of-truth records used in how to find orphan pages on large websites and in your internal linking logic.
This is also where relationship-aware publishing systems become much easier to maintain. When content objects know their parents, siblings, and downstream targets, the queue can make better decisions about ordering and retry scope.
What the worker should do
A worker should not “just generate content.” It should perform a small, predictable sequence:
- Claim an eligible job atomically.
- Mark the reservation with a token and expiry.
- Load the job payload and dependencies.
- Generate the artifact.
- Validate the output.
- Persist the result.
- Transition the job to the next state.
- Record timestamps and errors.
If validation fails, the worker should move the job into a failure state that preserves the reason and determines whether the job can be retried. If generation succeeds but a downstream publish step fails, the job may need to stay reserved or move to a separate publish queue. That separation keeps generation failures from being confused with deployment failures.
For systems that connect generation directly to publishing, the queue is only one part of the pipeline. The rest of the lifecycle is covered more fully in SEO publishing workflows: from draft to indexed page.
Design the queue for observability, not just throughput
A queue that processes jobs quickly but hides failure patterns is not a good SEO system. You want to know which page types accumulate backlogs, which errors repeat, and which states last too long.
Useful metrics include:
- queue depth by status
- age of oldest eligible job
- reservation expiry count
- retry count by error class
- median time in each state
- success rate by page type
- time from generation to publish
That data helps you detect whether the bottleneck is eligibility logic, worker capacity, validation, or dependency ordering. It also makes it easier to decide whether a queue needs sharding by page type, tenant, locale, or workflow stage.
The goal is not to create a sophisticated queue for its own sake. It is to make generated content predictable enough that your publishing system can scale without becoming opaque.
A good content generation queue is boring in the right way: it knows what exists, what can run, who owns it, and how to recover when things go wrong. That is the difference between automation that compounds and automation that just produces more work for the next team in line.