# `Ankusa.Sink.GooglePubSub.Batcher`
[🔗](https://github.com/jamescarr/ankusa/blob/ankusa-v0.7.0/packages/ankusa/lib/ankusa/sink/google_pubsub/batcher.ex#L1)

`Ankusa.Sink.GooglePubSub`'s codec for `Ankusa.Sink.Batcher`: up to 1 000
deliveries to one topic become one `topics.publish` request.

`publish` is all or nothing: a `200` names one message id per message, and a
refusal answers the request as a whole with no per-message result. So the
reply maps back like this:

  * a `200` with one id for each message: every entry is `:ok`; any other
    `200` fails every entry with `{:unexpected_reply, body}`.
  * a transient error (`UNAVAILABLE`, a `5xx`, a timeout, no credentials), a
    config error (`NOT_FOUND`, `PERMISSION_DENIED`, `UNAUTHENTICATED`: every
    message's fault, so a permanent `{:config, _}` for each) or a throttle
    (`RESOURCE_EXHAUSTED`, `429`, `503`): every entry gets it, with its own retry
    class and jitter, and retries on its own.
  * `INVALID_ARGUMENT` for one message: the request is **bisected**. Its two
    halves are published one after the other (so the entries' order is kept),
    and a half that is refused again is split again, until the refused message
    stands alone and is `{:permanent, {:pubsub, status, "INVALID_ARGUMENT",
    message}}` while every other message of the batch is delivered. One bad
    message among 999 good ones takes `2 * log2(1000)` extra requests, and the
    work is bounded: a batch of `n` spends at most `2 * (ceil(log2 n) + 1)`
    splits, after which the entries still unresolved are `{:batch_rejected,
    reason}` (transient: they retry, in other batches). The extra requests are
    counted in `ankusa_sink_batch_bisections_total`.

`Ankusa.Sink.GooglePubSub` checks a message's size and attributes before it
enters a batch, so the usual causes of a refusal never reach one.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
