Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
342952c
Skip the scheduled oplog archive for ephemeral agents
kmatasfp Aug 31, 2026
2c3cb12
Archive quiet ephemeral oplogs from a periodic sweep
kmatasfp Sep 1, 2026
081eab0
Name the right transfer function in the sweep docs
kmatasfp Sep 1, 2026
3a1f9a4
Resolve the sweep target environment from the component
kmatasfp Sep 1, 2026
261a6c9
Check residency before probing, and drop tracking entries a pass did …
kmatasfp Sep 1, 2026
5c192c8
Rework the sweep tick into a scan phase and an archive phase
kmatasfp Sep 1, 2026
2f72f6c
Correct the scan cursor for archived keys instead of rewinding
kmatasfp Sep 1, 2026
65dcd4a
Scope the in-memory scan cursor to the namespace it scans
kmatasfp Sep 1, 2026
781f5b0
Cover scan-with-deletion across every indexed-storage backend
kmatasfp Sep 1, 2026
2a138cd
Bound scan pages, restamp a quiet agent, and grace the tracking table…
kmatasfp Sep 1, 2026
7647b5d
Say which namespace the scan cost applies to
kmatasfp Sep 1, 2026
5d6f658
Page the sweep with a resumable scan instead of a corrected offset
kmatasfp Sep 1, 2026
893086f
Read the last index without its payload and bound the multi-file walk
kmatasfp Sep 1, 2026
5d7ab24
Bound the multi-file walk, resolve each component once, and name the …
kmatasfp Sep 1, 2026
174b1bd
Cover the tick budgets, the archive step limit, and the labelled stor…
kmatasfp Sep 1, 2026
4e6d970
Give the stable-scan tests a namespace of their own and pin the file …
kmatasfp Sep 1, 2026
7977054
Bound the archiving sweep tests with a timeout
kmatasfp Sep 1, 2026
df24edc
Name the tick histogram for its unit and correct stale comments
kmatasfp Sep 1, 2026
12d4641
Treat a zero-shard assignment as unassigned
kmatasfp Sep 1, 2026
0317305
Bound the in-memory page instead of sorting the whole namespace
kmatasfp Sep 1, 2026
8e801fa
Check for shutdown before each component resolve
kmatasfp Sep 1, 2026
ab64f51
Name the sweep tick buckets like the others in the file
kmatasfp Sep 1, 2026
8b195f9
Say where cancellation is checked
kmatasfp Sep 1, 2026
e1c320d
Wrap the sweep metric help like the rest of the file
kmatasfp Sep 1, 2026
8bb6024
Correct what the interval sets and what an archive leaves behind
kmatasfp Sep 1, 2026
ce0ab60
Fix the swept agent modes in code instead of configuration
kmatasfp Sep 1, 2026
ceabfdf
Merge branch '1.5.x' into oplog-archive-schedule
kmatasfp Sep 1, 2026
6bd1b31
Match the test double to the new activate_worker signature
kmatasfp Sep 1, 2026
690b5c1
Read the sweep flag in the guard that suppresses the ephemeral regist…
kmatasfp Sep 1, 2026
260a02f
Bound an oplog sweep tick in time and back off when it is cut short
kmatasfp Sep 3, 2026
a6d80bb
Regenerate the service configs for the new sweep bounds
kmatasfp Sep 3, 2026
a25bad3
Floor the sweep tick deadline and correct what the loop claims
kmatasfp Sep 3, 2026
75fa1c8
Address the sweep review: quiet gate, deleted components, step bound,…
kmatasfp Sep 4, 2026
2929e0c
Merge 1.5.x
kmatasfp Sep 10, 2026
6530aa7
Keep a cut tick's archive candidates and stop allocating a spent budget
kmatasfp Sep 10, 2026
e965077
Check ownership and remember environments on the carried archive path
kmatasfp Sep 10, 2026
9eb4e7e
Remember a failed environment lookup and charge archive work one price
kmatasfp Sep 10, 2026
f217548
Re-read a carried agent's index and cover the carried path with tests
kmatasfp Sep 10, 2026
0ec6ea5
Cover the tick budget charge with a two-route stack
kmatasfp Sep 10, 2026
79b8198
Charge every walked key once and re-read the index on both archive paths
kmatasfp Sep 10, 2026
606029f
Keep the index re-read on the carried path only
kmatasfp Sep 10, 2026
3d201bb
Hold the quiet gate on the carried path and pin the staleness checks
kmatasfp Sep 10, 2026
818c422
Fail closed when a carried agent has no sighting left
kmatasfp Sep 10, 2026
7746e00
Carry the sighting with the candidate and confirm on one path
kmatasfp Sep 10, 2026
1562a18
Archive each agent in the task that probed it
kmatasfp Sep 10, 2026
ada8faf
Pin the archive step bound with a layer that never empties
kmatasfp Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions golem-debugging-service/config/debug-worker-executor.sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ GOLEM__OPLOG__INDEXED_STORAGE_RETRY__MIN_DELAY="100ms"
GOLEM__OPLOG__INDEXED_STORAGE_RETRY__MULTIPLIER=3.0
GOLEM__OPLOG__OPLOG_PROCESSOR_SNAPSHOTTING__TYPE="EveryNInvocation"
GOLEM__OPLOG__OPLOG_PROCESSOR_SNAPSHOTTING__CONFIG__COUNT=10
GOLEM__OPLOG__SWEEP__ENABLED=true
GOLEM__OPLOG__SWEEP__INTERVAL="1m"
GOLEM__OPLOG__SWEEP__MAX_ARCHIVES_PER_TICK=256
GOLEM__OPLOG__SWEEP__MAX_BACKOFF_INTERVALS=8
GOLEM__OPLOG__SWEEP__MAX_CONCURRENCY=4
GOLEM__OPLOG__SWEEP__MAX_SCANNED_PER_TICK=4096
GOLEM__OPLOG__SWEEP__MAX_TICK_DURATION="30s"
GOLEM__OPLOG__SWEEP__MAX_TRACKED_AGENTS=100000
GOLEM__OPLOG__SWEEP__PAGE_SIZE=128
GOLEM__PUBLIC_WORKER_API__CONNECT_TIMEOUT="10s"
GOLEM__PUBLIC_WORKER_API__HOST="localhost"
GOLEM__PUBLIC_WORKER_API__HTTP2_KEEP_ALIVE_INTERVAL="10s"
Expand Down Expand Up @@ -282,6 +291,15 @@ GOLEM__OPLOG__INDEXED_STORAGE_RETRY__MIN_DELAY="100ms"
GOLEM__OPLOG__INDEXED_STORAGE_RETRY__MULTIPLIER=3.0
GOLEM__OPLOG__OPLOG_PROCESSOR_SNAPSHOTTING__TYPE="EveryNInvocation"
GOLEM__OPLOG__OPLOG_PROCESSOR_SNAPSHOTTING__CONFIG__COUNT=10
GOLEM__OPLOG__SWEEP__ENABLED=true
GOLEM__OPLOG__SWEEP__INTERVAL="1m"
GOLEM__OPLOG__SWEEP__MAX_ARCHIVES_PER_TICK=256
GOLEM__OPLOG__SWEEP__MAX_BACKOFF_INTERVALS=8
GOLEM__OPLOG__SWEEP__MAX_CONCURRENCY=4
GOLEM__OPLOG__SWEEP__MAX_SCANNED_PER_TICK=4096
GOLEM__OPLOG__SWEEP__MAX_TICK_DURATION="30s"
GOLEM__OPLOG__SWEEP__MAX_TRACKED_AGENTS=100000
GOLEM__OPLOG__SWEEP__PAGE_SIZE=128
GOLEM__PUBLIC_WORKER_API__CONNECT_TIMEOUT="10s"
GOLEM__PUBLIC_WORKER_API__HOST="localhost"
GOLEM__PUBLIC_WORKER_API__HTTP2_KEEP_ALIVE_INTERVAL="10s"
Expand Down
22 changes: 22 additions & 0 deletions golem-debugging-service/config/debug-worker-executor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@ type = "EveryNInvocation"
[oplog.oplog_processor_snapshotting.config]
count = 10

[oplog.sweep]
enabled = true
interval = "1m"
max_archives_per_tick = 256
max_backoff_intervals = 8
max_concurrency = 4
max_scanned_per_tick = 4096
max_tick_duration = "30s"
max_tracked_agents = 100000
page_size = 128

[public_worker_api]
connect_timeout = "10s"
host = "localhost"
Expand Down Expand Up @@ -435,6 +446,17 @@ without_time = false
# [oplog.oplog_processor_snapshotting.config]
# count = 10
#
# [oplog.sweep]
# enabled = true
# interval = "1m"
# max_archives_per_tick = 256
# max_backoff_intervals = 8
# max_concurrency = 4
# max_scanned_per_tick = 4096
# max_tick_duration = "30s"
# max_tracked_agents = 100000
# page_size = 128
#
# [public_worker_api]
# connect_timeout = "10s"
# host = "localhost"
Expand Down
27 changes: 27 additions & 0 deletions golem-worker-executor/config/worker-executor.sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ GOLEM__OPLOG__INDEXED_STORAGE_RETRY__MIN_DELAY="100ms"
GOLEM__OPLOG__INDEXED_STORAGE_RETRY__MULTIPLIER=3.0
GOLEM__OPLOG__OPLOG_PROCESSOR_SNAPSHOTTING__TYPE="EveryNInvocation"
GOLEM__OPLOG__OPLOG_PROCESSOR_SNAPSHOTTING__CONFIG__COUNT=10
GOLEM__OPLOG__SWEEP__ENABLED=true
GOLEM__OPLOG__SWEEP__INTERVAL="1m"
GOLEM__OPLOG__SWEEP__MAX_ARCHIVES_PER_TICK=256
GOLEM__OPLOG__SWEEP__MAX_BACKOFF_INTERVALS=8
GOLEM__OPLOG__SWEEP__MAX_CONCURRENCY=4
GOLEM__OPLOG__SWEEP__MAX_SCANNED_PER_TICK=4096
GOLEM__OPLOG__SWEEP__MAX_TICK_DURATION="30s"
GOLEM__OPLOG__SWEEP__MAX_TRACKED_AGENTS=100000
GOLEM__OPLOG__SWEEP__PAGE_SIZE=128
GOLEM__PUBLIC_WORKER_API__CONNECT_TIMEOUT="10s"
GOLEM__PUBLIC_WORKER_API__HOST="localhost"
GOLEM__PUBLIC_WORKER_API__HTTP2_KEEP_ALIVE_INTERVAL="10s"
Expand Down Expand Up @@ -360,6 +369,15 @@ GOLEM__OPLOG__INDEXED_STORAGE_RETRY__MIN_DELAY="100ms"
GOLEM__OPLOG__INDEXED_STORAGE_RETRY__MULTIPLIER=3.0
GOLEM__OPLOG__OPLOG_PROCESSOR_SNAPSHOTTING__TYPE="EveryNInvocation"
GOLEM__OPLOG__OPLOG_PROCESSOR_SNAPSHOTTING__CONFIG__COUNT=10
GOLEM__OPLOG__SWEEP__ENABLED=true
GOLEM__OPLOG__SWEEP__INTERVAL="1m"
GOLEM__OPLOG__SWEEP__MAX_ARCHIVES_PER_TICK=256
GOLEM__OPLOG__SWEEP__MAX_BACKOFF_INTERVALS=8
GOLEM__OPLOG__SWEEP__MAX_CONCURRENCY=4
GOLEM__OPLOG__SWEEP__MAX_SCANNED_PER_TICK=4096
GOLEM__OPLOG__SWEEP__MAX_TICK_DURATION="30s"
GOLEM__OPLOG__SWEEP__MAX_TRACKED_AGENTS=100000
GOLEM__OPLOG__SWEEP__PAGE_SIZE=128
GOLEM__PUBLIC_WORKER_API__CONNECT_TIMEOUT="10s"
GOLEM__PUBLIC_WORKER_API__HOST="localhost"
GOLEM__PUBLIC_WORKER_API__HTTP2_KEEP_ALIVE_INTERVAL="10s"
Expand Down Expand Up @@ -578,6 +596,15 @@ GOLEM__OPLOG__INDEXED_STORAGE_RETRY__MIN_DELAY="100ms"
GOLEM__OPLOG__INDEXED_STORAGE_RETRY__MULTIPLIER=3.0
GOLEM__OPLOG__OPLOG_PROCESSOR_SNAPSHOTTING__TYPE="EveryNInvocation"
GOLEM__OPLOG__OPLOG_PROCESSOR_SNAPSHOTTING__CONFIG__COUNT=10
GOLEM__OPLOG__SWEEP__ENABLED=true
GOLEM__OPLOG__SWEEP__INTERVAL="1m"
GOLEM__OPLOG__SWEEP__MAX_ARCHIVES_PER_TICK=256
GOLEM__OPLOG__SWEEP__MAX_BACKOFF_INTERVALS=8
GOLEM__OPLOG__SWEEP__MAX_CONCURRENCY=4
GOLEM__OPLOG__SWEEP__MAX_SCANNED_PER_TICK=4096
GOLEM__OPLOG__SWEEP__MAX_TICK_DURATION="30s"
GOLEM__OPLOG__SWEEP__MAX_TRACKED_AGENTS=100000
GOLEM__OPLOG__SWEEP__PAGE_SIZE=128
GOLEM__PUBLIC_WORKER_API__CONNECT_TIMEOUT="10s"
GOLEM__PUBLIC_WORKER_API__HOST="localhost"
GOLEM__PUBLIC_WORKER_API__HTTP2_KEEP_ALIVE_INTERVAL="10s"
Expand Down
33 changes: 33 additions & 0 deletions golem-worker-executor/config/worker-executor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,17 @@ type = "EveryNInvocation"
[oplog.oplog_processor_snapshotting.config]
count = 10

[oplog.sweep]
enabled = true
interval = "1m"
max_archives_per_tick = 256
max_backoff_intervals = 8
max_concurrency = 4
max_scanned_per_tick = 4096
max_tick_duration = "30s"
max_tracked_agents = 100000
page_size = 128

[public_worker_api]
connect_timeout = "10s"
host = "localhost"
Expand Down Expand Up @@ -550,6 +561,17 @@ without_time = false
# [oplog.oplog_processor_snapshotting.config]
# count = 10
#
# [oplog.sweep]
# enabled = true
# interval = "1m"
# max_archives_per_tick = 256
# max_backoff_intervals = 8
# max_concurrency = 4
# max_scanned_per_tick = 4096
# max_tick_duration = "30s"
# max_tracked_agents = 100000
# page_size = 128
#
# [public_worker_api]
# connect_timeout = "10s"
# host = "localhost"
Expand Down Expand Up @@ -888,6 +910,17 @@ without_time = false
# [oplog.oplog_processor_snapshotting.config]
# count = 10
#
# [oplog.sweep]
# enabled = true
# interval = "1m"
# max_archives_per_tick = 256
# max_backoff_intervals = 8
# max_concurrency = 4
# max_scanned_per_tick = 4096
# max_tick_duration = "30s"
# max_tracked_agents = 100000
# page_size = 128
#
# [public_worker_api]
# connect_timeout = "10s"
# host = "localhost"
Expand Down
23 changes: 23 additions & 0 deletions golem-worker-executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ use crate::services::oplog::{
BlobOplogArchiveService, CompressedOplogArchiveService, MultiLayerOplogService,
OplogArchiveService, OplogService, PrimaryOplogService,
};
use crate::services::oplog_sweep::OplogSweeper;
use crate::services::promise::{DefaultPromiseService, DefaultPromiseWorkerAccess, PromiseService};
use crate::services::quota::QuotaService;
use crate::services::registry_event_subscriber::WorkerExecutorRegistryInvalidationHandler;
Expand Down Expand Up @@ -771,6 +772,9 @@ pub async fn create_worker_executor_impl<
Arc::new(BlobOplogArchiveService::new(blob_storage.clone(), idx));
oplog_archives.push(svc);
}
// The sweeper needs the same layer stack, but it is built here while its other dependency
// (the worker activator) only exists further down, so keep a handle.
let sweep_archives = oplog_archives.clone();
let oplog_archives = NEVec::try_from_vec(oplog_archives);

let base_oplog_service: Arc<dyn OplogService> = match oplog_archives {
Expand Down Expand Up @@ -924,6 +928,25 @@ pub async fn create_worker_executor_impl<
shutdown_token.clone(),
);

// Same work as `ScheduledAction::ArchiveOplog`, driven by a paginated scan of the oplog layers
// instead of a row written on the oplog commit path. Spawned into the executor's join set so a
// shutdown waits for the tick in flight rather than cutting an archive step in half.
let oplog_sweeper = OplogSweeper::over_layers(
golem_config.oplog.sweep.clone(),
indexed_storage.clone(),
&sweep_archives,
shard_service.clone(),
component_service.clone(),
Arc::new(lazy_worker_activator.clone() as Arc<dyn WorkerActivator<Ctx>>),
);
join_set.spawn({
let shutdown_token = shutdown_token.clone();
async move {
oplog_sweeper.run(shutdown_token).await;
Ok(())
}
});

let additional_deps = bootstrap.create_additional_deps(registry_service.clone());

let direct_invocation_auth_service =
Expand Down
45 changes: 45 additions & 0 deletions golem-worker-executor/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ const SCHEDULER_TICK_DURATION_BUCKETS: &[f64; 24] = &[
2.5, 3.0, 5.0, 10.0, 15.0, 20.0, 30.0, 60.0,
];

/// Tick-duration buckets for the oplog sweep. They reach well past the shared set's minute: a
/// tick is a budgeted walk of a storage namespace, so slow storage is exactly when its duration
/// is worth reading, and the shared buckets saturate there.
const OPLOG_SWEEP_TICK_BUCKETS: &[f64; 9] =
&[0.01, 0.1, 1.0, 5.0, 15.0, 60.0, 300.0, 900.0, 3600.0];

/// Buckets for the size of a single `memory.grow` allocation. Deliberately
/// fine-grained in the 1-32 MiB band where typical guest grows cluster, so
/// that p90/p99 quantiles are not pinned to a coarse 4-16 MiB bucket edge.
Expand Down Expand Up @@ -1009,6 +1015,26 @@ pub mod oplog {
&["account_id", "environment_id"]
)
.unwrap();
static ref OPLOG_SWEEP_OUTCOME_TOTAL: CounterVec = register_counter_vec!(
"oplog_sweep_outcome_total",
"Keys the oplog sweep examined, by what it decided about each. One outcome per key, \
so the outcomes sum to what a tick decided",
&["route", "outcome"]
)
.unwrap();
static ref OPLOG_SWEEP_TICK_TIME: HistogramVec = register_histogram_vec!(
"oplog_sweep_tick_seconds",
"Time taken by one oplog sweep tick",
&["route"],
crate::metrics::OPLOG_SWEEP_TICK_BUCKETS.to_vec()
)
.unwrap();
static ref OPLOG_SWEEP_TRUNCATED_TOTAL: CounterVec = register_counter_vec!(
"oplog_sweep_truncated_total",
"Oplog sweep ticks that hit a budget or their deadline before reaching the end of the namespace",
&["route"]
)
.unwrap();
static ref OPLOG_STORAGE_RETRY_TOTAL: CounterVec = register_counter_vec!(
"oplog_storage_retry_total",
"Number of oplog storage operation retries due to transient errors",
Expand All @@ -1033,6 +1059,25 @@ pub mod oplog {
.inc();
}

pub fn record_oplog_sweep_outcome(route: &str, outcome: &'static str, count: u64) {
if count > 0 {
OPLOG_SWEEP_OUTCOME_TOTAL
.with_label_values(&[route, outcome])
.inc_by(count as f64);
}
}

pub fn record_oplog_sweep_tick(route: &str, duration: std::time::Duration, truncated: bool) {
OPLOG_SWEEP_TICK_TIME
.with_label_values(&[route])
.observe(duration.as_secs_f64());
if truncated {
OPLOG_SWEEP_TRUNCATED_TOTAL
.with_label_values(&[route])
.inc();
}
}

pub fn record_scheduled_archive(duration: std::time::Duration, has_more: bool) {
SCHEDULED_ARCHIVE_TIME
.with_label_values(if has_more {
Expand Down
Loading
Loading