# `Ankusa.BlobStore.Options`
[🔗](https://github.com/jamescarr/ankusa/blob/ankusa-v0.7.0/packages/ankusa/lib/ankusa/blob_store/options.ex#L1)

The transport options every HTTP object store takes (`Ankusa.BlobStore.S3`,
`GCS`, `Azure`, `OCI`), their defaults and their ranges. They are adapter
options, like `:timeout_ms` and `:retries`: Elixir config, and the `s3`/`gcs`
sections of the YAML.

| Option | Default | What |
| --- | --- | --- |
| `:timeout_ms` | `10_000` | One metadata-sized request (`GET`, `DELETE`, `LIST`, a body up to 1 MiB): connect and response |
| `:upload_timeout_ms` | `60_000` | One request that carries a large body: a bigger single `PUT`, or one multipart part |
| `:op_deadline_ms` | `120_000` | **One absolute deadline per blob operation**, covering every attempt, every multipart part and (for `Ankusa.ClaimCheck.check_in/4`) every pack-id retry together. Nothing sleeps or sends past it |
| `:multipart_threshold_bytes` | `8_388_608` | A `put` of more bytes goes up as a multipart (S3, OCI), block-list (Azure) or resumable (GCS) upload |
| `:multipart_part_bytes` | `8_388_608` | The size of each part or chunk (a multiple of 256 KiB, at least 5 MiB) |
| `:retries` | `2` | Extra attempts after a transient failure (`Ankusa.BlobStore.Retry`) |
| `:breaker_failures` | `5` | Consecutive failures of a `{endpoint, bucket}` that open its breaker; `0` turns the breaker off, the latency trip with it |
| `:breaker_open_ms` | `30_000` | How long an open breaker answers `{:error, :breaker_open}` before it lets one request through |
| `:breaker_slow_ms` | `5_000` | The breaker also opens when the p99 of the last 100 metadata-sized requests passes this |
| `:pool_size`, `:pool_count` | `32`, `1` | Connections per pool and pools, of the Finch pool the instance starts for this store's endpoint (`Ankusa.BlobStore.Pool`) |
| `:pool_timeout` | `5_000` | How long a request waits for a free connection of that pool |

Every bound is checked when the config is validated (`validate!/3`), so a bad
value is a boot error and never a request that waits an hour.

# `default`

```elixir
@spec default(atom()) :: pos_integer() | non_neg_integer()
```

The option's default.

# `get`

```elixir
@spec get(keyword(), atom()) :: non_neg_integer()
```

`opts[key]`, else the default.

# `kind`

```elixir
@spec kind(non_neg_integer()) :: :small | :bulk
```

`:small` for a request that carries at most 1 MiB, `:bulk` for one that
carries more: which timeout it runs under, and whether its latency is a
breaker sample.

# `remaining`

```elixir
@spec remaining(keyword()) :: integer()
```

Milliseconds left to `opts[:deadline]` (negative when passed).

# `validate!`

```elixir
@spec validate!(module(), keyword(), String.t()) :: :ok
```

Raise `ArgumentError` naming `where` unless every transport option `opts`
sets is an integer inside its range, and the part size is a multiple of
256 KiB. Stores that are not an HTTP object store are not checked.

# `with_deadline`

```elixir
@spec with_deadline(keyword()) :: keyword()
```

`opts` with `:deadline` (an absolute `System.monotonic_time(:millisecond)`)
set to `op_deadline_ms` from now, or to the deadline the caller already
passed when that is sooner. Every adapter entry point calls it once, so every
request the operation makes shares one deadline.

---

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