Using Tansu with Tigris on Fly
Tansu is an Apache Kafka® compatible stateless broker. Rather than replicating data, Tansu relies on the durability of its underlying storage. For example, Amazon S3 is designed to exceed 99.999999999% (11 nines) of durability. Tansu can use any S3 compatible or PostgreSQL database for storage. In this article we deploy Tansu on Fly using Tigris Data's S3 compatible storage. Using flycast we elastically scale up on demand and back to zero when quiescent. We do this without planning, reassigning and waiting to replicate data to other brokers. No waiting for Raft (or ZooKeeper) to reach... consensus about who is a leader or follower. All brokers are Spartacus.
All in about 40 lines of fly.toml configuration.
Firstly, download and install the fly command line with these instructions.
Create a new directory called fly-tigris-demo
and use fly launch
to clone the tansu-io/fly-tigris-demo template (note the --no-deploy
so that we can apply some tweaks first):
mkdir fly-tigris-demo
cd fly-tigris-demo
fly launch --from https://github.com/tansu-io/fly-tigris-demo --no-deploy
When asked Would you like to copy its configuration to the new app?
, hit Y
.
When asked Do you want to tweak these settings before proceeding?
, hit N
.
Output will look be something like:
Launching from git repo https://github.com/tansu-io/fly-tigris-demo
Cloning into '.'...
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (4/4), done.
An existing fly.toml file was found for app tansu
? Would you like to copy its configuration to the new app? Yes
Using build strategies '[the "ghcr.io/tansu-io/tansu:pr-165" docker image]'. Remove [build] from fly.toml to force a rescan
Creating app in /Users/bickle/tmp/fly-tigris-demo
We're about to launch your app on Fly.io. Here's what you're getting:
Organization: Travis Bickle (fly launch defaults to the personal org)
Name: tansu (from your fly.toml)
Region: London, United Kingdom (this is the fastest region for you)
App Machines: shared-cpu-1x, 256MB RAM (from your fly.toml)
Postgres: <none> (not requested)
Redis: <none> (not requested)
Tigris: <none> (not requested)
? Do you want to tweak these settings before proceeding? No
Created app 'tansu' in organization 'personal'
Admin URL: https://fly.io/apps/tansu
Hostname: tansu.fly.dev
Wrote config file fly.toml
Validating /Users/bickle/tmp/fly-tigris-demo/fly.toml
✓ Configuration is valid
Your app is ready! Deploy with `flyctl deploy`
Create a new S3 bucket on Tigris using:
fly storage create
Use the default name provided, or of your choice:
? Choose a name, use the default, or leave blank to generate one: tansu
Your Tigris project (tansu) is ready. See details and next steps with: https://fly.io/docs/reference/tigris/
Setting the following secrets on tansu:
AWS_ACCESS_KEY_ID: tid_YOUR_ACCESS_KEY_ID
AWS_ENDPOINT_URL_S3: https://fly.storage.tigris.dev
AWS_REGION: auto
AWS_SECRET_ACCESS_KEY: tsec_YOUR_SECRET_KEY_ID
BUCKET_NAME: tansu
Secrets are staged for the first deployment
You can verify the secrets that have been created with:
fly secrets list
The secrets AWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
, AWS_REGION
and BUCKET_NAME
are automatically used by Tansu:
NAME DIGEST CREATED AT
AWS_ACCESS_KEY_ID 8cbc2a5f5f704ae7 1m12s ago
AWS_ENDPOINT_URL_S3 85e8ac62d7de0c23 1m12s ago
AWS_REGION 274e16452b90854d 1m12s ago
AWS_SECRET_ACCESS_KEY 08469f68dfc80810 1m12s ago
BUCKET_NAME 057611985ecb3ae3 1m12s ago
Our fly.toml is
already setup to use these secrets to communicate with Tigris S3 using: STORAGE_ENGINE = "s3://${BUCKET_NAME}"
:
[env]
RUST_LOG = 'warn,tansu_server=debug,tansu_storage=debug,tansu_schema_registry=debug'
AWS_ENDPOINT = "https://fly.storage.tigris.dev"
ADVERTISED_LISTENER_URL = "tcp://${FLY_APP_NAME}.flycast:9092/"
CLUSTER_ID = "tansu-fly-tigris"
STORAGE_ENGINE = "s3://${BUCKET_NAME}"
The ${FLY_APP_NAME}
is part of the environment
that all fly machines have. We advertise our address as tcp://${FLY_APP_NAME}.flycast:9092/
.
This is a flycast private IPv6 address that we allocate
to connect with the Tansu brokers using:
fly ips allocate-v6 --private
Finally, deploy the application onto fly with:
fly deploy
Start an interactive shell using the Apache Kafka® Java client to connect to Tansu:
fly machine run --shell apache/kafka:3.9.0
Create a test topic, note that our bootstrap-server
is ${FLY_APP_NAME}.flycast:9092
using the
flycast address allocated earlier:
/opt/kafka/bin/kafka-topics.sh \
--bootstrap-server ${FLY_APP_NAME}.flycast:9092 \
--partitions=3 \
--replication-factor=1 \
--create \
--topic test
A demo, isn't a demo without a Hello World!
. Lets produce a message:
echo "hello world" | \
/opt/kafka/bin/kafka-console-producer.sh \
--bootstrap-server ${FLY_APP_NAME}.flycast:9092 \
--topic test
Fetch a message, using a consumer group, expect a short delay while the group is formed:
/opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server ${FLY_APP_NAME}.flycast:9092 \
--consumer-property fetch.max.wait.ms=15000 \
--group test-consumer-group \
--topic test \
--from-beginning \
--property print.timestamp=true \
--property print.key=true \
--property print.offset=true \
--property print.partition=true \
--property print.headers=true \
--property print.value=true
The consumer should output:
CreateTime:SOME_TIME Partition:1 Offset:0 NO_HEADERS null hello world
Hit ^C
a couple of times to exit the consumer.
If you wait a minute or so, the Tansu brokers will scaling to zero shutting down automatically. The brokers are stateless, without the overhead of distributed consensus. All data is persisted in S3 using optimistic concurrency control, made possible with conditional write support, offered by most S3 vendors.
You can verify whether the brokers are still running using:
fly machines ls
Checking that they're all stopped
. If you now run:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ${FLY_APP_NAME}.flycast:9092 --list
A broker will automatically restart to handle this request scaling back up from zero.
There is a short delay while waiting
for the broker to become ready. In environments where such a delay isn't acceptable, scaling
to one may be appropriate (using fly scale
). Brokers can also run in multiple regions without
overhead because they are stateless.
Tansu uses lightweight from scratch super minimal docker images containing only a static binary (plus some certificates because... SSL!). Our footprint is a few megabytes rather than the hundreds commonly found in docker images. First byte response times start with a lightweight container that can be deployed to a new fly machine as we scale up on demand. Tansu is written in Rust which doesn't have the startup overhead of a language VM runtime or JIT compiler needing to warm up. All of which reminds me of Grace Hopper's nanoseconds:

Licensed under the GNU AGPL, Tansu is written in 100% safe 🦺 async 🚀 Rust 🦀 and is available on GitHub.