> For the complete documentation index, see [llms.txt](https://docs.opendatadiscovery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opendatadiscovery.org/developer-guides/architecture-decision-log/adr-0045-housekeeping-partition-separation.md).

# ADR-0045: Housekeeping is a separate subsystem from partition management

## Status

**Accepted.** Reconstructed from the codebase on 2026-05-30; the decision is live in the source today.

## Context

Two kinds of background maintenance touch the same high-volume tables: **structural** lifecycle (creating range partitions ahead of need, per ADR-0028) and **content** cleanup (deleting aged rows by TTL, and dropping partitions once they are empty). These have different criticality and cadence — a missing partition is an insert failure that must be created reliably ahead of time; deleting old rows is best-effort cleanup that can wait. The platform had to decide whether to fold them into one mechanism or keep them apart.

## Decision

**The two concerns are separate subsystems, and the one task that bridges them lives on the cleanup side but delegates to the structural side.** The `housekeeping` package runs content cleanup — a manager iterating `List<HousekeepingJob>` on a frequent fixed-rate schedule (every 15 minutes, ShedLock-elected under the lock name `housekeepingJob`). The `partition` package runs structural creation — the nightly forward-coverage job of ADR-0028 (ShedLock-elected under the distinct lock name `partitionCreationJob`). They are different packages, different schedules, and different ShedLock locks; both use ShedLock for cluster election (neither uses a Postgres advisory lock for scheduling).

Their single intersection is **empty-partition dropping**, and it is modelled to name both concerns explicitly at the type level: `EmptyPartitionsHousekeepingJob` is an abstract class that *lives in* the housekeeping package and *implements* the housekeeping `HousekeepingJob` interface, but *consumes* the `PartitionService` and does its work by fetching the empty past partitions (`partitionService.getEmptyPastPartitions(...)`) and dropping each one (`partitionService.dropPartition(...)`). Its concrete subclasses (`ActivityEmptyPartitionsHousekeepingJob`, and the message-table equivalent) register as housekeeping jobs. So dropping empty partitions runs on the housekeeping cadence and through the housekeeping manager, while the actual partition operation stays owned by the partition service — the coupling is one explicit class, not a blurring of the two subsystems.

## Consequences

* Partition **creation** (deployment-critical) and partition **empty-drop** (best-effort) run on independent schedules and locks, so the frequent housekeeping cycle never interferes with, or depends on, the once-nightly creation cycle.
* The intersection is discoverable: a contributor reading `EmptyPartitionsHousekeepingJob` sees both the housekeeping interface and the partition service in one type signature, rather than partition logic hidden inside a housekeeping flow or vice-versa.
* A contributor who wants to "consolidate all scheduled DB maintenance into one job" is working against this separation — the split (frequent content cleanup vs nightly structural creation) is the deliberate shape, and the empty-drop job is the sanctioned way to touch partitions from the cleanup side.
* Adding another cleanup task is an add-a-`HousekeepingJob`-`@Component` change; adding another partitioned table is an add-a-`PartitionManager` change (ADR-0028) — the two extension seams stay distinct.

## Evidence

* `odd-platform-api/.../housekeeping/job/EmptyPartitionsHousekeepingJob.java:13-14` — `abstract class EmptyPartitionsHousekeepingJob implements HousekeepingJob` with `private final PartitionService partitionService`; `:17` — `doHousekeeping(connection)` fetches `partitionService.getEmptyPastPartitions(connection, getTargetTable(), exclusions())` (`:21-22`) and drops each via `partitionService.dropPartition(connection, partition)` (`:26`) — the explicit cross-package coupling.
* `odd-platform-api/.../housekeeping/job/ActivityEmptyPartitionsHousekeepingJob.java:8-9` — `@Component public class … extends EmptyPartitionsHousekeepingJob` (registers as a housekeeping job).
* `odd-platform-api/.../housekeeping/HousekeepingJobManager.java:17-26` — `@Component @ConditionalOnProperty("housekeeping.enabled", havingValue="true")`, `@Scheduled(fixedRate = 15, timeUnit = MINUTES)` + `@SchedulerLock(name = "housekeepingJob", lockAtLeastFor = "14m", lockAtMostFor = "14m")`, iterating `List<HousekeepingJob>` — the housekeeping subsystem's manager and cadence.
* `odd-platform-api/.../partition/PostgreSQLPartitionCreationJob.java:40-41` — the partition subsystem's distinct nightly `@Scheduled` cron + `@SchedulerLock(name = "partitionCreationJob", …)` (ADR-0028).
* `odd-platform-api/.../housekeeping/job/HousekeepingJob.java:5-6` — the `void doHousekeeping(Connection)` interface the cleanup jobs share (alongside the TTL row-cleanup jobs such as `AlertHousekeepingJob`).

## See also

* [ADR-0028 — High-volume tables are range-partitioned by a scheduled forward-coverage job](/developer-guides/architecture-decision-log/adr-0028-range-partition-lifecycle.md) — the partition *creation* subsystem this one is kept separate from.
* [ADR-0046 — Housekeeping ships enabled by default](/developer-guides/architecture-decision-log/adr-0046-housekeeping-opt-out-by-default.md) — the on/off posture of the housekeeping subsystem described here.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.opendatadiscovery.org/developer-guides/architecture-decision-log/adr-0045-housekeeping-partition-separation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
