Skip to content

Storage backends ​

Moʻolelo's durable record store sits on object storage. You bring the bucket; Moʻolelo writes Apache Iceberg tables into it.

Supported backends ​

BackendURI schemeCommon deployments
AWS S3s3://, s3a://, s3n://Production AWS workloads
S3-compatible (MinIO, Ceph, R2, Backblaze)s3://On-prem, hybrid, multi-cloud
Google Cloud Storagegs://Production GCP workloads
Azure ADLS Gen2abfss://Production Azure workloads
Azure Blob Storagewasbs://Legacy Azure workloads (prefer ADLS Gen2 for new deployments)
Local filesystemfile://Development only

Switching between backends is a configuration change, not a code change. The same Moʻolelo binaries work with all of them.

Choosing a region ​

Pick a storage region in the same geographic area as your Moʻolelo deployment. Cross-region writes are slower and more expensive than same-region writes, and the iceberg-sink commits batches every few seconds — a slow round-trip directly affects ingest latency.

For multi-region deployments, each Moʻolelo region writes to its own bucket (typically named moolelo-warehouse-eu-central, moolelo-warehouse-us-east, etc.). Cross-region search is supported; cross-region writes are not (by design — see Data residency).

Configuring the iceberg-sink ​

The iceberg-sink reads two configuration values:

  • ICEBERG_SINK_WAREHOUSE — the warehouse URI; the scheme picks the backend.
  • ICEBERG_SINK_CATALOG_PROPS_JSON — a JSON object of backend-specific credentials and endpoint settings.

In Helm, these come from chart values:

yaml
warehouse: s3://your-bucket/moolelo-warehouse/
catalogProperties:
  literal: |
    {
      "s3.region": "us-east-1",
      "s3.access-key-id": "AKIA…",
      "s3.secret-access-key": "…"
    }
yaml
# Best practice: use IAM Roles for Service Accounts; no static keys.
warehouse: s3://your-bucket/moolelo-warehouse/
catalogProperties:
  literal: |
    {
      "s3.region": "us-east-1"
    }
# The pod's ServiceAccount has the IAM role attached;
# AWS SDK picks up credentials from the projected token.
yaml
warehouse: s3://moolelo-warehouse/
catalogProperties:
  literal: |
    {
      "s3.endpoint": "https://minio.your-domain.com",
      "s3.region": "us-east-1",
      "s3.access-key-id": "moolelo",
      "s3.secret-access-key": "your-minio-password",
      "s3.path-style-access": "true"
    }
yaml
warehouse: gs://your-bucket/moolelo-warehouse/
catalogProperties:
  literal: |
    {
      "gcs.credtype": "adc"
    }
# The pod's KSA is bound to a GCP service account with the
# right Storage permissions; ADC picks it up from the projected token.
yaml
warehouse: gs://your-bucket/moolelo-warehouse/
catalogProperties:
  literal: |
    {
      "gcs.keypath": "/var/run/secrets/gcp/sa.json"
    }
# Mount the service-account JSON via a Kubernetes Secret + projected volume.
yaml
warehouse: abfss://moolelo-warehouse@youraccount.dfs.core.windows.net/
catalogProperties:
  literal: |
    {
      "adls.auth.shared-key.account.name": "youraccount",
      "adls.auth.shared-key.account.key": "your-adls-key",
      "adls.endpoint": "youraccount.dfs.core.windows.net",
      "adls.protocol": "https"
    }

For production, mount the catalog properties JSON from a Kubernetes Secret rather than embedding it in the values file:

yaml
catalogProperties:
  secretRef:
    name: iceberg-catalog-credentials
    key: props.json

The Secret's props.json key contains the JSON payload. With External Secrets Operator + Vault, the JSON gets refreshed automatically.

Configuring Nessie's catalog ​

Nessie also writes to the same warehouse — it manages the table metadata that lives alongside the Parquet files. Nessie's environment variables follow the same shape as Moʻolelo's, just with the prefix NESSIE_CATALOG_SERVICE_<BACKEND>_*. See the Project Nessie docs for the per-backend key list.

Bucket layout ​

Once Moʻolelo starts writing, your bucket will look like this:

text
moolelo-warehouse/
└── moolelo/
    ├── chunks/
    │   ├── data/<parquet files>
    │   └── metadata/<json snapshot files>
    ├── entities/
    │   ├── data/...
    │   └── metadata/...
    ├── relations/
    └── acl_authority/

The moolelo segment is the catalog namespace (configurable via ICEBERG_SINK_NAMESPACE). Each table holds its own data and metadata directories — standard Apache Iceberg layout.

Bucket policies ​

The Moʻolelo deployment needs:

  • Read-write on the warehouse path (e.g. moolelo-warehouse/moolelo/*).
  • Read on the bucket itself (for object listing).

A minimal IAM policy on AWS:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "MoʻoleloWarehouseAccess",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::your-bucket",
        "arn:aws:s3:::your-bucket/moolelo-warehouse/*"
      ]
    }
  ]
}

For GCS, grant the Storage Object Admin role on the bucket. For ADLS, the Storage Blob Data Contributor role.

Lifecycle policies ​

Iceberg snapshots accumulate over time. A reasonable default lifecycle policy on your bucket:

  • Keep all current snapshots indefinitely.
  • Delete orphaned files (files no longer referenced by any snapshot) older than 30 days.

Moʻolelo doesn't currently run snapshot expiration in-platform — that's on the roadmap. Until it ships, run expire_snapshots periodically via Trino or Spark against the catalog. The Apache Iceberg maintenance docs cover the procedure.

Encryption at rest ​

Use the bucket's native server-side encryption:

  • AWS S3: SSE-S3 (default), SSE-KMS, or SSE-C.
  • GCS: customer-managed encryption keys (CMEK).
  • ADLS: customer-managed keys (CMK).

Moʻolelo doesn't add a layer of application-level encryption on top — the regional vault handles encryption of personally-identifiable fields, and the bucket-level encryption handles the rest.

Moʻolelo — self-hosted federated enterprise knowledge platform.