Repository: vercel/next.js
Pull request: turbopack: move "compact database" tracing span to backend layer

### What?

Move the `"compact database"` tracing span from `turbo-persistence` (`db.rs`) to the backend layer (`turbo-tasks-backend/src/backend/mod.rs`), make it a root span, group it with the sibling `"persist"` span under a shared `"background snapshot"` root, and emit one span per compaction session rather than one per iteration.

### Why?

The `compact()` function is called from the background persistence job, which runs inside a `"persisting background job"` span (inherited via `.in_current_span()` at task spawn time). Previously the `"compact database"` span was a child of that ambient span, making it appear nested under `"persisting background job"` in traces — inconsistent with its sibling spans `"snapshot"` and `"persist"`, which are already top-level root spans.

Moving the span to the backend call site and giving it an explicit parent allows us to:

1. Detach it from the `"persisting background job"` ambient span.
2. Group it with the `"persist"` span under a shared `"background snapshot"` root span so all snapshot + compaction work appears together in trace viewers.
3. Keep `turbo-persistence` free of span-policy decisions (root vs. child), which belong at the call site where the ambient span context is known.

### How?

- Introduced a non-entered `background_span` (`tracing::info_span!(parent: None, "background snapshot")`) as a shared root for both the snapshot/persist work and the compaction loop.
- Passed `background_span.id()` as the `parent_span` argument to `snapshot_and_persist`, so the `"snapshot"` and `"persist"` spans become children of it.
- Inside the idle compaction loop, a child `"compact database"` span is entered and dropped synchronously around each `compact()` call (not across the `tokio::select!` await point).
- Moved the span creation outside the `MAX_IDLE_COMPACTION_PASSES` loop so there is one `"compact database"` span per compaction session rather than one per iteration.
- Removed the `tracing::info_span!("compact database")` span from `turbo-persistence/src/db.rs` entirely.
- Downgraded the `"sync new files"` span from `info` to `trace` level (noise reduction).

The non-entered `tracing::Span` is `Send`, so it can be safely held across the `select!` await without triggering the `future cannot be sent between threads safely` compile error that an `EnteredSpan` would cause.

Changed files:
filename: turbopack/crates/turbo-persistence/src/db.rs
patch: @@ -574,7 +574,7 @@ impl<S: ParallelScheduler, const FAMILIES: usize> TurboPersistence<S, FAMILIES>
 
         new_meta_files.sort_unstable_by_key(|(seq, _)| *seq);
 
-        let sync_span = tracing::info_span!("sync new files").entered();
+        let sync_span = tracing::trace_span!("sync new files").entered();
         let mut new_meta_files = self
             .parallel_scheduler
             .parallel_map_collect_owned::<_, _, Result<Vec<_>>>(new_meta_files, |(seq, file)| {
@@ -814,7 +814,6 @@ impl<S: ParallelScheduler, const FAMILIES: usize> TurboPersistence<S, FAMILIES>
         if self.read_only {
             bail!("Compaction is not allowed on a read only database");
         }
-        let _span = tracing::info_span!("compact database").entered();
         if self
             .active_write_operation
             .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)

filename: turbopack/crates/turbo-tasks-backend/src/backend/mod.rs
patch: @@ -2769,12 +2769,22 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
                         }
 
                         let this = self.clone();
-                        let snapshot = this.snapshot_and_persist(None, reason, turbo_tasks);
+                        // Create a root span shared by both the snapshot/persist
+                        // work and the subsequent compaction so they appear
+                        // grouped together in trace viewers.
+                        let background_span =
+                            tracing::info_span!(parent: None, "background snapshot");
+                        let snapshot =
+                            this.snapshot_and_persist(background_span.id(), reason, turbo_tasks);
                         if let Some((snapshot_start, new_data)) = snapshot {
                             last_snapshot = snapshot_start;
 
                             // Compact while idle (up to limit), regardless of
                             // whether the snapshot had new data.
+                            // `background_span` is not entered here because
+                            // `EnteredSpan` is `!Send` and would prevent the
+                            // future from being sent across threads when it
+                            // suspends at the `select!` await below.
                             const MAX_IDLE_COMPACTION_PASSES: usize = 10;
                             for _ in 0..MAX_IDLE_COMPACTION_PASSES {
                                 let idle_ended = tokio::select! {
@@ -2788,6 +2798,14 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
                                 if idle_ended {
                                     break;
                                 }
+                                // Enter the span only around the synchronous
+                                // compact() call so we never hold an
+                                // `EnteredSpan` across an await point.
+                                let _compact_span = tracing::info_span!(
+                                    parent: background_span.id(),
+                                    "compact database"
+                                )
+                                .entered();
                                 match self.backing_storage.compact() {
                                     Ok(true) => {}
                                     Ok(false) => break,