fdb-client

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 25, 2026 License: MIT

README

fdb-client

Public Go smart client for FrogoDB.

Scope

  • ./pkg/client: smart client (routing, pooling, policies, batch/pipeline).
  • ./pkg/queries: reusable query helpers (timeseries, previous, window, lsh).
  • ./pkg/protocol, ./pkg/ripemd160: client-side wire/hash support.

Install

go get github.com/FrogoAI/fdb-client

Connect

Use one or more seed nodes. The client connects to the first available seed, discovers the cluster topology in the background, and routes keys directly to the node that owns the partition.

package main

import (
    "context"
    "log"
    "time"

    "github.com/FrogoAI/fdb-client/pkg/client"
)

func main() {
    c, err := client.New("node1:3000", "node2:3000", "node3:3000")
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()

    ctx := context.Background()

    pingCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()

    if err := c.Ping(pingCtx); err != nil {
        log.Fatalf("FrogoDB connection is not live: %v", err)
    }
}

For explicit connection policy:

c, err := client.NewWithConfig(client.Config{
    Seeds:             []string{"node1:3000", "node2:3000"},
    PoolSizePerNode:   64,
    ConnectionTimeout: 5 * time.Second,
    IdleTimeout:       55 * time.Second,
    TendInterval:      10 * time.Millisecond,
    MaxErrorRate:      100,
    ErrorRateWindow:   time.Second,
})

Records

Put writes bins, Get reads records, and Delete removes records. Write options make bin semantics, existence checks, and TTL behavior explicit.

err := c.Put(ctx, "myns", "users", "user-123", map[string]any{
    "name":  "Alice",
    "age":   int64(30),
    "score": 95.5,
}, client.WithMergeBins(), client.WithPreserveTTL())
if err != nil {
    log.Fatal(err)
}

rec, err := c.Get(ctx, "myns", "users", "user-123")
if err != nil {
    log.Fatal(err)
}
log.Printf("name=%s age=%d", rec.Bins["name"], rec.Bins["age"])

existed, err := c.Delete(ctx, "myns", "users", "user-123")
if err != nil {
    log.Fatal(err)
}
log.Printf("deleted=%t", existed)

Common write options:

  • client.WithMergeBins(): update supplied bins and preserve untouched bins.
  • client.WithReplaceBins(): rebuild the record from only supplied bins.
  • client.WithPreserveTTL(): keep the current TTL.
  • client.WithTTL(seconds): set a new TTL in seconds.
  • client.WithClearTTL(): clear expiration.
  • client.WithCreateOnly(): fail if the key already exists.
  • client.WithReplace(): require that the key already exists.
  • client.WithGeneration(n): optimistic locking.

Query Helpers

The query packages sit on top of the client and accept the shared queries.Client interface. *client.Client already satisfies that interface, so the same client value can be passed directly.

import (
    "time"

    "github.com/FrogoAI/fdb-client/pkg/queries"
    "github.com/FrogoAI/fdb-client/pkg/queries/lsh"
    "github.com/FrogoAI/fdb-client/pkg/queries/previous"
    "github.com/FrogoAI/fdb-client/pkg/queries/timeseries"
    "github.com/FrogoAI/fdb-client/pkg/queries/window"
)

src := queries.NewMapSource(map[string]any{
    "event_id":             "evt-789",
    "standard.user_id":     "user-42",
    "standard.email":       "alice@example.com",
    "standard.merchant_id": "shop-456",
    "amount":               42.5,
    "frequency":            int64(10),
    "recency":              3.14,
    "event_time":           time.Now(),
})
Timeseries

Use timeseries queries for time-bucketed aggregations such as counts, averages, standard deviation, min/max, distinct counts, and percentiles.

tsReq := timeseries.Request{
    Name:           "transaction_stats",
    Namespace:      "scoring",
    GroupBy:        []string{"standard.user_id"},
    Range:          24 * time.Hour,
    Fields:         queries.FieldCount | queries.FieldAvg | queries.FieldSTD | queries.FieldMin | queries.FieldMax,
    Value:          "amount",
    TTL:            48 * time.Hour,
    IncludeCurrent: true,
}

tsResult, err := timeseries.Execute(ctx, c, tsReq, src)
if err != nil {
    log.Fatal(err)
}
log.Printf("count=%d avg=%.2f std=%.2f", tsResult.Count, tsResult.Average(), tsResult.STD())
Previous

Use previous queries to retrieve a value from the previous event for the same entity. Exclude can require the previous event to differ on another field.

prevReq := previous.Request{
    Name:           "prev_amount",
    Namespace:      "scoring",
    Ref:            "standard.user_id",
    Retrieve:       "amount",
    Exclude:        "standard.merchant_id",
    EventID:        src.String("event_id"),
    TTL:            24 * time.Hour,
    IncludeCurrent: false,
}

prevResult, err := previous.Execute(ctx, c, prevReq, src)
if err != nil {
    log.Fatal(err)
}
if prevResult.Found {
    log.Printf("previous amount=%v", prevResult.Value)
}
Window

Use window queries for exact sliding-window aggregation over the last N events.

winReq := window.Request{
    Name:           "last_10_amounts",
    Namespace:      "scoring",
    Ref:            "standard.user_id",
    Value:          "amount",
    WindowSize:     10,
    Fields:         queries.FieldCount | queries.FieldAvg | queries.FieldSTD | queries.FieldPercentile,
    PercentileP:    0.90,
    EventID:        src.String("event_id"),
    TTL:            24 * time.Hour,
    IncludeCurrent: true,
}

winResult, err := window.Execute(ctx, c, winReq, src)
if err != nil {
    log.Fatal(err)
}
log.Printf("count=%d avg=%.2f p90=%.2f", winResult.Count, winResult.Avg, winResult.Percentile)
LSH

Use LSH queries for near-duplicate string buckets and vector-based behavioural clustering. The server computes the LSH bucket and the client stores entries with the requested TTL.

dedupResult, err := lsh.Dedup(ctx, c, lsh.DedupRequest{
    Namespace: "scoring",
    Reference: "standard.email",
    TTL:       24 * time.Hour,
}, src)
if err != nil {
    log.Fatal(err)
}
log.Printf("email dedup bucket=%s", dedupResult.BucketID)

vectorResult, err := lsh.Vector(ctx, c, lsh.VectorRequest{
    Namespace:  "scoring",
    Attributes: []string{"amount", "frequency", "recency"},
    TTL:        24 * time.Hour,
}, src)
if err != nil {
    log.Fatal(err)
}
log.Printf("behavioral cluster=%s", vectorResult.BehavioralID)

For direct client-level LSH calls without the query helper source mapping:

bucketID, err := c.LSHDedup(ctx, "scoring", "email", "alice@example.com", client.WithTTL(86400))
if err != nil {
    log.Fatal(err)
}
log.Printf("email bucket=%s", bucketID)

behavioralID, err := c.LSHVector(ctx, "scoring", "behavior", []float64{42.5, 10, 3.14}, client.WithTTL(86400))
if err != nil {
    log.Fatal(err)
}
log.Printf("behavioral id=%s", behavioralID)

Bloom Filter

Bloom filters are exposed through atomic Operate calls. They are useful for fast membership checks where false positives are acceptable.

_, err := c.Operate(ctx, "myns", "filters", "seen-users", []client.Operation{
    client.BloomInitOp("bloom", 10000, 0.01),
})
if err != nil {
    log.Fatal(err)
}

_, err = c.Operate(ctx, "myns", "filters", "seen-users", []client.Operation{
    client.BloomAddOp("bloom", []byte("user-42")),
})
if err != nil {
    log.Fatal(err)
}

rec, err := c.Operate(ctx, "myns", "filters", "seen-users", []client.Operation{
    client.BloomTestOp("bloom", []byte("user-42")),
})
if err != nil {
    log.Fatal(err)
}
log.Printf("probably seen=%v", rec.Bins["bloom"])

Other bloom helpers include client.BloomRemoveOp and client.BloomResetOp.

HyperLogLog

HyperLogLog estimates cardinality, for example unique users or unique devices.

_, err := c.Operate(ctx, "myns", "stats", "page-visitors", []client.Operation{
    client.HLLInitOp("visitors", 14, 6),
})
if err != nil {
    log.Fatal(err)
}

_, err = c.Operate(ctx, "myns", "stats", "page-visitors", []client.Operation{
    client.HLLAddOp("visitors", []byte("user-1"), []byte("user-2"), []byte("user-3")),
})
if err != nil {
    log.Fatal(err)
}

rec, err := c.Operate(ctx, "myns", "stats", "page-visitors", []client.Operation{
    client.HLLCountOp("visitors"),
})
if err != nil {
    log.Fatal(err)
}
log.Printf("unique visitors=%v", rec.Bins["visitors"])

Use client.HLLUnionOp, client.HLLUnionCountOp, and client.HLLIntersectCountOp to combine estimates across records.

TDigest

TDigest estimates quantiles and distribution statistics, for example p95 or p99 latency.

_, err := c.Operate(ctx, "myns", "stats", "latency", []client.Operation{
    client.TDigestAddOp("tdigest", 42.0, 1.0),
    client.TDigestAddOp("tdigest", 125.0, 1.0),
    client.TDigestAddOp("tdigest", 300.0, 1.0),
})
if err != nil {
    log.Fatal(err)
}

rec, err := c.Operate(ctx, "myns", "stats", "latency", []client.Operation{
    client.TDigestQuantileOp("tdigest", 0.99),
})
if err != nil {
    log.Fatal(err)
}
log.Printf("p99 latency=%v", rec.Bins["tdigest"])

Other TDigest helpers include client.TDigestCountOp, client.TDigestMinOp, client.TDigestMaxOp, client.TDigestCDFOp, and client.TDigestMergeOp.

More Documentation

  • ./docs/client.md: full client API, CRUD, scans, batch operations, policies, and topology.
  • ./docs/queries.md: full query helper API and examples.
  • ./docs/publish-checklist.md: repository publication and release checklist.

Contributing and Security

  • ./CONTRIBUTING.md: development workflow and contribution guidelines.
  • ./SECURITY.md: private vulnerability reporting policy.

License

MIT. See ./LICENSE.

Contracts

  • This repository must not import private server modules.
  • FrogoDB server can depend on this client module.

Build

go test ./...
make lint
make test-race

Directories

Path Synopsis
pkg
client
Package client provides the FrogoDB smart client library.
Package client provides the FrogoDB smart client library.
protocol
Package protocol defines the FrogoDB binary wire protocol.
Package protocol defines the FrogoDB binary wire protocol.
ripemd160
Package ripemd160 provides a zero-allocation RIPEMD-160 hash implementation for FrogoDB key digest computation.
Package ripemd160 provides a zero-allocation RIPEMD-160 hash implementation for FrogoDB key digest computation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL