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:

  1. 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.

  2. 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.

  3. 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.

  4. 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:

  1. No binary fields. Alerts are pure text — no packed decimal, no COMP fields. CHAR is the correct type.

  2. 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

  1. 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?

  2. 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?

  3. 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?

  4. 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.

  5. 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.