Case Study 2: CNB's Multi-Container Transaction Design
Background
City National Bank's CICS environment processes 2.4 million transactions daily across a CICSPlex of seven regions: two TORs handling internet banking and branch 3270 terminals, three AORs running account processing, and two specialized AORs for fraud detection and regulatory reporting. Kwame Asante is the architecture lead, Lisa Park handles the account services programs, and Rob Mercer owns the fraud detection subsystem.
CNB's internet banking modernization project is entering Phase 3: real-time account consolidation. A customer with multiple accounts — checking, savings, money market, CDs, credit cards, lines of credit — needs to see all positions in a single view with real-time balances, pending transactions, and alerts. The current system handles this through seven separate COMMAREA-based inquiries, one per product type, with the presentation layer stitching the results together. Response time: 3.2 seconds average. The target: under 800 milliseconds.
The Problem
The seven-inquiry approach is slow because:
-
Sequential execution. Each inquiry is a separate LINK, executed one after another. The 3270 interface tolerated this because operators expected per-screen retrieval. The internet banking interface does not.
-
Redundant data. Every inquiry returns the full customer header (name, address, contact info) — 400 bytes repeated seven times. The same DB2 query runs seven times.
-
No cross-product context. The credit card inquiry doesn't know the customer's savings balance. The fraud detection system can't see the full picture without reassembling data from multiple inquiries.
-
COMMAREA size limits. The credit card inquiry already uses 28KB of COMMAREA for customers with high transaction volumes. Adding cross-product data would exceed 32KB.
Kwame proposed a consolidated inquiry: one transaction, one LINK chain, one channel carrying all product data. Lisa and Rob had concerns.
Lisa's concern: "If we put everything in one channel, every program in the chain needs to know about every product type. We'll end up with a mega-copybook worse than what we have now."
Rob's concern: "Fraud detection needs to see the whole picture, but it runs in a separate region. If we're sending 200KB of account data across the MRO link for every inquiry, the network team will have my head."
Kwame's response: "That's exactly why we use multiple containers. Each program reads only the containers it needs. And Rob, we'll design the fraud channel to carry only the data your programs actually use — not the entire customer portfolio."
Design Phase
Channel Architecture: CONSOL-INQ
Kwame designed the consolidated inquiry channel with container groups:
Request Containers (created by the terminal handler, CINQ0000):
| Container | Type | Size | Description |
|---|---|---|---|
| CINQ-META | BIT | 200 bytes | Correlation ID, timestamp, version, trace flag, hop count |
| CINQ-REQUEST | BIT | 150 bytes | Customer ID, inquiry type, product filter flags |
Product Containers (created by respective product programs):
| Container | Type | Size | Description |
|---|---|---|---|
| CINQ-CHECKING | BIT | 2,500 bytes | Checking accounts (up to 5 accounts, balances, pending) |
| CINQ-SAVINGS | BIT | 2,000 bytes | Savings accounts (up to 5 accounts, balances, interest) |
| CINQ-MMARKET | BIT | 1,800 bytes | Money market accounts |
| CINQ-CD | BIT | 3,000 bytes | Certificates of deposit (up to 10, maturity dates) |
| CINQ-CREDITCARD | BIT | Variable, up to 45KB | Credit cards (up to 5 cards, last 50 transactions each) |
| CINQ-LOC | BIT | 2,200 bytes | Lines of credit (up to 3, draw history) |
| CINQ-ALERTS | CHAR | Variable, up to 5KB | Customer alerts and notifications (text messages) |
Cross-Cutting Containers:
| Container | Type | Size | Description |
|---|---|---|---|
| CINQ-CUSTDATA | BIT | 800 bytes | Customer demographics (queried once, shared by all) |
| CINQ-FRAUD-REQ | BIT | 1,500 bytes | Fraud detection request (subset of product data) |
| CINQ-FRAUD-RSP | BIT | 500 bytes | Fraud detection response (risk score, flags) |
| CINQ-ERROR | BIT | 350 bytes | Standard error container |
Program Flow
CINQ0000 (TOR)
Creates: CINQ-META, CINQ-REQUEST
Links to: CINQ0010 (AOR1) with CONSOL-INQ channel
CINQ0010 (AOR1) — Orchestrator
Reads: CINQ-REQUEST
Links to: CINQ0020 (customer data retrieval)
CINQ0020 creates: CINQ-CUSTDATA
Links to: CINQ0030 (product retrieval — parallel via async LINK*)
CINQ0030 creates: CINQ-CHECKING, CINQ-SAVINGS,
CINQ-MMARKET, CINQ-CD,
CINQ-CREDITCARD, CINQ-LOC
Links to: CINQ0040 (alerts)
CINQ0040 creates: CINQ-ALERTS
Builds: CINQ-FRAUD-REQ (subset of product data)
Links to: CINQ0050 (AOR-FRAUD) via DPL with reduced channel
CINQ0050 reads: CINQ-FRAUD-REQ, CINQ-CUSTDATA
CINQ0050 creates: CINQ-FRAUD-RSP
Links to: CINQ0060 (response formatting)
CINQ0060 reads: All product containers, CINQ-FRAUD-RSP
CINQ0060 creates: (modifies CINQ-META with response timing)
Returns to CINQ0000 (TOR)
Reads: All product containers, CINQ-ALERTS, CINQ-FRAUD-RSP, CINQ-ERROR
Formats: JSON response for internet banking / 3270 screen for branch
*Note: The "parallel via async LINK" for product retrieval is a design aspiration. CICS TS 5.5+ supports EXEC CICS RUN TRANSID for asynchronous work, but the actual implementation uses sequential LINKs within CINQ0030, which queries all product types in a single DB2 join. The async pattern is documented for future optimization.
The Fraud Detection Optimization
Rob's concern about sending 200KB across the MRO link was valid. Kwame's solution: the orchestrator (CINQ0010) builds a reduced-footprint fraud request container, CINQ-FRAUD-REQ, containing only the data the fraud detection program actually needs:
01 WS-FRAUD-REQUEST.
05 FR-CUSTOMER-ID PIC X(12).
05 FR-TOTAL-BALANCE PIC S9(13)V99 COMP-3.
05 FR-ACCOUNT-COUNT PIC S9(4) COMP.
05 FR-LARGEST-TXN-AMT PIC S9(11)V99 COMP-3.
05 FR-LARGEST-TXN-DATE PIC X(10).
05 FR-PENDING-TXN-COUNT PIC S9(4) COMP.
05 FR-PENDING-TOTAL-AMT PIC S9(13)V99 COMP-3.
05 FR-CREDIT-UTIL-PCT PIC S9(3)V99 COMP-3.
05 FR-UNUSUAL-PATTERN-FLAG PIC X(1).
05 FR-LAST-LOGIN-TS PIC X(26).
05 FR-CHANNEL-INDICATOR PIC X(4).
Instead of sending the full channel (potentially 60KB+) to the fraud AOR, CINQ0010 creates a separate channel, FRAUD-CHK, containing only CINQ-FRAUD-REQ and CINQ-CUSTDATA, and LINKs to CINQ0050 with that reduced channel. The response comes back in CINQ-FRAUD-RSP, which CINQ0010 then MOVEs into the main CONSOL-INQ channel.
* Build the fraud request from product data
PERFORM BUILD-FRAUD-REQUEST
* Create a reduced channel for fraud DPL
EXEC CICS PUT CONTAINER('CINQ-FRAUD-REQ')
CHANNEL('FRAUD-CHK')
FROM(WS-FRAUD-REQUEST)
FLENGTH(LENGTH OF WS-FRAUD-REQUEST)
BIT
RESP(WS-RESP)
END-EXEC
* Copy customer data to the fraud channel
EXEC CICS GET CONTAINER('CINQ-CUSTDATA')
CHANNEL('CONSOL-INQ')
INTO(WS-CUST-DATA)
FLENGTH(WS-CUST-LEN)
RESP(WS-RESP)
END-EXEC
EXEC CICS PUT CONTAINER('CINQ-CUSTDATA')
CHANNEL('FRAUD-CHK')
FROM(WS-CUST-DATA)
FLENGTH(WS-CUST-LEN)
BIT
RESP(WS-RESP)
END-EXEC
* DPL to fraud detection region with reduced channel
EXEC CICS LINK PROGRAM('CINQ0050')
CHANNEL('FRAUD-CHK')
SYSID('CICFRAUD')
RESP(WS-RESP)
RESP2(WS-RESP2)
END-EXEC
* Move fraud response back to main channel
EXEC CICS MOVE CONTAINER('CINQ-FRAUD-RSP')
CHANNEL('FRAUD-CHK')
AS('CINQ-FRAUD-RSP')
TOCHANNEL('CONSOL-INQ')
RESP(WS-RESP)
END-EXEC
This pattern — creating a purpose-built channel for a DPL call and then moving selected containers back to the main channel — is critical for performance in CICSPlex environments. Rob's fraud detection region receives only 2.3KB per inquiry instead of 60KB+. At 2.4 million daily transactions (roughly 40% triggering fraud checks), that's 55GB/day of avoided MRO traffic.
The Alerts Container — CHAR Decision
Kwame made one container CHAR: CINQ-ALERTS. The alerts are human-readable text messages displayed to the customer. Two reasons for the CHAR designation:
-
No binary fields. Alerts are pure text — no packed decimal, no COMP fields. CHAR is the correct type.
-
Future internationalization. CNB plans to support Spanish-language alerts within 18 months. When the alert generation program starts producing UTF-8 text (CCSID 1208), the CHAR container with FROMCCSID will handle the conversion transparently when the data reaches the TOR for formatting. If the alerts were in a BIT container, every consuming program would need explicit conversion logic.
Lisa challenged this: "What if someone adds a numeric field to the alerts structure later?" Kwame's answer: "Then they create a separate BIT container for the alert metadata. The text stays CHAR. We don't compromise the conversion capability for a hypothetical."
Implementation Challenges
Challenge 1: Container Name Collisions
During testing, Lisa discovered that another application on AOR1 also used a channel named INQUIRY with a container named CUSTDATA. When both transactions ran simultaneously, there was no collision — channels are task-scoped, not region-scoped. But Lisa initially confused herself by looking at CICS Explorer and seeing containers from another task. She documented the scoping rules in the team's runbook:
"Channels are scoped to the task (transaction instance). Two transactions running simultaneously with identically named channels and containers do not interfere with each other. Each task has its own channel namespace."
Challenge 2: CINQ-CREDITCARD Variable Length
The credit card container can vary from 900 bytes (one card, no transactions) to 45KB (five cards, 50 transactions each with extended merchant data). CINQ0060 (the formatting program) needed to handle this variability.
Lisa's approach: use GET with SET for the credit card container, then inspect the header to determine the actual structure:
01 WS-CC-PTR POINTER.
01 WS-CC-LEN PIC S9(8) COMP.
LINKAGE SECTION.
01 LS-CC-DATA.
05 LS-CC-HEADER.
10 LS-CC-COUNT PIC S9(4) COMP.
10 LS-CC-VERSION PIC X(4).
05 LS-CC-CARDS.
10 LS-CC-CARD-ENTRY OCCURS 1 TO 5 TIMES
DEPENDING ON LS-CC-COUNT.
15 LS-CC-NUMBER PIC X(16).
15 LS-CC-TXN-CNT PIC S9(4) COMP.
15 LS-CC-TXNS PIC X(1).
* (variable length follows)
PROCEDURE DIVISION.
EXEC CICS GET CONTAINER('CINQ-CREDITCARD')
CHANNEL('CONSOL-INQ')
SET(WS-CC-PTR)
FLENGTH(WS-CC-LEN)
RESP(WS-RESP)
END-EXEC
IF WS-RESP = DFHRESP(NORMAL)
SET ADDRESS OF LS-CC-DATA TO WS-CC-PTR
PERFORM VARYING WS-CARD-IDX
FROM 1 BY 1
UNTIL WS-CARD-IDX > LS-CC-COUNT
PERFORM FORMAT-CARD-DATA
END-PERFORM
ELSE IF WS-RESP = DFHRESP(CONTAINERERR)
* Customer has no credit cards — not an error
CONTINUE
ELSE
PERFORM HANDLE-CONTAINER-ERROR
END-IF
Note the CONTAINERERR handling: if the customer has no credit cards, the CINQ-CREDITCARD container simply doesn't exist. The formatting program treats this as a valid state, not an error. This is a fundamental advantage of the multi-container approach — optional data is represented by optional containers.
Challenge 3: Error Propagation in the Pipeline
Rob raised a scenario: what if the fraud detection DPL fails (region down, network timeout)? Should the entire consolidated inquiry fail?
Kwame designed a tiered error strategy:
- Fatal errors (customer not found, authentication failure): populate CINQ-ERROR, short-circuit the pipeline. Return immediately.
- Degraded errors (fraud check unavailable, alerts service down): populate the error container with severity 'W' (warning), continue processing. The response includes all available data with a flag indicating which services were unavailable.
- Product errors (one product type query fails): skip that product's container. The formatting program checks for each container's existence and formats whatever is available.
* After fraud DPL attempt
IF WS-RESP NOT = DFHRESP(NORMAL)
* Fraud check failed — degrade gracefully
MOVE 'Y' TO WS-FRAUD-DEGRADED
INITIALIZE WS-ERROR-CONTAINER
SET EC-ERROR-OCCURRED TO TRUE
MOVE 'CINQ0010' TO EC-ERROR-SOURCE
MOVE 'FRDDPL01' TO EC-ERROR-CODE
SET EC-SEV-WARNING TO TRUE
MOVE 'FRAUD CHECK UNAVAILABLE - RESPONSE DEGRADED'
TO EC-ERROR-MESSAGE
EXEC CICS PUT CONTAINER('CINQ-ERROR')
CHANNEL('CONSOL-INQ')
FROM(WS-ERROR-CONTAINER)
FLENGTH(LENGTH OF WS-ERROR-CONTAINER)
BIT
END-EXEC
* Continue processing — do not short-circuit
ELSE
PERFORM HANDLE-FRAUD-RESPONSE
END-IF
Performance Results
After three months in production:
| Metric | Before (7 COMMAREAs) | After (1 Channel) | Improvement |
|---|---|---|---|
| Average response time | 3,200 ms | 680 ms | 79% faster |
| DB2 queries per inquiry | 14 (7 x 2, header + product) | 3 (1 header + 1 join + 1 alerts) | 79% fewer |
| MRO traffic (fraud) | N/A (no real-time fraud) | 2.3 KB/inquiry | New capability |
| TS queue usage | 15% of inquiries | 0% | Eliminated |
| Data volume per inquiry | 7 x 31.5KB = 220KB COMMAREA total | 40KB average channel | 82% less data movement |
| CPU per inquiry | 12.4 ms | 8.1 ms | 35% reduction |
The response time improvement came primarily from eliminating redundant DB2 queries and the sequential LINK overhead. The CPU reduction came from eliminating the repeated customer header retrieval and the TS queue I/O.
Lessons Learned
1. Selective DPL channels are not optional — they're essential. Sending the full channel across MRO for every DPL call would have negated the performance gains. Building purpose-specific channels for remote calls is extra code but pays for itself immediately.
2. Optional containers simplify the data model. The old COMMAREA approach required every field to be present with a "not applicable" value. The channel approach lets absent data be represented by absent containers. This eliminated dozens of "if field is not spaces" checks throughout the codebase.
3. CHAR containers for text data is an investment in the future. The CINQ-ALERTS CHAR container required no additional code for the Spanish language launch — the FROMCCSID/INTOCCSID conversion handled it transparently.
4. Error severity tiers keep the user experience intact. A customer viewing their accounts shouldn't get an error page because the fraud detection region had a 30-second hiccup. The degraded-response pattern preserves functionality while flagging the limitation.
5. Container naming conventions must be enforced, not suggested. Kwame's [FLOW]-[CONTENT] convention prevented three naming collisions that were caught in code review. After the second incident, Lisa added a naming convention check to the pre-deployment review checklist.
Discussion Questions
-
Kwame chose to build a separate FRAUD-CHK channel for the DPL call rather than sending the full CONSOL-INQ channel. Under what circumstances would sending the full channel be acceptable? What threshold of MRO traffic justifies the extra code to build a reduced channel?
-
The consolidated inquiry combines seven product types into one transaction. If the checking account query takes 400ms due to a DB2 lock wait, all seven product types are delayed. How would you redesign the flow to isolate slow product queries without reverting to seven separate transactions?
-
Lisa used GET with SET for the variable-length credit card container. The SET pointer is valid until the next GET with SET for the same container or until the container is deleted. What happens if Lisa's formatting code issues a PUT to a different container between the SET and the data access? Is the pointer still valid?
-
The error container uses a single structure for all error types. An alternative would be separate error containers per severity (CINQ-ERROR-FATAL, CINQ-ERROR-WARN, CINQ-ERROR-INFO). Evaluate both approaches in terms of code complexity, container browsing, and error aggregation.
-
CNB plans to expose the consolidated inquiry as a REST API. The channel containers map naturally to JSON objects. Sketch the JSON response structure that corresponds to the CONSOL-INQ channel, noting which containers become top-level JSON objects and which become nested arrays.