README.md

# ExAlp

ALP (Adaptive Lossless floating-Point) compression for Elixir. Wraps the [SIGMOD 2024 reference implementation](https://github.com/cwida/ALP) as a NIF.

Compresses `{timestamp, value}` time series data using ALP encoding for values and delta-of-delta encoding for timestamps. Optional zstd container compression.

## Performance

Benchmarked against [gorilla_stream](https://hex.pm/packages/gorilla_stream) on 10K gauge data points (CPU-like oscillating values):

| Metric | ExAlp + zstd 2 | Gorilla + zstd 2 |
|--------|---------------|-----------------|
| Compression | **1.63 B/pt** | 4.08 B/pt |
| Encode speed | **28M pts/s** | 12.7M pts/s |
| Decode speed | **18.7M pts/s** | 9.2M pts/s |

2.5x better compression, 2.2x faster encode, 2x faster decode. Lossless.

## Usage

```elixir
points = [{1700000000, 45.2}, {1700000015, 45.8}, {1700000030, 46.1}]

# ALP only
{:ok, blob} = ExAlp.compress(points)
{:ok, ^points} = ExAlp.decompress(blob)

# ALP + zstd container compression
{:ok, blob} = ExAlp.compress(points, compression: :zstd)
{:ok, ^points} = ExAlp.decompress(blob, compression: :zstd)

# ALP + zstd with custom level
{:ok, blob} = ExAlp.compress(points, compression: :zstd, compression_level: 9)
```

## Installation

```elixir
{:ex_alp, "~> 0.1"}
```

Requires a C++17 compiler. The NIF compiles automatically via `elixir_make`.

Optional container compression:

```elixir
{:ezstd, "~> 1.2"}      # for compression: :zstd
{:ex_openzl, "~> 0.4"}  # for compression: :openzl
```

## How ALP Works

Unlike Gorilla (XOR-based), ALP converts floating-point values to integers by multiplying by a power of 10, then bit-packs the integers using Frame-of-Reference encoding. Values that can't be encoded exactly are stored as exceptions.

For example, `45.23` × 100 = `4523` (integer). A block of similar values like `[4523, 4518, 4531, ...]` has a small range that packs into few bits per value.

Timestamps use delta-of-delta encoding with varint compression (same approach as Gorilla).

## License

MIT