Quickstart
Run the local demo and open your first message timeline. About 10 minutes, no AWS account.
Run Trailbeam on your laptop against an SQS emulator, then open one message's timeline. No AWS account or credentials are used.
Before you start#
You need:
- git
- Docker with Compose v2: Docker Desktop on macOS or Windows, or Docker Engine with the compose plugin on Linux
- About 2 GB of free disk for the images (about 1.5 GB once built)
- Access to
nick-lennox/trailbeamon GitHub (the repository is private for now) and the GitHub CLI, logged in withgh auth login
Check the tools:
git --version
docker compose version # v2.x or later
docker info # look for a "Server Version:" linedocker info prints a Server: heading even when the daemon is down, so look for Server Version:. If it isn't there, start Docker first.
1Step 1: Get the code#
gh repo clone nick-lennox/trailbeam
cd trailbeamA plain git clone https://github.com/nick-lennox/trailbeam fails with could not read Username unless git already has credentials for an account with access.
2Step 2: Start the demo#
docker compose -f demo/compose.yaml up -d --build --waitThe first run downloads the base images and builds four images, which takes 1 to 5 minutes. The command returns when every container is running. Run docker compose -f demo/compose.yaml ps and you should see 7 containers, all Up:
| Service | What it does |
|---|---|
sqs |
ElasticMQ, an SQS emulator, with queue orders and its dead-letter queue orders-dlq |
producer |
Python with the boto3 hooks. Sends one message a second to orders |
consumer-python |
Four consumer threads on orders, and one thread that receives and deletes from orders-dlq |
consumer-node |
Node.js with @trailbeam/sdk. Four concurrent consumers on orders |
deleter |
A separate Python process that deletes messages the consumers hand to it, by receipt handle |
collector |
The OpenTelemetry Collector with collector/trailbeam-collector.yaml |
trailbeam |
Trailbeam and web screen on 127.0.0.1:4318. Its status reads (healthy) |
orders has a 10 s visibility timeout and a redrive policy to orders-dlq with a maxReceiveCount of 3.
3Step 3: Open a timeline#
Wait about a minute, then open http://127.0.0.1:4318/ and click any message ID.
The timeline shows when the message was sent, each delivery and its consumer, and the delete. A message that a slow consumer held too long looks like this:
- 12:00:01.204Sentproducer
- 12:00:01.230Receivedconsumer-python · receive count 1
- inferredVisibility timeout expiredno delete within the 10 s timeout
- 12:00:11.412Receivedconsumer-node · receive count 2
- 12:00:11.540Deleteconsumer-node · accepted
- 12:00:14.251Deleteconsumer-python · failed, 13 s after its receive
Each recorded step shows an interval, from just before its SQS request was sent to when the response came back. SQS acted somewhere inside it.
A step is drawn above another only when an observed fact proves it came first, and the page names that fact under the step:
| Source | The page says |
|---|---|
clock |
A's interval ended before B's started |
process_order |
In one process, A's call returned before B's call was issued |
receive_count |
Two receives on one queue, and SQS gave A the lower receive count |
sqs_timestamps |
The send and the first receive, and SQS's SentTimestamp is earlier than its ApproximateFirstReceiveTimestamp |
Two markings to know:
- Dashed border with an
inferredbadge: Trailbeam worked this step out; nothing recorded it. It shows a time window and a reason instead of a time. See Recorded and inferred steps. - Dashed box with a
concurrentbadge, "concurrent, order unknown": no fact orders these steps. Inside the box each step is a bar on one time scale, sorted by request start, which is not necessarily the order SQS acted in. Expect it around a send and its first receive: an idle consumer was already waiting in a long poll, and ElasticMQ stamps both SQS timestamps in the same millisecond. See Reading a timeline.
A receive from a Lambda wrapper shows only "no later than" one time, because the wrapper sees the event after Lambda received the message.
4Step 4: Explore the demo scenarios#
The demo produces each of these on a schedule:
| Look for | What happened | Guide |
|---|---|---|
Deliveries 2 (1 ok), first and last seen about equal |
The consumer set the visibility timeout to 0 on delivery 1, the timeout expired, and delivery 2 was deleted | Visibility timeouts |
Deliveries 2 (1 ok), last seen about 13 s after first seen |
A slow consumer ran past the 10 s visibility timeout. Delivery 2 went to another consumer, which deleted it. The first consumer's late delete then failed with ReceiptHandleIsInvalid, because ElasticMQ rejects a stale handle |
Visibility timeouts |
Deliveries 4 (1 ok), after about a minute |
A poison message: three receives on orders, a move to orders-dlq reported by SQS, then a receive and a delete there |
Dead-letter queue |
| /duplicates | Messages delivered more than once | Duplicate processing |
| /stuck | Messages with no final delete after 60 s (the demo sets TRAILBEAM_STUCK_AFTER_S to 60) |
Stuck messages |
Every message page shows the notice for the published default receipt-hash key. That's expected in the demo. To run it with your own key:
TRAILBEAM_RECEIPT_HASH_KEY=$(openssl rand -hex 32) docker compose -f demo/compose.yaml up -d --build --waitThe same data as JSON#
port=${TRAILBEAM_DEMO_PORT:-4318}
id=$(curl -s "http://127.0.0.1:$port/api/messages?limit=1" | python3 -c 'import json,sys; print(json.load(sys.stdin)["messages"][0]["message_id"])')
curl -s "http://127.0.0.1:$port/api/messages/$id" | python3 -m json.tool
curl -s "http://127.0.0.1:$port/api/health" # "status": "ok", span counts, "max_receive_counts": {"orders": 3}The fields are in the HTTP API.
5Step 5: Stop the demo#
docker compose -f demo/compose.yaml down # keeps the timelines, on the volume trailbeam-demo_trailbeam-data
docker compose -f demo/compose.yaml down -v # deletes them tooNext steps#
- How Trailbeam works
- Add Trailbeam to your services
- Reading a timeline
- Something went wrong? See Troubleshooting.
Message
Amazon SQS terms in Vocabulary
Timeline
Queue
Amazon SQS terms in Vocabulary
Dead-letter queue (DLQ)
Amazon SQS terms in Vocabulary
Hooks
trailbeam.boto.install()), the AWS SDK v3 middleware (trailbeam.instrument(client)) and the Lambda wrapper.Consumer
ReceiveMessage) and then deletes them. A Lambda function behind an event source mapping is a consumer too.Amazon SQS terms in Vocabulary
Receipt handle
Amazon SQS terms in Vocabulary
Collector
OpenTelemetry terms in Vocabulary
Visibility timeout
Amazon SQS terms in Vocabulary
Redrive policy
maxReceiveCount.Amazon SQS terms in Vocabulary
maxReceiveCount
Amazon SQS terms in Vocabulary
Message ID
MessageId, assigned by SQS when the message is sent. It stays the same on every receive, including a receive from a dead-letter queue. Trailbeam joins everything about a message by this ID.Amazon SQS terms in Vocabulary
Delivery
Delete
DeleteMessage, made with a receipt handle. This is the only way a consumer removes a message. With the handle of an older receive, the call can still succeed while the message is received again.Amazon SQS terms in Vocabulary
Recorded (observed)
inferred: false.Step
Receive count
ApproximateReceiveCount: how many times SQS has returned the message. It goes up by one on every receive.Amazon SQS terms in Vocabulary
Receive
ReceiveMessage call returning a message. A receive does not remove the message; it starts its visibility timeout.Amazon SQS terms in Vocabulary
SentTimestamp
Amazon SQS terms in Vocabulary
ApproximateFirstReceiveTimestamp
Amazon SQS terms in Vocabulary
Long polling
ReceiveMessage with WaitTimeSeconds above 0, which waits up to that long for a message instead of returning empty at once.Amazon SQS terms in Vocabulary
Lambda wrapper
trailbeam.aws_lambda.wrap (Python) or trailbeam.wrapHandler (Node.js), around a handler invoked by an event source mapping.Final delete
Receipt-hash key
TRAILBEAM_RECEIPT_HASH_KEY. Every service on the same queues must use the same key.