---
id: f5707f0f4f230a276a3428b1eb1e6469
author: Lemon Mint
title: 'Schottky: Zero-Allocation, Order-Preserving Byte-Key Encoding for Go'
description: Biblioteka w języku Go, nieprzydzielająca pamięci (zero-allocation), która koduje złożone krotki w bajtowe klucze uporządkowane zgodnie z memcmp, z niezależnym sortowaniem ASC/DESC, znacznikami określającymi położenie wartości NULL oraz granicami skanowania prefiksów.
language: pl
date: 2026-08-31T07:47:42.614875839Z
path: /schottky
go_package: gosuda.org/schottky
go_repourl: https://github.com/gosuda/schottky.git
category: database
---

Podczas implementacji magazynów klucz-wartość opartych na LSM-tree lub B-tree oraz indeksów baz danych, pola złożone często muszą zostać połączone w jeden klucz bajtowy.

Standardowe formaty serializacji, takie jak JSON czy Protocol Buffers, nie są projektowane w tym celu, ponieważ ich bajtowe wyjścia serializacji nie zachowują naturalnej kolejności sortowania wymaganej przez porównania bajtów bez znaku (`bytes.Compare` lub `memcmp`).

Schottky to biblioteka w Go zaprojektowana do kodowania wielotypowych krotek złożonych w klucze bajtowe zachowujące porządek.

```bash
go get gosuda.org/schottky@latest
```

### Serializacja a klucze sortujące

Zagwarantowanie poprawnego sortowania bajtowego wymaga uwzględnienia kilku szczegółów niskopoziomowej reprezentacji danych:

- **Liczby całkowite**: Standardowe kodowanie dwójkowe uzupełnień do dwóch w porządku big-endian łamie naturalny porządek ze względu na najbardziej znaczący bit znaku. Odwrócenie bitu znaku jest niezbędne do poprawnych porównań bajtowych bez znaku.
- **Liczby zmiennoprzecinkowe**: Wymagają korekt bitu znaku, odwróconego porządku dla wartości ujemnych oraz spójnej obsługi `NaN` i `-0`.
- **Ciągi znaków o zmiennej długości i wycinki bajtów**: Granice pól muszą być zachowane bez naruszania porządku sortowania prefiksów.
- **Wymagania dotyczące kluczy złożonych**: Obsługa niezależnego porządku ASC/DESC dla każdego pola, niezależnych reguł NULLS FIRST/LAST, ścisłego pierwszeństwa leksykograficznego (wcześniejsze pola determinują porządek) oraz zgodności ze skanowaniem prefiksów.

Schottky przekształca każdą wartość w kanoniczny ładunek przed zastosowaniem znaczników obecności i orientacji kierunkowej. Dla pól DESC każdy bajt ładunku ASC jest bitowo odwracany (`^b`). Umieszczenie wartości NULL realizowane jest za pomocą dedykowanych znaczników obecności i działa niezależnie od kierunku sortowania.

### Podstawowe użycie

Poniższy przykład buduje klucz złożony składający się z identyfikatora konta (`ASC, NULLS LAST`) oraz nazwy (`DESC, NULLS FIRST`):

```go
package main

import (
        "fmt"

        "gosuda.org/schottky"
)

func main() {
        storage := make([]byte, 0, 128)
        builder := schottky.NewBuilder(storage)

        builder.Int64(42, schottky.AscNullsLast)
        accountPrefixLen := builder.Len()

        builder.String("Ada", schottky.DescNullsFirst)
        key, err := builder.Key()

        if err != nil {
                panic(err)
        }
        accountPrefix := key[:accountPrefixLen]
        fmt.Printf("key=%x\nprefix=%x\n", key, accountPrefix)
}
```

Schottky udostępnia cztery jawne konfiguracje porządku sortowania:

- `AscNullsFirst`
- `AscNullsLast`
- `DescNullsFirst`
- `DescNullsLast`

Pozycja NULL nigdy nie jest wnioskowana w sposób domyślny. Jeśli przekazana zostanie nieprawidłowa wartość `Order`, builder zapisuje `ErrInvalidOrder`, który zostaje zwrócony przy wywołaniu `Key()` lub `Err()`.

### Skanowanie prefiksów i granice zakresów

Klucze złożone Schottky nie zawierają globalnych nagłówków, metadanych liczby pól, znaczników typów ani stopek. Zakładając, że schemat jest znany z góry, kodowania pól są po prostu łączone.

Ze względu na taki układ zakodowane bajty wiodących pól tworzą prawidłowy prefiks dla skanów zakresowych. W powyższym przykładzie `accountPrefix` może być bezpośrednio użyty jako filtr prefiksowy do przeskanowania wszystkich rekordów, dla których `Account ID == 42`.

Aby obliczyć wykluczające górne ograniczenie dla skanów zakresów półotwartych `[prefix, upper)`, należy użyć `PrefixUpperBound`:

```go
upperStorage := make([]byte, 0, len(accountPrefix))
upper, finite, err := schottky.PrefixUpperBound(upperStorage, accountPrefix)

if err != nil {
        panic(err)
}

if finite {
        // Skan zakresu półotwartego [accountPrefix, upper)
} else {
        // Skan zakresu otwartego bez ograniczeń
}
```

_Uwaga: `Builder.Len()` musi być mierzone na czystych granicach pól. Wycinanie wewnątrz wewnętrznego strumienia bajtów pola tworzy nieprawidłowy prefiks._

### Brak alokacji i zarządzanie buforem

Generowanie kluczy często działa na krytycznych ścieżkach bazy danych. Aby wyeliminować alokacje na stercie i narzut zmiany rozmiaru bufora, `Builder` działa ściśle w obrębie pojemności wycinka dostarczonego przez wywołującego i nie dokonuje wewnętrznej realokacji.

Jeśli bufor wyczerpie pojemność, zapisywany jest `ErrShortBuffer` bez zapisywania częściowych bajtów. Zapisy pól są atomowe, a pierwszy napotkany błąd jest zachowywany do momentu sprawdzenia przez `Key()` lub `Err()`. Zapewnienie wystarczającej pojemności z góry gwarantuje kodowanie bez alokacji.

Rozmiary buforów można obliczyć wcześniej za pomocą funkcji pomocniczych, takich jak `EncodedBytesSize`, `EncodedStringSize` i `EncodedDecimalSize`, lub za pomocą stałych o stałym rozmiarze. Zwracany klucz odwołuje się bezpośrednio do dostarczonego bufora, pozostawiając zarządzanie cyklem życia pamięci wywołującemu.

`Decoder` działa symetrycznie: pożycza dane bezpośrednio z klucza wejściowego, wymaga dostarczonych przez wywołującego buforów docelowych dla pól o zmiennej długości i udostępnia `Remaining() == 0`, aby wykryć nadmiarowe bajty lub niezgodności schematu.

### Obsługiwane typy danych

- **Liczby całkowite**: ze znakiem i bez znaku (od 8 do 64 bitów), `Int128`
- **Liczby zmiennoprzecinkowe i numeryczne**: `Float32`, `Float64`, tekst dziesiętny
- **Typy podstawowe**: ciąg binarny, wycinek bajtów, wartość logiczna, ranga wyliczenia (Enum Rank)
- **Data i czas**: Date, Time, Zoned Time, Timestamp, Duration, Calendar Interval
- **Sieć i identyfikatory**: UUID, MAC, IP, IP Prefix, Canonical Network Prefix, LSN
- **Struktury złożone**: zagnieżdżone krotki (Nested Tuples), zakresy (Ranges) oraz surowe kodowania strukturalne
- **Collation**: Unicode Collation Keys oraz zewnętrzne kanoniczne tokeny

Mapowanie typów SQL jest zgodne z regułami sortowania B-tree w PostgreSQL 18. Typy zależne od katalogów bazy danych lub wewnętrznego stanu silnika są obsługiwane poprzez przekazywanie zewnętrznych kanonicznych tokenów.

### Porównania ciągów znaków

`Builder.String` domyślnie stosuje porządek binarny UTF-8. Do sortowania uwzględniającego ustawienia regionalne Schottky udostępnia bezpieczny współbieżnie, niezmienny `Collator`:

- **Deterministyczne porównywanie (Deterministic Collation)**: Koduje klucz porównania wraz z surowymi bajtami UTF-8, aby zapewnić rozstrzyganie remisów, gdy wagi porównania są identyczne.
- **Niedeterministyczne porównywanie (Nondeterministic Collation)**: Traktuje ciągi równe w porównaniu jako identyczne, pomijając rozstrzyganie remisów na podstawie surowych bajtów.

Wersje Unicode i profili powinny być śledzone w metadanych schematu. Jeśli dostawcy porównań lub ustawienia profilu ulegną zmianie, istniejące klucze muszą zostać odbudowane.

### Zarządzanie schematem

Ponieważ klucze Schottky są surowymi sekwencjami bajtów bez nagłówków, warstwa schematu musi śledzić:

1. Kolejność pól i typy danych.
2. Kierunki sortowania (`ASC`/`DESC`) oraz porządek NULL (`NULLS FIRST`/`LAST`).
3. Reguły porównywania i normalizacji ciągów znaków.
4. Wersje profili Schottky i porównań.

Porównywanie kluczy wygenerowanych przy użyciu różnych schematów lub dekodowanie względem niezgodnego schematu narusza gwarancje porządku.

### Wydajność i odnośniki

W Go 1.27+ eksperymentalne przenośne przyspieszenie SIMD można włączyć za pomocą `GOEXPERIMENT=simd`. Ścieżki skalarne i SIMD tworzą identyczne bajtowo klucze.

- **Repozytorium GitHub**: https://github.com/gosuda/schottky
- **Specyfikacja układu kluczy**: https://github.com/gosuda/schottky/blob/main/docs/03-key-layout.md
- **Przewodnik mapowania typów SQL**: https://github.com/gosuda/schottky/blob/main/docs/17-sql-type-map.md
- **Dokumentacja API Go**: https://github.com/gosuda/schottky/blob/main/docs/18-api.md
