← Back to list

Trino: The Query Engine Your Data Platform Needs (But Probably Doesn’t Have Yet)

A practical guide for engineers building dashboard platforms — from zero to running queries across multiple databases with one SQL…

Sadrealam Ahmed · 2026-06-23 06:13 · 0 claps · 6.8 min read
#trinos #sql #python #software-engineering #problem-solving
Open on Medium ↗
Wiki topics: 🎬 · Film & Television 🏃 · Running & Endurance

Trino: The Query Engine Your Data Platform Needs (But Probably Doesn’t Have Yet)

A practical guide for engineers building dashboard platforms — from zero to running queries across multiple databases with one SQL statement.

I recently went down a rabbit hole exploring Trino while building an internal dashboard platform. We were at a point where our platform could create datasets, wire up datasources, and render widgets — but every time we added a new database, the backend got messier. We needed something cleaner. That something was Trino.

Here’s everything I learned, explained as plainly as possible.

What is Trino, and why should you care?

Trino is a distributed SQL query engine. It does not store your data. It does not replace your database. It sits in front of all your databases and gives you one single SQL interface to query all of them — at the same time, in parallel, with standard ANSI SQL.

Think of it like this.

You have three libraries in your city. Library A organizes books in red shelves (Postgres). Library B uses blue shelves (MySQL). Library C is a massive warehouse (S3). Every time someone asks you “find all books about history after 2000,” you have to visit all three libraries separately, learn each one’s system, collect results, and combine them by hand.

Trino is the assistant who sits at the front desk, already knows all three libraries, and handles the entire thing for you. You just ask the question once.

-- One query. Three databases. Trino handles the rest.
SELECT o.order_id, c.customer_name, s.warehouse_location
FROM postgres_db.sales.orders o
JOIN mysql_db.crm.customers c ON o.customer_id = c.id
JOIN s3_lake.warehouse.stock s ON o.product_id = s.product_id;

The terminology you need to know

Trino doesn’t use the words “datasource” and “dataset” — but those concepts exist, just under different names:

Your term Trino term What it is Datasource Catalog A connection to a database or data system Dataset View A saved, named SQL query you reuse Database Schema A namespace containing tables

Once this mapping clicks, everything else makes sense.

How the architecture works

Trino has three components:

Coordinator — the brain. It receives your SQL, parses it, builds an execution plan, and distributes tasks to workers.

Workers — the muscle. They execute the actual data fetching and processing in parallel. More workers = more speed.

Connectors — the bridges. Each connector lets Trino talk to a specific data system: PostgreSQL, MySQL, S3, Kafka, MongoDB, and more. You configure one connector per data source, and Trino handles everything else.

When you run a query, here’s what happens under the hood:

Your SQL
  → Coordinator parses + plans
  → Splits work across workers in parallel
  → Each worker fetches its slice via a connector
  → Workers combine intermediate results
  → Coordinator returns the final result to you

The key thing to understand: Trino processes data in memory. It reads from your source, does the computation, and returns results. Nothing is stored in Trino itself.

Setting up your first Datasource (Catalog)

There are two ways to create a catalog — a config file or a SQL statement.

Config file approach (classic):

Create a file at etc/catalog/my_mysql.properties:

connector.name=mysql
connection-url=jdbc:mysql://localhost:3306
connection-user=root
connection-password=secret

That’s it. Restart Trino and the catalog is live.

SQL approach (Trino 435+):

CREATE CATALOG my_mysql USING mysql
WITH (
  "connection-url"      = 'jdbc:mysql://localhost:3306',
  "connection-user"     = 'root',
  "connection-password" = 'secret'
);

Once created, verify it works:

SHOW CATALOGS;
-- my_mysql
-- system
-- tpch

Exploring your data

With the catalog connected, navigate it exactly like a regular database:

-- List all databases inside MySQL
SHOW SCHEMAS FROM my_mysql;
-- List all tables in a specific database
SHOW TABLES FROM my_mysql.sales;
-- Inspect a table's structure
DESCRIBE my_mysql.sales.orders;

Creating a Dataset (Named View)

This is the part most people miss. Trino lets you save a SQL query as a named view — your “dataset” — that you or your application can call by name forever without rewriting the SQL.

-- Create a schema to store your views (do this once)
CREATE SCHEMA IF NOT EXISTS my_mysql.analytics;
-- Save a named query as a dataset
CREATE OR REPLACE VIEW my_mysql.analytics.revenue_by_region AS
SELECT
  region,
  DATE_TRUNC('month', order_date) AS month,
  SUM(total_amount)               AS revenue,
  COUNT(*)                        AS order_count
FROM my_mysql.sales.orders
GROUP BY 1, 2;

Now call it anytime, with filters, without touching the underlying SQL:

SELECT * FROM my_mysql.analytics.revenue_by_region
WHERE region = 'APAC'
ORDER BY month DESC;

You can also create materialized views for heavy aggregations you don’t need in real-time:

CREATE MATERIALIZED VIEW my_mysql.analytics.revenue_snapshot AS
SELECT region, SUM(total_amount) AS revenue
FROM my_mysql.sales.orders
GROUP BY region;
-- Refresh on demand
REFRESH MATERIALIZED VIEW my_mysql.analytics.revenue_snapshot;

Calling Trino from your application (REST API)

Trino exposes a REST API at /v1/statement. Every query goes through it — submit, then poll until done.

Step 1: Submit the query

curl -X POST http://your-trino-host:8080/v1/statement \
  -H "X-Trino-User: admin" \
  -H "Content-Type: text/plain" \
  -d "SELECT * FROM my_mysql.analytics.revenue_by_region LIMIT 10"

The response looks like this:

{
  "id": "20240101_123456_00001_abc",
  "nextUri": "http://trino:8080/v1/statement/executing/...",
  "stats": { "state": "QUEUED" }
}

Important: QUEUED is not an error. It's always the first response. You must poll nextUri to get results.

Step 2: Poll until FINISHED

Keep calling nextUri until there is no nextUri in the response. That's when your data arrives.

{
  "columns": [
    { "name": "region",  "type": "varchar" },
    { "name": "month",   "type": "timestamp" },
    { "name": "revenue", "type": "double" }
  ],
  "data": [
    ["APAC", "2024-01-01", 125000.00],
    ["EMEA", "2024-01-01", 98000.50]
  ],
  "stats": { "state": "FINISHED" }
}

Note that data is a 2D array — rows × columns, positionally ordered. You zip them into named objects yourself.

A complete Python client

Here’s a reusable Python client that handles polling automatically and returns clean named rows:

import requests
import time
TRINO_HOST = "http://your-trino-host:8080"
TRINO_USER = "admin"
def run_query(sql):
    headers = {
        "X-Trino-User": TRINO_USER,
        "Content-Type": "text/plain"
    }
    # Submit
    response = requests.post(f"{TRINO_HOST}/v1/statement", headers=headers, data=sql)
    result = response.json()
    columns, rows = [], []
    # Poll
    while result.get("nextUri"):
        time.sleep(0.5)
        result = requests.get(result["nextUri"],
                    headers={"X-Trino-User": TRINO_USER}).json()
        if result.get("columns"):
            columns = result["columns"]
        if result.get("data"):
            rows.extend(result["data"])
        if result.get("stats", {}).get("state") == "FAILED":
            raise Exception(result.get("error", {}).get("message"))
    # Zip into named dicts
    col_names = [c["name"] for c in columns]
    return [dict(zip(col_names, row)) for row in rows]
# Usage
results = run_query("SELECT * FROM my_mysql.analytics.revenue_by_region")
for row in results:
    print(row)
# {'region': 'APAC', 'month': '2024-01-01', 'revenue': 125000.0}

How this fits into a dashboard platform

Here’s the real-world pattern for a platform that auto-generates dashboards:

User: "Show me Q1 revenue by region"
  │
  Agent
  │  ├── 1. Create datasource → POST /api/datasource
  │  │         → Adapter runs: CREATE CATALOG my_mysql USING mysql WITH (...)
  │  │
  │  ├── 2. Create dataset → POST /api/dataset
  │  │         → Adapter runs: CREATE VIEW my_mysql.analytics.q1_revenue AS ...
  │  │
  │  ├── 3. Fetch data → GET /api/data?dataset=q1_revenue
  │  │         → Adapter calls Trino REST API
  │  │         → Polls nextUri until FINISHED
  │  │         → Returns [{ region: "APAC", revenue: 125000 }, ...]
  │  │
  │  └── 4. Render dashboard widget

The agent only knows your platform’s API surface. It never talks to Trino directly. The adapter owns the Trino integration — it translates your platform’s dataset/datasource model into Trino catalogs and views.

This separation is intentional. When you add a new database type tomorrow, you add one connector config to Trino. Your agent code doesn’t change. Your platform API doesn’t change. Only the adapter gets a small update if needed.

When does Trino actually help?

Be honest with yourself here. Trino is not always the right tool.

It helps when you have:

  • More than one database type to query
  • A need to join data across those databases in a single query
  • Large datasets where parallel execution gives you a real speed benefit
  • A growing number of data sources and you want the adapter code to stay simple

It adds complexity without much benefit when:

  • All your data lives in a single database
  • Your queries are simple and your data is small
  • You’re in early-stage development still figuring out your data model

The honest way to think about it: Trino doesn’t solve today’s problem, it prevents tomorrow’s mess. As your platform grows and more data sources are added, your application layer stays exactly the same. Only Trino grows.

The Trino Web UI

If you’re running Trino, open http://your-trino-host:8080/ui and you'll see a cluster overview dashboard. Here's what the metrics mean:

Running Queries — queries currently executing. If this is always high, you may need more workers.

Queued Queries — queries waiting to run. If this is non-zero for extended periods, your cluster is overloaded.

Blocked Queries — queries waiting for memory. If you see this, your workers need more RAM.

Active Workers — how many worker nodes are running. For production, you want at least 3.

Reserved Memory — memory currently in use. A non-zero value means a query is actively running.

Every query you run appears in the Query Details section. Click any query to see a detailed breakdown — which stage took how long, how many rows were scanned, where time was spent. This is invaluable for debugging slow dashboard queries.

Managing datasets via API — the full CRUD

# Create a dataset
run_query("""
    CREATE OR REPLACE VIEW my_mysql.analytics.my_dataset AS
    SELECT region, SUM(amount) AS total FROM my_mysql.sales.orders GROUP BY 1
""")
# Read (query) a dataset
run_query("SELECT * FROM my_mysql.analytics.my_dataset WHERE region = 'APAC'")
# Update a dataset (just CREATE OR REPLACE)
run_query("""
    CREATE OR REPLACE VIEW my_mysql.analytics.my_dataset AS
    SELECT region, product, SUM(amount) AS total
    FROM my_mysql.sales.orders GROUP BY 1, 2
""")
# List all datasets
run_query("SHOW VIEWS FROM my_mysql.analytics")
# Delete a dataset
run_query("DROP VIEW IF EXISTS my_mysql.analytics.my_dataset")

Wrapping up

Trino is one of those tools that feels like overkill until the day it suddenly isn’t. The moment you have two databases you need to join across, or the moment your adapter code starts looking like a spider web of database-specific connectors, Trino becomes the clean solution.

The mental model is simple: one catalog per datasource, one view per dataset, one REST endpoint for all queries. Your application speaks one language — standard SQL — and Trino figures out the rest.

If you’re building a dashboard platform that needs to scale across multiple data sources, Trino is worth setting up early. The investment in understanding it now pays off significantly when the data sources multiply.

Have questions or want to share how you’re using Trino in your stack? Drop a comment below.


메타데이터
post_id
14fb09d0b0dc
slug
trino-the-query-engine-your-data-platform-needs-but-probably-doesnt-have-yet-14fb09d0b0dc
url
https://medium.com/@sadrealam.ahmed/trino-the-query-engine-your-data-platform-needs-but-probably-doesnt-have-yet-14fb09d0b0dc
canonical_url
https://medium.com/@sadrealam.ahmed/trino-the-query-engine-your-data-platform-needs-but-probably-doesnt-have-yet-14fb09d0b0dc
author_url
https://medium.com/@sadrealam.ahmed
status
ok
fetched_at
2026-07-15 02:09:20