# Seek & Snapshots (/connectors/gcp-pub-sub/how-to/seek-and-snapshots)



Because every topic is backed by a durable, replayable **Events Store log** `gcp.{t}`, a
subscription can be **rewound**. `Seek` resets a subscription's position to a point in the past —
either a **timestamp** or a saved **snapshot** — and replays the topic log from there into the
subscription's queue. This is how you reprocess messages: redeploy a consumer with a bug fix, then
seek the subscription back to before the bad window and let it re-consume.

## How Seek works [#how-seek-works]

A `Seek` against a subscription:

1. **Resolves the start sequence** from the topic log — from a timestamp (the first message at or
   after that time) or from a snapshot's captured cursor.
2. **Purges the subscription queue and drops outstanding leases** — in-flight `ack_id`s become
   invalid (this is the reset).
3. **Replays the topic log** from the start sequence and **re-applies the subscription's filter** as
   it fans the replayed messages back into `gcp.sub.{s}`.

```text
Seek(subscription, time | snapshot)
   │  resolve start seq from gcp.{t}
   ▼
purge sub queue + drop leases   (in-flight ack_ids now invalid)
   │
   ▼
replay gcp.{t} from start seq ──(re-apply filter)──▶ refill gcp.sub.{s}
   │
   ▼  bounded by MaxSeekReplay (default 1,000,000) → hit cap = WARN, no silent loss
```

A `Seek` call looks the same in every client; both forms are shown here:

<Tabs groupId="language" items="['Go','Python','Java','JavaScript','C#','Ruby']">
  <Tab value="Go">
    ```go
    // Seek to a timestamp.
    _, _ = subClient.Seek(ctx, &pubsubpb.SeekRequest{
    	Subscription: subName,
    	Target:       &pubsubpb.SeekRequest_Time{Time: timestamppb.New(cutoff)},
    })
    // Seek to a snapshot.
    _, _ = subClient.Seek(ctx, &pubsubpb.SeekRequest{
    	Subscription: subName,
    	Target:       &pubsubpb.SeekRequest_Snapshot{Snapshot: snapshotName},
    })
    ```
  </Tab>

  <Tab value="Python">
    ```python
    # Seek to a timestamp.
    subscriber.seek(request={"subscription": sub_path, "time": cutoff})
    # Seek to a snapshot.
    subscriber.seek(request={"subscription": sub_path, "snapshot": snapshot_path})
    ```
  </Tab>

  <Tab value="Java">
    ```java
    // Seek to a timestamp.
    subscriptionAdminClient.seek(SeekRequest.newBuilder()
        .setSubscription(subName.toString()).setTime(cutoff).build());
    // Seek to a snapshot.
    subscriptionAdminClient.seek(SeekRequest.newBuilder()
        .setSubscription(subName.toString()).setSnapshot(snapshotName.toString()).build());
    ```
  </Tab>

  <Tab value="JavaScript">
    ```javascript
    const sub = pubSubClient.subscription('orders-sub');
    // Seek to a timestamp.
    await sub.seek(cutoff);
    // Seek to a snapshot.
    await sub.seek('orders-snapshot');
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    // Seek to a timestamp.
    await subscriber.SeekAsync(new SeekRequest
    {
        SubscriptionAsSubscriptionName = subName,
        Time = Timestamp.FromDateTime(cutoff),
    });
    // Seek to a snapshot.
    await subscriber.SeekAsync(new SeekRequest
    {
        SubscriptionAsSubscriptionName = subName,
        SnapshotAsSnapshotName = snapshotName,
    });
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    sub = pubsub_client.subscription "orders-sub"
    # Seek to a timestamp.
    sub.seek cutoff
    # Seek to a snapshot.
    sub.seek snapshot
    ```
  </Tab>
</Tabs>

## Timestamp clamping [#timestamp-clamping]

<Callout type="info">
  **Seeking before the retained window clamps — it is not an error.** A `Seek` to a timestamp older
  than the earliest retained message does not fail; it clamps to the **earliest retained message** and
  replays from there. Because per-resource retention is itself clamped to the broker's
  `Store.MaxRetention`, what is "retained" depends on the broker ceiling. Don't rely on a pre-window
  seek returning an error to detect "too far back" — it silently starts at the oldest available
  message. See [Reliability](/connectors/gcp-pub-sub/how-to/reliability).
</Callout>

## Replay cap [#replay-cap]

A single `Seek` replays at most `CONNECTORS_GCP_MAX_SEEK_REPLAY` messages (default **1,000,000**).

<Callout type="warn">
  **Hitting the replay cap stops at the cap and logs a WARN — there is no silent loss.** You simply do
  not replay beyond the limit in one seek. Raise `CONNECTORS_GCP_MAX_SEEK_REPLAY`, or seek in smaller
  windows, if you need to replay more.
</Callout>

## Snapshots [#snapshots]

A **snapshot** captures a subscription's current cursor so you can seek back to it later without
knowing an exact timestamp:

* `CreateSnapshot(subscription)` records the cursor as a registry record.
* `Seek(subscription, snapshot)` rewinds to that captured cursor.
* Snapshots have a **7-day default expiry** and are swept hourly. `UpdateSnapshot` may change the
  `labels` and `expire_time`.

<Callout type="warn">
  **You cannot snapshot a detached subscription.** `CreateSnapshot` on a subscription whose topic has
  been deleted or detached returns `FAILED_PRECONDITION`. Snapshot **before** you detach.
</Callout>

## Related [#related]

<Cards>
  <Card title="Reliability" href="/connectors/gcp-pub-sub/how-to/reliability" description="Retention clamping that bounds how far back a seek can rewind, plus dead-letter and retry." />

  <Card title="Capabilities" href="/connectors/gcp-pub-sub/reference/capabilities" description="The Seek, snapshot, and DetachSubscription RPCs and the full supported v1 surface." />

  <Card title="Limits & rules" href="/connectors/gcp-pub-sub/reference/limits-and-rules" description="The MaxSeekReplay ceiling, retention range, and snapshot expiry." />
</Cards>
