04 · Data & AI Services (watsonx, Db2 Warehouse)¶
Every database module so far used Databases for PostgreSQL — transactional, row-oriented. This module adds two different workload shapes: Db2 Warehouse for analytical/columnar queries at scale, and watsonx.ai for foundation-model inference wired into an application.
Provision Db2 Warehouse¶
ibmcloud resource service-instance-create orders-analytics \
dashdb-for-transactions performance us-south \
--parameters '{"num_cpu": 4, "storage_gb": 500}'
performance plan is compute-elastic; use it for analytical workloads
whose query load spikes rather than staying flat, unlike the steady
transactional load Databases for PostgreSQL was sized for in Level 1.
Load data from the transactional database¶
Analytical warehouses work best fed by an ETL pipeline, not queried directly against a live OLTP database (Level 3's Databases for PostgreSQL instances weren't sized for large scans):
-- Db2 Warehouse: external table over a Cloud Object Storage export
CREATE EXTERNAL TABLE orders_export (
order_id VARCHAR(32),
customer_id VARCHAR(32),
total_cents INT,
created_at TIMESTAMP
)
USING (
DATAOBJECT 's3://orders-archive-mastery/exports/orders/'
FORMAT 'parquet'
);
CREATE TABLE orders_fact AS
SELECT * FROM orders_export;
Exporting periodically to the orders-archive-mastery Cloud Object
Storage bucket (already built in Level 3's DR module) as Parquet, then
loading via external table, keeps the transactional database's I/O
budget for transactions, not analytics.
Provision watsonx.ai¶
ibmcloud resource service-instance-create watsonx-mastery \
pm-20 lite us-south --resource-group-name mastery-path
Call a foundation model from application code¶
from ibm_watsonx_ai import APIClient, Credentials
from ibm_watsonx_ai.foundation_models import ModelInference
creds = Credentials(
url="https://us-south.ml.cloud.ibm.com",
api_key=os.environ["WATSONX_API_KEY"],
)
client = APIClient(creds)
client.set.default_project(os.environ["WATSONX_PROJECT_ID"])
model = ModelInference(
model_id="ibm/granite-13b-instruct-v2",
api_client=client,
params={"decoding_method": "greedy", "max_new_tokens": 200},
)
response = model.generate_text(
prompt=f"Summarize this order's shipping status in one sentence: {order_json}"
)
print(response)
"Order ord_1029 shipped from the us-south fulfillment center on 2026-08-24 and is expected to arrive within 3 business days."
Ground responses with your own data (RAG pattern)¶
from ibm_watsonx_ai.foundation_models import ModelInference
# Embed and store order-policy documents once (offline step)
# ... vector store setup omitted — any COS-backed vector DB works ...
def answer_policy_question(question: str, retrieved_chunks: list[str]) -> str:
context = "\n".join(retrieved_chunks)
prompt = f"""Using only the context below, answer the question.
Context:
{context}
Question: {question}
Answer:"""
return model.generate_text(prompt=prompt)
Retrieval-augmented generation matters here for the same reason API Connect's gateway policies mattered in Level 3: it constrains what the model is allowed to say to information your own system provided, substantially reducing (never eliminating) fabricated answers about things like return policy specifics.
Governance: watsonx.governance for tracking model behavior¶
Wire a deployed model's inference calls to log through watsonx.governance to track drift (are responses trending away from historical baseline) and fairness metrics over time — the AI-specific analogue of Activity Tracker and SCC: it isn't enough to deploy a model once, its behavior needs ongoing monitoring the same way infrastructure posture does.
Terraform for the service instances¶
resource "ibm_resource_instance" "db2_warehouse" {
name = "orders-analytics"
service = "dashdb-for-transactions"
plan = "performance"
location = "us-south"
resource_group_id = data.ibm_resource_group.mastery_path.id
}
resource "ibm_resource_instance" "watsonx" {
name = "watsonx-mastery"
service = "pm-20"
plan = "lite"
location = "us-south"
resource_group_id = data.ibm_resource_group.mastery_path.id
}
Gotchas¶
- Don't point BI dashboards at the OLTP database — the temptation to
skip the warehouse and query
orders-dbdirectly for reports reintroduces the exact contention problem Db2 Warehouse exists to avoid; keep the ETL boundary even when it's tempting to skip for a "quick" report. - Foundation model token limits and cost scale with prompt length, including retrieved RAG context — an unbounded number of retrieved chunks stuffed into a prompt is a silent cost and latency problem, not just an accuracy one; cap retrieved context deliberately.
liteplans on watsonx have low request-per-minute caps — fine for this module's exercises, will visibly throttle under any real application load; check plan limits before assuming a production workload can run on the free tier.- Model outputs are not deterministic even with
greedydecoding across model version updates — pin a specific model version (ibm/granite-13b-instruct-v2, not an unpinned "latest" alias) for anything where consistent output matters, such as automated report generation.
How It Actually Works¶
- Db2 Warehouse gets its analytical speed from a columnar, MPP (massively
parallel processing) storage engine rather than the row-store B-tree
indexing an OLTP engine like PostgreSQL uses. Data is physically
stored column-by-column and distributed across worker nodes, so a query
like
SUM(total_cents)reads only thetotal_centscolumn's compressed blocks across all nodes in parallel instead of scanning entire rows one at a time — exactly backwards from what makes a transactional engine fast at single-row inserts and lookups, which is the real reason the two workload shapes don't share one database well. - The external-table/Parquet loading pattern works because Db2
Warehouse's storage engine can read columnar Parquet files directly off
Cloud Object Storage without a separate import step —
CREATE EXTERNAL TABLEregisters metadata pointing at the S3-compatible COS path, andCREATE TABLE ... AS SELECTtriggers a parallel read-and-load into the warehouse's own storage. That's why the export format matters: Parquet already carries columnar layout and compression the warehouse's engine reads efficiently, versus a row-oriented format like CSV that would need full re-parsing per column read. - A foundation-model call is a stateless HTTP request to a hosted inference endpoint — the "model" holds no memory between calls, so RAG's grounding effect comes entirely from what's stuffed into that single prompt's context window, not from the model learning anything. Every token in the retrieved context plus the question plus the generated response counts against the model's context-length limit and the account's billed/metered token usage, which is the direct mechanical reason unbounded retrieved-chunk counts are a cost and latency problem: it's literally more tokens sent and generated per call, with no caching of "what was retrieved last time" built in.
- Pinning a model version matters because IBM periodically retrains or swaps out what a named model checkpoint under an unpinned alias actually serves — the inference API resolves the model ID to a specific set of weights at call time, and "greedy decoding" only guarantees determinism against one fixed set of weights, not across a silent version change behind the same alias. Referencing an explicit version string is what actually freezes which weights answer your prompt; an alias frees IBM to move it later without changing your code, which is precisely the tradeoff to avoid for reproducible report generation.
Cheat sheet¶
| Task | Command |
|---|---|
| Create Db2 Warehouse instance | ibmcloud resource service-instance-create <n> dashdb-for-transactions performance <region> |
| Create watsonx.ai instance | ibmcloud resource service-instance-create <n> pm-20 lite <region> |
| Create watsonx.governance instance | ibmcloud resource service-instance-create <n> aiopenscale lite <region> |
| List service instance plans | ibmcloud catalog service-marketplace <service> --output json |
Exercise¶
- Design an ETL flow (prose plus a SQL
CREATE EXTERNAL TABLEstatement) moving order data from Cloud Object Storage Parquet exports into Db2 Warehouse. - Write a Python snippet calling a watsonx.ai foundation model with a prompt built from a variable (not a hardcoded string).
- Extend that snippet into a minimal RAG pattern: retrieve a short list of text chunks and constrain the prompt to answer only from them.
- Explain, in a few sentences, what watsonx.governance would add on top of a working RAG integration that inference alone doesn't provide.