Definition
- SQL hint – An inline annotation written in
/*+ ... */comment syntax that overrides behavior for a single query. - OPTIONS hint – Placed after a table name to override connector options like Kafka startup mode, scan parallelism, or flush settings.
- LOOKUP hint – Tunes lookup-join behavior by enabling async lookups, setting buffer capacity, and configuring retry on miss.
- Governance gate – OPTIONS requires
table.dynamic-table-options.enabled = truebecause it can change security-relevant connector behavior. - Per-query tuning – Hints separate per-query overrides from the shared DDL, so one team tunes without disturbing others.
The National Bank of India has one shared definition of the transactions table that every team uses. Normally everyone reads from the latest offset. But one afternoon the fraud team needs to replay from the beginning of the day. Instead of editing the shared table definition, they write a sticky note on their own query, a hint, that says "for this query only, start from the earliest offset." It changes how that single query is handled without rewriting the bank's master rulebook.
Understanding the Concepts
What is a Flink SQL hint?
A hint is an inline annotation in the SQL comment form /*+ ... */ placed immediately after the thing it applies to. It overrides behavior for a single query without changing the table's DDL.
-- OPTIONS hint: override Kafka startup mode for this query only
SELECT *
FROM transactions /*+ OPTIONS('scan.startup.mode'='earliest-offset') */
WHERE amount > 1000000;
-- All other consumers of 'transactions' are unaffected
How does the OPTIONS hint work?
The OPTIONS hint goes after a table reference in the FROM clause and temporarily overrides the connector options declared in the DDL.
-- Replay from a specific timestamp for backfill
SELECT account_id, SUM(amount) AS total
FROM transactions /*+ OPTIONS(
'scan.startup.mode'='timestamp',
'scan.startup.timestamp-millis'='1704067200000'
) */
GROUP BY account_id;
-- Replays from Jan 1, 2024 00:00:00 UTC
-- Other jobs reading 'transactions' stay at latest-offset
package com.bank.flink;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.configuration.Configuration;
public class OptionsHintDemo {
public static void main(String[] args) {
Configuration config = new Configuration();
// Must enable dynamic table options for hints to work
config.setBoolean("table.dynamic-table-options.enabled", true);
TableEnvironment tEnv = TableEnvironment.create(
EnvironmentSettings.newInstance()
.inStreamingMode()
.withConfiguration(config)
.build());
// Use hint to replay from earliest offset
tEnv.executeSql(
"SELECT * FROM transactions " +
"/*+ OPTIONS('scan.startup.mode'='earliest-offset') */ " +
"WHERE amount > 1000000"
);
}
}
Why is the OPTIONS hint gated behind a configuration flag?
Because OPTIONS can change security-relevant connector behavior, Flink requires explicit enablement. In a locked-down production environment, you may deliberately leave it disabled.
// Enable OPTIONS hints (required)
Configuration config = new Configuration();
config.setBoolean("table.dynamic-table-options.enabled", true);
// Without this flag, using OPTIONS hint produces an error:
// "Dynamic table options is not allowed"
How does the LOOKUP hint improve throughput?
The LOOKUP hint controls how a lookup join against a dimension table behaves. Its most important use is enabling async lookups, which turns blocking one-at-a-time external lookups into many concurrent in-flight lookups.
-- Lookup join with async hint for enrichment
SELECT t.txn_id, t.amount, a.risk_score
FROM transactions t
JOIN account_risk /*+ LOOKUP(
'table'='account_risk',
'async'='true',
'output-mode'='allow_unordered',
'capacity'='100'
) */ FOR SYSTEM_TIME AS OF t.proc_time AS a
ON t.account_id = a.account_id;
-- Without async: 1 lookup at a time → 100 lookups/sec
-- With async: 100 concurrent lookups → 10,000 lookups/sec
package com.bank.flink;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;
public class LookupHintDemo {
public static void main(String[] args) {
TableEnvironment tEnv = TableEnvironment.create(
EnvironmentSettings.inStreamingMode());
// Async lookup join for fraud enrichment
tEnv.executeSql(
"SELECT t.txn_id, t.amount, r.risk_score " +
"FROM transactions t " +
"JOIN risk_scores /*+ LOOKUP('table'='risk_scores', " +
" 'async'='true', 'capacity'='200') */ " +
" FOR SYSTEM_TIME AS OF t.proc_time AS r " +
" ON t.account_id = r.account_id"
);
}
}
How do LOOKUP retry hints work?
Retry directives handle the race when a dimension row has not been written yet. The lookup retries with a delay until it finds the row.
-- Retry on lookup miss: dimension row may not exist yet
SELECT t.txn_id, a.holder_name
FROM transactions t
JOIN accounts /*+ LOOKUP(
'table'='accounts',
'retry-predicate'='lookup_miss',
'retry-strategy'='fixed_delay',
'fixed-delay'='1s',
'max-attempts'='3'
) */ FOR SYSTEM_TIME AS OF t.proc_time AS a
ON t.account_id = a.account_id;
-- If account not found, retries 3 times with 1s delay
How do OPTIONS and LOOKUP compare?
| Hint | Applies To | Purpose | Common Overrides |
|---|---|---|---|
| OPTIONS | Table in FROM clause | Override connector options | scan.startup.mode, parallelism |
| LOOKUP | Lookup join dimension | Tune enrichment behavior | async, capacity, retry |
What are other hint types?
Flink also supports hints for state time-to-live and join strategies in some versions.
-- State TTL hint (per-query override)
SELECT account_id, COUNT(*) AS txn_count
FROM transactions /*+ OPTIONS('table.exec.state.ttl'='3600000') */
GROUP BY account_id;
-- State TTL = 1 hour for this query only
What should a senior engineer know?
- Hints are surgical: they let one team tune one query without a DDL change that would affect every consumer.
- The async LOOKUP hint is often the single most effective throughput fix for enrichment pipelines.
- Synchronous external lookups serialize the whole operator; async keeps many requests in flight.
- In locked-down environments, disable OPTIONS hints and require DDL changes to go through a review process.
- Always test hint overrides in a development environment before applying to production.
// Senior tip: measure throughput before and after async LOOKUP
// Before: synchronous → 100 records/sec (blocked by external DB latency)
// After: async, capacity=200 → 15,000 records/sec
// The improvement is proportional to the external lookup latency
Visual Overview
OPTIONS Hint – Per-Query Override
Shared DDL: Query with hint:
┌──────────────────────┐ ┌──────────────────────────────┐
│ CREATE TABLE txns │ │ SELECT * FROM txns │
│ WITH ( │ │ /*+ OPTIONS( │
│ scan.startup.mode │ │ 'scan.startup.mode' = │
│ = 'latest-offset' │ │ 'earliest-offset' │
│ ) │ │ ) */ │
└──────────────────────┘ │ WHERE amount > 1000000; │
Used by all jobs └──────────────────────────────┘
Used by ONE query only
Sync vs Async Lookup Join
Synchronous (default): Async (with LOOKUP hint):
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ Event 1│───▶│ DB │ │ Event 1│───▶│ DB │
│ │ │ lookup │ │ Event 2│───▶│ lookup │
│ wait...│ │ ... │ │ Event 3│───▶│ ... │
│ Event 2│───▶│ DB │ │ Event 4│───▶│ ... │
│ │ │ lookup │ │ │ │ ... │
│ wait...│ │ ... │ │(results│ │(results│
└────────┘ └────────┘ │ arrive)│ │ arrive)│
1 at a time = slow └────────┘ └────────┘
Many in flight = fast
Key Takeaway
Flink SQL hints are inline annotations that attach per-query directives to a table or join without touching the shared DDL. The OPTIONS hint overrides connector options like Kafka startup mode for a single query, gated behind an explicit configuration flag for security. The LOOKUP hint tunes lookup-join behavior, and enabling async lookups is often the single most effective throughput improvement for enrichment pipelines.
Hints cleanly separate per-query tuning from the shared schema. One team can replay from the earliest offset for a backfill without editing the DDL that all other teams depend on. The LOOKUP async hint turns blocking one-at-a-time external lookups into many concurrent in-flight lookups, dramatically improving throughput.
At the National Bank of India, when the fraud team needed to replay a whole day, they added an OPTIONS hint setting startup mode to earliest on just their query without disturbing other jobs. Our enrichment pipeline was throttled by synchronous lookups until we added the LOOKUP async hint with a buffer capacity of 200, which improved throughput by roughly an order of magnitude.
Remember one sentence: use OPTIONS hints to override connector settings per query without touching shared DDL, and use LOOKUP async hints to turn blocking enrichment lookups into concurrent requests for dramatically better throughput.
---