Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Docs

# Build the Hugo documentation site and publish it to GitHub Pages.
#
# Pages must be enabled for the repository with "Source: GitHub Actions"
# (Settings → Pages). The site is served at https://predatorray.github.io/candybox/.
on:
push:
branches: [main]
paths:
- 'docs/**'
- '.github/workflows/docs.yml'
# Allow manual runs from the Actions tab.
workflow_dispatch:

# Cancel superseded runs on the same branch to save CI minutes.
concurrency:
group: pages-${{ github.ref }}
cancel-in-progress: true

# Least-privilege token scoped to what the Pages deploy needs.
permissions:
contents: read
pages: write
id-token: write

env:
# Keep in sync with docs/README.md; the theme requires >= 0.158.0 extended.
HUGO_VERSION: '0.158.0'

jobs:
build:
name: Build site
runs-on: ubuntu-latest
steps:
- name: Checkout (with theme submodule)
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0 # enableGitInfo needs full history for last-modified dates

- name: Install Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: ${{ env.HUGO_VERSION }}
extended: true

- name: Configure Pages
id: pages
uses: actions/configure-pages@v5

- name: Build
working-directory: docs
# baseURL comes from configure-pages so the site works regardless of repo/user-page URL.
run: hugo --gc --minify --baseURL "${{ steps.pages.outputs.base_url }}/"

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/public

deploy:
name: Deploy to GitHub Pages
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v4
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ target/

# OS / editor cruft
.DS_Store

# Hugo documentation build output (docs/)
docs/public/
docs/resources/
.hugo_build.lock
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "docs/themes/hugo-book"]
path = docs/themes/hugo-book
url = https://github.com/alex-shpak/hugo-book
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</p>

<p align="center">
<a href="https://predatorray.github.io/candybox/"><img alt="Documentation" src="https://img.shields.io/badge/docs-website-blue"></a>
<a href="https://hub.docker.com/r/zetaplusae/candybox"><img alt="Docker Hub" src="https://img.shields.io/badge/dockerhub-available-2496ED?logo=docker&logoColor=white"></a>
<a href="https://github.com/predatorray/candybox/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/predatorray/candybox"></a>
<br>
Expand Down
57 changes: 57 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Candybox documentation site

The Candybox user manual / project website. It is built with [Hugo](https://gohugo.io) and the
[hugo-book](https://github.com/alex-shpak/hugo-book) theme, and published to GitHub Pages at
**https://predatorray.github.io/candybox/** by the
[`docs.yml`](../.github/workflows/docs.yml) GitHub Actions workflow on every push to `main` that
touches `docs/`.

## Layout

```
docs/
├── config.toml # Hugo site configuration
├── content/
│ ├── _index.md # landing page (https://predatorray.github.io/candybox/)
│ └── docs/ # the manual; this section becomes the left sidebar
│ ├── getting-started/
│ ├── installation/
│ ├── architecture/
│ ├── client/
│ ├── s3-gateway/
│ ├── operations/
│ └── reference/
└── themes/
└── hugo-book/ # theme, vendored as a git submodule
```

To add a page, drop a markdown file (with a `title` and `weight` front matter) into the right folder
under `content/docs/`. The sidebar order follows `weight`.

## Prerequisites

- **Hugo extended**, version **0.158.0 or newer** (the theme requires it). Install from
<https://gohugo.io/installation/>.
- The theme submodule. After cloning the repo:

```bash
git submodule update --init docs/themes/hugo-book
```

(A fresh clone can also use `git clone --recurse-submodules`.)

## Preview locally

```bash
cd docs
hugo server # live-reloading preview at http://localhost:1313/candybox/
```

## Build

```bash
cd docs
hugo --gc --minify # output written to docs/public/
```

`docs/public/` is generated output and is git-ignored.
52 changes: 52 additions & 0 deletions docs/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Hugo configuration for the Candybox documentation site.
#
# The site is built with Hugo (https://gohugo.io) and the hugo-book theme
# (https://github.com/alex-shpak/hugo-book), vendored as a git submodule under
# themes/hugo-book. See README.md in this directory for how to build and preview.
#
# baseURL is the GitHub Pages project URL. The publish workflow overrides it with
# --baseURL when needed, but keep it correct here so local `hugo` produces working
# links too.
baseURL = 'https://predatorray.github.io/candybox/'
title = 'Candybox'
languageCode = 'en-us'
theme = 'hugo-book'

# hugo-book renders the section tree as the left navigation, so a deep, ordered
# content/docs tree is all that is needed for the sidebar.
enableGitInfo = true

# Use a clean URL layout (/docs/getting-started/ rather than .../index.html).
[markup]
[markup.goldmark.renderer]
unsafe = true # allow raw HTML in markdown (badges, the landing hero)
[markup.highlight]
noClasses = false
style = 'github'
[markup.tableOfContents]
startLevel = 2
endLevel = 3

[params]
# hugo-book options — see https://github.com/alex-shpak/hugo-book#configuration
BookTheme = 'auto' # light / dark / auto
BookToC = true # per-page table of contents
BookRepo = 'https://github.com/predatorray/candybox'
BookEditPath = 'edit/main/docs' # "Edit this page" points back at the source
BookSearch = true # client-side search box
BookComments = false
# Only pages under content/docs become part of the manual's menu; the landing
# page (content/_index.md) stays out of the sidebar tree.
BookSection = 'docs'

[menu]
# A couple of fixed links pinned under the sidebar nav.
[[menu.before]]
[[menu.after]]
name = "GitHub"
url = "https://github.com/predatorray/candybox"
weight = 10
[[menu.after]]
name = "Docker Hub"
url = "https://hub.docker.com/r/zetaplusae/candybox"
weight = 20
48 changes: 48 additions & 0 deletions docs/content/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: Candybox
type: docs
bookToc: false
---

# Candybox

> [!TIP]
> **A self-hosted, distributed object store with a sorted LSM-tree index on Apache BookKeeper.**

Candybox is a **distributed, S3-like object store** written in Java. You create *buckets* and store
*objects* in them through a small TCP API, a command-line client, or an S3-compatible HTTP gateway;
Candybox keeps those objects durable and replicated across a cluster.

Under the hood it is a **distributed LSM tree built on [Apache BookKeeper](https://bookkeeper.apache.org/)**:
object data and index live in BookKeeper's replicated, append-only ledgers, and a single fenced owner
per bucket partition keeps reads and writes consistent during failover, with partitions spread evenly
across the cluster.

Because keys are stored sorted and object bytes live behind small pointers, Candybox offers a few
operations an S3-style store cannot do cheaply: bounded / reverse-order range scans, zero-copy
`copy` / `rename`, and `delete-range` over a whole prefix or key window in constant work.

## Where to start

- 🚀 **[Get started]({{< relref "/docs/getting-started" >}})** — spin up a full cluster with Docker
Compose, store your first object, and learn the vocabulary.
- 🧩 **[Concepts & architecture]({{< relref "/docs/architecture" >}})** — how the LSM engine,
BookKeeper ledgers, and fenced ownership fit together.
- ☁️ **[S3 gateway]({{< relref "/docs/s3-gateway" >}})** — use Candybox from any S3 SDK or tool, with
optional SigV4 auth and ACLs.

## Vocabulary

| Term | Meaning |
|---|---|
| **Box** | a bucket |
| **Candy** | an object |
| **CandyKey** | an object key |
| **Syrup** | a data ledger that holds object bytes |

*(Candy in a box — that's the whole theme.)*

---

Candybox is open source under the [Apache License 2.0](https://github.com/predatorray/candybox/blob/main/LICENSE).
The source lives on [GitHub](https://github.com/predatorray/candybox).
25 changes: 25 additions & 0 deletions docs/content/docs/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: Documentation
type: docs
weight: 1
bookFlatSection: false
---

# Candybox documentation

Welcome to the Candybox manual. Candybox is a distributed, S3-like object store backed by a
distributed LSM tree on Apache BookKeeper.

Use the navigation on the left to browse, or jump to a common starting point:

- **[Getting started]({{< relref "/docs/getting-started" >}})** — run a cluster and store your first object.
- **[Installation & downloads]({{< relref "/docs/installation" >}})** — Docker image, distribution tarball, and client library.
- **[Concepts & architecture]({{< relref "/docs/architecture" >}})** — the LSM engine, ledgers, and fenced ownership.
- **[Command-line client]({{< relref "/docs/client" >}})** — the `candybox` CLI and the Java client library.
- **[S3 gateway]({{< relref "/docs/s3-gateway" >}})** — the S3-compatible HTTP endpoint.
- **[Operations]({{< relref "/docs/operations" >}})** — running, monitoring, and Kubernetes.
- **[Configuration reference]({{< relref "/docs/reference/configuration" >}})** — every tunable key.

> [!WARNING]
> Candybox is pre-`1.0` (current version `0.1.0-SNAPSHOT`). APIs, wire protocol, and on-disk formats
> may still change between releases.
102 changes: 102 additions & 0 deletions docs/content/docs/architecture/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: Concepts & architecture
weight: 30
bookCollapseSection: false
---

# Concepts & architecture

Candybox is layered top to bottom: an S3-like object API sits on a per-Box LSM engine, which talks to
two narrow SPIs, which in turn run on Apache BookKeeper (durable ledgers) and ZooKeeper
(coordination/metadata).

```
┌────────────────────────────────────────────────────────────┐
│ Client API — S3-like object store: Boxes of Candy │
├────────────────────────────────────────────────────────────┤
│ LSM engine (candybox-lsm) │
│ WAL → Memtable → SSTables → Manifest │
│ Compaction · GC · HLC · single fenced owner │
├──────────────────────────────┬─────────────────────────────┤
│ LedgerStore SPI │ Coordination SPI │
│ (candybox-bookkeeper) │ (candybox-coordination) │
│ ledger roles: WAL, │ fencing tokens, │
│ SSTable, Syrup, manifest │ manifest pointer CAS │
├──────────────────────────────┼─────────────────────────────┤
│ Apache BookKeeper │ ZooKeeper │
│ (durable ledgers) │ (metadata / CAS) │
└──────────────────────────────┴─────────────────────────────┘
```

`candybox-common` (shared records, `BinaryWriter`/`BinaryReader` serialization, HLC, config) underpins
every layer. Object bytes never enter the LSM tree: candy lives in Syrups and the tree holds only
`CandyLocator` pointers.

## How it works

Candybox blends three well-known designs.

- **A LevelDB-style LSM tree** for the index. Writes land in an in-memory *memtable* fronted by a
write-ahead log; when it fills, it is flushed to an immutable, sorted **SSTable** and later merged
into larger ones by background **compaction**. Reads merge the memtable and SSTables, newest wins.

- **Object data kept out of the tree.** Object bytes are written to dedicated data ledgers
(*Syrups*); the LSM tree stores only a small **pointer** to where each object lives. This keeps the
index tiny and compaction cheap no matter how large the objects are.

- **BookKeeper ledgers as the durable medium.** Every SSTable, WAL, manifest, and Syrup is a
BookKeeper ledger — append-only, replicated, and self-fencing. Candybox never mutates data in
place; updates and deletes are new appends (with tombstones), Apache-Pulsar-style.

## Fenced ownership

Consistency rests on **single, fenced ownership per partition**: every Box is split into a fixed
number of hash partitions, and at any moment exactly one node owns a partition, holding a ZooKeeper
lease with a *fencing token*. Every state-changing operation carries that token, so if ownership moves
during a failure, a stale former owner can no longer corrupt the partition. An elected balancer
spreads partition ownership evenly across the cluster, so one Box's writes are served by many nodes.
Each write is stamped with a hybrid logical clock for last-writer-wins ordering across nodes.

## Operations the sorted LSM tree makes cheap

Because keys are stored sorted and object bytes live behind small pointers, Candybox offers a few
operations an S3-style store cannot do cheaply:

- **Bounded / reverse range scans** walk a `[start, end)` window in either direction, paging with
`--start-after`.
- **Zero-copy `copy` / `rename`** point a new key at the *same* stored bytes — no data is moved — and
`rename` removes the source atomically (within a Box).
- **`delete-range`** deletes a whole prefix or key window with a single range tombstone (constant work
regardless of how many keys it covers); the bytes are reclaimed lazily by compaction.

See the [command-line client]({{< relref "/docs/client" >}}) for how to invoke them.

## Deeper design docs

The authoritative design write-ups live in the repository and go well beyond this overview:

- [`DESIGN.md`](https://github.com/predatorray/candybox/blob/main/DESIGN.md) — record formats and the
fencing / handover protocol.
- [`BOX_PARTITIONING_PLAN.md`](https://github.com/predatorray/candybox/blob/main/BOX_PARTITIONING_PLAN.md)
— partitioning.
- [`MULTIPART_RANGE_PLAN.md`](https://github.com/predatorray/candybox/blob/main/MULTIPART_RANGE_PLAN.md)
— range GET and multipart upload.
- [`S3_GATEWAY_PLAN.md`](https://github.com/predatorray/candybox/blob/main/S3_GATEWAY_PLAN.md) and
[`AUTH_PLAN.md`](https://github.com/predatorray/candybox/blob/main/AUTH_PLAN.md) — the S3 gateway and auth.

## Module layout

| Module | Responsibility |
|---|---|
| `candybox-common` | Domain types, versioned serialization, configuration, CRC32C, bloom filter. |
| `candybox-bookkeeper` | The `LedgerStore` abstraction over BookKeeper — the only module that touches the raw BookKeeper client — with an in-memory fake. |
| `candybox-coordination` | Membership, fenced leases, and CAS key-value over ZooKeeper, with an in-memory fake. |
| `candybox-lsm` | The LSM engine: memtable, WAL, SSTables, Syrup chunking, manifest, merge/read path, compaction. |
| `candybox-protocol` | The framed TCP wire protocol and transport. |
| `candybox-server` | The storage node: wires the engine behind the protocol, plus the runnable entrypoint, health/metrics, and ownership. |
| `candybox-client` | The thin client library and the `candybox` command-line tool. |
| `candybox-s3-gateway` | A path-style, S3-compatible HTTP gateway (Netty) with optional SigV4 auth + S3 ACL enforcement. Stateless. |
| `candybox-admin-api` | A stateless HTTP service exposing cluster / boxes / LSM / metrics as JSON, plus the static SPA mount. |
| `candybox-web` | React + TypeScript + MUI dashboard, packaged into a jar so the admin API serves it from the classpath. |
| `candybox-dist` | Packages the runnable distribution (`bin/ lib/ conf/`) and the Docker/Kubernetes assets. |
| `candybox-integration-tests` | End-to-end tests on embedded BookKeeper + ZooKeeper. |
Loading
Loading