Few things in modern municipal living are as quietly frustrating as carrying two giant tote bags of rinsed plastic bottles and aluminum cans through Singapore's midday heat to your neighborhood supermarket, only to be greeted by a glowing red screen:
Machine Status: Bin Full. Please try again later.
Singapore’s Beverage Container Return Scheme (BCRS) is a fantastic sustainability initiative. Across the island, Reverse Vending Machines (RVMs) accept beverage containers and return deposits. But like any physical infrastructure distributed across hundreds of residential hubs, MRT stations, and supermarkets, real-world machines fill up, experience mechanical jams, or undergo routine maintenance.
As a Site Reliability Engineer, I don't like guessing whether a distributed system is healthy before interacting with it. If a network node has a status, it should have telemetry.
So I built BCRSTracking.
The Upstream API
When inspecting how the official tracking system works (bts.bcrs.sg), it turns out the platform exposes public API endpoints under /forapi/v2/. These endpoints provide:
- Location Listings (
/locations): A directory of all deployed RVM stations across the island, including operating hours, physical coordinates, and machine identifiers. - Bin Status & Telemetry (
/bin-status): Real-time machine operational status, current bin capacity percentage, and hardware availability.
While the data was publicly accessible, there was no historical trend tracking, no differential event stream, and no way to query capacity curves over time to see when specific machines are emptied or filled.
Engineering Requirements
When architecting the tracker, I wanted it to be lightweight, resilient, and polite:
- Polite Upstream Concurrency: Never overwhelm public services. Polling must use strict token-bucket rate limiting (
golang.org/x/time/rate), bounded worker pools, and exponential backoff with jitter on HTTP 429 or 5xx responses. - Differential State Engine: Instead of blindly dumping snapshots, compute state transitions in memory (
MACHINE_ADDED,STATUS_CHANGED,CAPACITY_THRESHOLD_CROSSED) so downstream systems can alert on state changes. - Fast Columnar Analytics: Store telemetry snapshots in ClickHouse using compressed columnar engines (
ReplacingMergeTreefor current state,MergeTreewith TTL for historical bin levels). - Native Observability: Expose Prometheus metrics for scrapers, queue depth, upstream latency, and error rates, scraped by VictoriaMetrics.
System Architecture
The resulting service is written in Go 1.27 and runs as a lightweight container in Kubernetes:
┌───────────────────────────────────────────────┐
│ Upstream BCRS Platform │
│ (bts.bcrs.sg/forapi/v2) │
└──────────────────────┬────────────────────────┘
│
│ Rate-Limited HTTP/2 (Token Bucket)
│ Exponential Jittered Backoff
▼
┌───────────────────────────────────────────────┐
│ bcrs-tracker │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Location Poller & Diff Engine │ │
│ └────────────────────┬────────────────────┘ │
│ │ Active RVM IDs │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Bin Worker Pool (Bounded Concurrency) │ │
│ └────────────────────┬────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Batch Writer (500 rows / 5s flush) │ │
│ └────────────────────┬────────────────────┘ │
│ │ │
└───────────────────────┼───────────────────────┘
│ Native Batch Insert (LZ4)
▼
┌───────────────────────────────────────────────┐
│ ClickHouse Database │
│ │
│ • rvm_current (ReplacingMergeTree) │
│ • rvm_events (MergeTree) │
│ • rvm_bin_snapshots (MergeTree + 2yr TTL) │
└───────────────────────────────────────────────┘
▲
│ SQL / PromQL
│
┌───────────────────────┴───────────────────────┐
│ VictoriaMetrics & Grafana Dashboards │
└───────────────────────────────────────────────┘
What the Telemetry Reveals
Having continuous time-series telemetry on reverse vending machines across Singapore reveals fascinating patterns:
- The Weekend Surge: Neighborhood machines near residential housing estates typically reach 80–100% capacity on Saturday and Sunday between 2:00 PM and 6:00 PM.
- Clearance Cadence: Logistics crews typically clear bins during early morning runs between 7:00 AM and 9:30 AM on weekdays.
- Pre-Trip Confirmation: Instead of walking down hoping for the best, I can query my nearest machine via Prometheus/Grafana or a quick API curl to confirm it is operational and has available bin capacity.
Open Source
The entire tracker—including the Go poller, ClickHouse migrations, and Helm charts—is open-source on GitHub:
- Repository: github.com/TheDJVG/BCRSTracking
If you're in Singapore and interested in tracking local smart-city infrastructure, feel free to check out the repository, submit PRs, or send me feedback at [email protected].