---
id: f5707f0f4f230a276a3428b1eb1e6469
author: Lemon Mint
title: 'Schottky: Nullallokering, rekkefølgebevarende byte-nøkkelkoding for Go'
description: Allokeringsfritt Go-bibliotek som koder sammensatte tupler til byte-nøkler sortert etter memcmp, med uavhengig sortering stigende/synkende (ASC/DESC), markører for plassering av NULL og grenser for prefiksskanning.
language: "no"
date: 2026-08-31T07:47:42.614875839Z
path: /schottky
go_package: gosuda.org/schottky
go_repourl: https://github.com/gosuda/schottky.git
category: database
---

Når man implementerer LSM-tree- eller B-tree-baserte nøkkel-verdi-lagre og databaseindekser, må sammensatte felt ofte kombineres til en enkelt byte-nøkkel.

Standard serialiseringsformater som JSON eller Protocol Buffers er ikke utformet for dette formålet, fordi deres serialiserte byte-utdata ikke bevarer den naturlige sorteringsrekkefølgen som kreves av usignerte bytevise sammenligninger (`bytes.Compare` eller `memcmp`).

Schottky er et Go-bibliotek utviklet for å kode sammensatte tupler med flere typer til bevarende-rekkefølge byte-nøkler.

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

### Serialisering kontra sorteringsnøkler

Å garantere korrekt bytevis sortering krever håndtering av flere lavnivådetaljer knyttet til datarepresentasjon:

- **Heltall**: Standard big-endian-koding med tosekomplement bryter den naturlige rekkefølgen på grunn av det mest signifikante fortegnsbitet. Invertering av fortegnsbitet er nødvendig for korrekte usignerte bytesammenligninger.
- **Flyttall**: Krever justeringer av fortegnsbitet, invertert rekkefølge for negative verdier, og konsekvent håndtering av `NaN` og `-0`.
- **Strenger og byte-slices med variabel lengde**: Feltgrenser må bevares uten å bryte prefiks-sorteringsrekkefølger.
- **Krav til sammensatte nøkler**: Støtte for uavhengig ASC/DESC-rekkefølge per felt, frakoblede NULLS FIRST/LAST-regler, streng leksikografisk prioritet (tidligere felt bestemmer rekkefølgen) og kompatibilitet med prefiksskanning.

Schottky konverterer hver verdi til en kanonisk nyttelast før presenseringskoder og retningsorientering anvendes. For DESC-felt inverteres hver byte i ASC-nyttelasten bitvis (`^b`). NULL-plassering håndteres via dedikerte presenseringskoder og opererer uavhengig av sorteringsretning.

### Grunnleggende bruk

Følgende eksempel bygger en sammensatt nøkkel bestående av en konto-ID (`ASC, NULLS LAST`) og et navn (`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 tilbyr fire eksplisitte konfigurasjoner for sorteringsrekkefølge:

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

NULL-posisjonering utledes aldri implisitt. Hvis en ugyldig `Order`-verdi sendes inn, registrerer builderen `ErrInvalidOrder`, som returneres ved kall til `Key()` eller `Err()`.

### Prefiksskanning og områdegrenser

Schottky-sammensatte nøkler inneholder ingen globale headere, metadata om antall felt, typekoder eller avsluttende markører. Forutsatt at skjemaet er kjent på forhånd, sammenkobles feltkodingene ganske enkelt.

På grunn av denne oppbyggingen danner de kodede bytene til ledende felt et gyldig prefiks for områdeskanninger. I eksemplet ovenfor kan `accountPrefix` brukes direkte som et prefiksfilter for å skanne alle poster der `Account ID == 42`.

For å beregne den eksklusive øvre grensen for halvåpne `[prefix, upper)`-områdeskanninger brukes `PrefixUpperBound`:

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

if err != nil {
        panic(err)
}

if finite {
        // Halvåpen skanning av [accountPrefix, upper)
} else {
        // Åpen skanning uten øvre grense
}
```

_Merk: `Builder.Len()` må måles ved rene feltgrenser. Oppdeling inne i et felts interne byte-strøm gir et ugyldig prefiks._

### Null allokeringer og bufferhåndtering

Nøkkelgenerering kjøres ofte på kritiske databaseveier. For å eliminere heap-allokeringer og overhead ved bufferendring opererer `Builder` strengt innenfor kapasiteten til det kallerleverte skiven og vil ikke reallokere internt.

Hvis bufferen går tom for kapasitet, registreres `ErrShortBuffer` uten at delvise byte skrives. Feltskrivinger er atomiske, og den første feilen som oppstår bevares til den kontrolleres via `Key()` eller `Err()`. Tilstrekkelig kapasitet på forhånd sikrer koding uten allokeringer.

Bufferstørrelser kan beregnes på forhånd ved hjelp av hjelpefunksjoner som `EncodedBytesSize`, `EncodedStringSize` og `EncodedDecimalSize`, eller via faste størrelseskonstanter. Den returnerte nøkkelen refererer direkte til den oppgitte bufferen, og ansvaret for minnelivssyklusen overlates til kalleren.

`Decoder` fungerer symmetrisk: den låner direkte fra inndatanøkkelen, krever kallerleverte destinasjonsbuffere for felt med variabel lengde, og tilbyr `Remaining() == 0` for å oppdage etterfølgende byte eller skjemaavvik.

### Støttede datatyper

- **Heltall**: Signerte og usignerte (8-bit til 64-bit), `Int128`
- **Flyttall og numeriske verdier**: `Float32`, `Float64`, desimaltekst
- **Grunnleggende typer**: Binær streng, byte-slice, boolsk verdi, enum-rang
- **Dato og tid**: Dato, tid, tid med sonesone, tidsstempel, varighet, kalenderintervall
- **Nettverk og identifikatorer**: UUID, MAC, IP, IP-prefiks, kanonisk nettverksprefiks, LSN
- **Sammensatte strukturer**: Nøstede tupler, områder og rå strukturkodinger
- **Kollasjon**: Unicode-kollasjonsnøkler og eksterne kanoniske tokens

SQL-typeoversettelse er tilpasset sorteringsreglene for PostgreSQL 18 B-tree. Typer som avhenger av databasekataloger eller intern motortilstand håndteres ved å sende inn eksterne kanoniske tokens.

### Strengkollasjon

`Builder.String` bruker som standard rå UTF-8-binær rekkefølge. For lokalitetsbevisst sortering tilbyr Schottky en samtidighetssikker, uforanderlig `Collator`:

- **Deterministisk kollasjon**: Koder kollasjonsnøkkelen sammen med rå UTF-8-byte for å gi en tie-breaker når kollasjonsvektene er identiske.
- **Ikke-deterministisk kollasjon**: Behandler kollasjonslike strenger som identiske, og utelater den rå byte-tie-breaker.

Unicode- og profilversjoner bør spores i metadataskjemaet. Dersom kollasjonsleverandører eller profilinnstillinger endres, må eksisterende nøkler bygges på nytt.

### Skjemahåndtering

Fordi Schottky-nøkler er rå byte-sekvenser uten header, må skjemalaget spore:

1. Feltsekvens og datatyper.
2. Sorteringsretninger (`ASC`/`DESC`) og NULL-rekkefølge (`NULLS FIRST`/`LAST`).
3. Strengkollasjon og normaliseringsregler.
4. Schottky- og kollasjonsprofilversjoner.

Sammenligning av nøkler generert med ulike skjemaer, eller dekoding mot et avvikende skjema, bryter rekkefølgegarantiene.

### Ytelse og lenker

På Go 1.27+ kan eksperimentell portabel SIMD-akselerasjon aktiveres med `GOEXPERIMENT=simd`. Skalare og SIMD-stier produserer byte-identiske nøkler.

- **GitHub-repositorium**: https://github.com/gosuda/schottky
- **Spesifikasjon for nøkkeloppsett**: https://github.com/gosuda/schottky/blob/main/docs/03-key-layout.md
- **Veiledning for SQL-typeoversettelse**: https://github.com/gosuda/schottky/blob/main/docs/17-sql-type-map.md
- **Go API-referanse**: https://github.com/gosuda/schottky/blob/main/docs/18-api.md
