# Bean & Bark: bulk work past the preview cap

In [change data without losing it](https://www.kanject.com/docs/dynostudio-bean-bark-safe-writes/) the preview saved you from a wrong `WHERE`, and the scoped cleanup touched **214** rows. A year later the loyalty-programme import has multiplied the table, and the same — *correct* — statement now matches tens of thousands. The preview that protected you refuses to even try. That refusal is not a dead end; it's a fork: **stream it as a background job**, or take the question to the **fleet**.

**You'll learn**

- Understand the **interactive preview cap** — why a bulk write refuses instead of silently previewing 40,000 rows
- Run a set-based write as a **background job**: no preview, the same per-item `WHERE` re-check, tracked from the footer
- Know when a **`BULK SELECT`** fleet read (Professional) beats scanning through the app's own connection

> **9:15am — the annual encore:** **Nadia:** *"board meeting again. can you retire this year's dormant subscriptions? heads up — after the loyalty import there are a LOT more of them."*

## The preview has a ceiling — on purpose

You already know the ritual: run the scoped statement, read the matched count, run again to apply. But the preview drains every matched row into the grid so you can *look* at the blast radius, and a human can't meaningfully review 40,000 rows — so the interactive path caps the preview (**1,000 items** by default; Settings → Querying → *Max preview items*). Past the cap it refuses outright rather than showing you a set too big to check.

```sql
-- same cleanup as last year. the table is not the same table.
UPDATE "BeanAndBark"
SET Status = 'retired'
WHERE begins_with(sk, 'SUB#') AND Status = 'dormant'
```

**Try it · the cleanup at scale**

_Run — the preview refuses_

```sql
UPDATE "BeanAndBark"
SET Status = 'retired'
WHERE begins_with(sk, 'SUB#') AND Status = 'dormant'
```

_Executes as:_ Bulk UPDATE · preview · BeanAndBark — The WHERE is right this time — the SET is scoped to subscriptions. The size is the problem, not the predicate. The refusal toast carries the way out: a "Run as job" button, right where the refusal lands.

_Result:_ Bulk write won't run — Matched more than 1,000 items — interactive bulk previews are capped for safety. Narrow the WHERE, raise Settings → Querying → Max preview items, or run as a background job.  [Run as job]

_Run as job — streamed_

```sql
UPDATE "BeanAndBark"
SET Status = 'retired'
WHERE begins_with(sk, 'SUB#') AND Status = 'dormant'
```

_Executes as:_ Background job · streaming bulk UPDATE · BeanAndBark — No preview at this scale — instead, every single write re-proves the WHERE at write time. A row that changed since it was read is skipped, never blindly overwritten. The footer chip tracks the live tally; the Background jobs panel shows progress, throughput, and a Cancel that stops cleanly between items.

_Result:_ Job started on "BeanAndBark" — running in the background; track it in the footer. … Bulk write on "BeanAndBark" done — 38,214 applied, 12 skipped.

_Fleet read — BULK SELECT (Pro)_

```sql
BULK SELECT pk, sk, Status
FROM "BeanAndBark"
WHERE Status = 'dormant'
INTO 's3://beanandbark-exports/dormant-2026/'
```

_Executes as:_ Fleet read · Glue job · BeanAndBark — Nothing streams through your laptop: the scan runs on a worker fleet in your own AWS account and writes straight to S3. The run confirm shows the cost envelope — table size, worker type, budget — before anything is billed. Requires the stage's bulk plane (WORKSPACE → Bulk Operations) and a Professional plan.

_Result:_ Fleet job submitted — the full dormant extract lands in s3://beanandbark-exports/dormant-2026/ when the run completes.

_Run it live in DynoStudio:_ https://www.kanject.com/dynostudio/

Three tools, one decision rule. Under the cap, the **preview-first** ritual stays the default — you look, then you write. Past the cap and still a *write*, the **background job** keeps the safety story without the preview: every item is written through an atomic `WHERE` re-check, so the guarantee moves from "you saw the set" to "each row still matched at the moment it was written". And when the job is really a *question* — export every dormant record, count them across the whole table — a **fleet read** does the scanning on workers in your own account instead of through the connection your grid is using.

> **The seatbelt still comes first:** A background job is still a mass write with no preview. The habit from chapter three doesn't change at scale — it matters *more*: `BACKUP TABLE "BeanAndBark" AS 'beanandbark-pre-cleanup-2026'` before you press Run as job. One line, and 38,000 writes stop being scary.

**Recap**

- The interactive preview is **capped** (1,000 items by default) because a set too big to review isn't a preview — past it, the studio refuses and offers the job path instead.
- A **background job** streams the write with no cap and no preview, but keeps the per-item guard: the `WHERE` is re-checked atomically on every single write, and the footer chip + jobs panel track the run.
- A **`BULK SELECT`** (Professional) runs the scan on a Glue worker fleet in your own account and delivers results to S3 — for table-scale questions that shouldn't ride your interactive connection.
- Fleet `BULK UPDATE` / `BULK DELETE` aren't available yet — today, set-based writes run interactively or as a streaming background job.

**Scale check**

**1. Your bulk UPDATE matches 40,000 rows and the preview refuses. What does "Run as job" trade away, and what does it keep?**

- It gives up the human review of the matched set, but keeps a per-item WHERE re-check on every write ✓ — the job path writes each item through an atomic ConditionExpression — a row that no longer matches is skipped, not overwritten. The safety moves from preview-time to write-time.
- It keeps the preview but paginates it — there is no preview on the job path at all — that's the point of the cap. The protection is the per-write re-check instead.
- Nothing — it's the same run without the dialog — it genuinely differs: no preview, no armed second press. You traded review for scale, which is why the backup habit matters more here.

**2. Nadia wants a CSV of *every* dormant record for the auditors — the table is millions of items. Best tool?**

- BULK SELECT … INTO 's3://…' — the fleet scans in your account and writes the extract to S3 ✓ — a whole-table extract is a read at fleet scale. Workers do the scanning; your session, laptop, and grid stay out of the data path.
- The background job — the background job is the *write* tool — it applies UPDATE/DELETE. An extract is a read; the fleet path exists exactly for this.
- Scroll the grid and export page by page — the grid is for looking at data, not moving millions of rows through your laptop. That's hours of paging and RCU with a one-statement alternative.

**Try it yourself**

**1. Raise the cap — or don't**

The refusal names three ways forward. When is raising Settings → Querying → Max preview items the *right* one, and when is it the wrong one?

_Hint:_ The cap exists so a preview stays reviewable. Ask what number a human can actually check.

_Solution:_ Raise it when your legitimate working sets sit just past the default — say 1,500 rows you genuinely eyeball. Don't raise it to swallow a 40,000-row set: nobody reviews 40,000 rows, so you'd keep the ceremony of a preview while losing its meaning. Past human scale, take the job path — its per-write guard is honest about what's being checked.

**2. Guard-rail the encore**

Write the two statements you'd run, in order, to retire this year's dormant subscriptions at scale.

_Hint:_ Chapter three's seatbelt, then the scoped write — the size changes the *how* (job, not preview), never the order.

_Solution:_ Backup first, then the scoped UPDATE via Run as job. The WHERE is already pinned to subscriptions with the sk prefix; the job re-checks it on every row it writes.

```sql
BACKUP TABLE "BeanAndBark" AS 'beanandbark-pre-cleanup-2026'

UPDATE "BeanAndBark"
SET Status = 'retired'
WHERE begins_with(sk, 'SUB#') AND Status = 'dormant'
```

> **Where next?:** That closes the loop on writes at every scale: [conditional and preview-first writes](https://www.kanject.com/docs/dynostudio-partiql-writes/) under the cap, background jobs past it, and the fleet for table-scale reads. If a standing question keeps forcing full scans, the durable fix is still an index — [add one](https://www.kanject.com/docs/dynostudio-bean-bark-gsi/) rather than re-scanning faster.

---
_Source: https://www.kanject.com/docs/dynostudio-bean-bark-bulk-jobs/ · Kanject Docs_
