Skip to content

Latest commit

 

History

History
430 lines (284 loc) · 14.3 KB

File metadata and controls

430 lines (284 loc) · 14.3 KB

Message Flow

How does a complete customer interaction travel through the framework?

This document follows a single conversation from creation to completion while explaining how different framework components cooperate to simulate realistic production behaviour.


Why Message Flow Matters

Performance testing often focuses on individual requests.

Login
  ↓
Search
  ↓
Logout

While this approach is perfectly suitable for stateless APIs, it falls short when the system under test contains asynchronous communication, stateful workflows and multiple interacting users.

This framework models the complete business processes instead.

Instead of asking

"How fast is this endpoint?"

it asks

"How does an entire e2e flow behave under load?"


Workflow Overview

Diagram: Message Flow (Single Client + Agent Cycle)

sequenceDiagram
    actor CV as Client VU
    participant NR as Node-RED<br/>(webhook)
    participant App as Application
    participant PG as Postgres
    participant Redis
    participant Agent as Agent VU
    participant WS as Agent<br/>WebSocket

    Note over CV,WS: Setup Phase
    CV->>NR: POST /webhook (create conversation)
    NR->>App: Process message
    App->>PG: INSERT conversation
    App->>Redis: PUBLISH customer_msg
    
    Note over CV,WS: Agent Assignment
    CV->>PG: Poll: SELECT conversation WHERE assigned_agent_id
    PG-->>CV: (wait, retry every 5s)
    App->>PG: UPDATE conversation (assign agent)
    CV->>PG: Poll returns agent_id
    
    Note over CV,WS: Agent Login (parallel)
    Agent->>App: GET /login_page
    App-->>Agent: HTML
    Agent->>App: POST /signin (credentials)
    App-->>Agent: Set-Cookie, Bearer token
    Agent->>App: GET /main (dashboard)
    App-->>Agent: Dashboard HTML
    Agent->>Redis: RPUSH ws:queue:pairing:ws {token, jwtId, agentId}

    Note over CV,WS: Agent WS Connection
    WS->>Redis: BLPOP ws:queue:pairing:ws (wait for token)
    Redis-->>WS: {token, jwtId, agentId}
    WS->>App: WebSocket handshake (jwtId + token)
    App-->>WS: WebSocket established
    WS->>Redis: RPUSH ws:queue:pairing:stream agentId

    Note over CV,WS: Message Exchange Cycle 1
    CV->>App: POST /bot_entry_logic_msg
    App->>PG: INSERT message
    App->>Redis: PUBLISH new_message
    Agent->>App: GET /get_my_conversations
    App-->>Agent: conversation_list
    Agent->>App: GET /get_conversation_messages
    App-->>Agent: message_list (includes new message)
    
    Note over CV,WS: Agent Sees Message (WS notification)
    App->>WS: WebSocket push (new_message_notification)
    WS->>Redis: Record actual_event with timestamp
    Agent->>App: POST /agent_conversation_msg (reply)
    App->>PG: INSERT agent_reply
    App->>Redis: PUBLISH agent_reply
    Agent->>App: PUT /agent_conversation_update_last_seen
    
    Note over CV,WS: Metrics Recording
    CV->>Redis: INCR client_vu_number
    Agent->>Redis: INCR agent_vu_number
    WS->>Redis: Compare expected vs actual event timestamps
    Redis->>InfluxDB: Push agents_ws_event_latency (ms)
    App->>InfluxDB: Push http_req_duration (ms)
    PG->>InfluxDB: Push db_query_duration (ms)

    Note over CV,WS: Think Time (8s delay)
    CV->>CV: sleep(8000)
    Agent->>Agent: sleep(8000)

    Note over CV,WS: Repeat for CONV_MSG_CYCLES times (default 6)
Loading

From the diagram it is easy to see that this is not a traditional HTTP benchmarks flow.

This framework models an entire customer support interaction involving multiple independent virtual users.

A single business workflow consists of six major phases:

  1. Setup Phase – a Client creates a new conversation through the webhook while the Agent authenticates in parallel.
  2. Agent Assignment – the Client waits until the application assigns an available Agent.
  3. WebSocket Pairing – the Agent publishes authentication data to Redis and a dedicated WebSocket Virtual User establishes a persistent connection.
  4. Message Exchange – Client and Agent exchange messages while WebSocket notifications are delivered asynchronously.
  5. Metrics Recording – workload scenarios, monitors and the WebSocket Event Checker record application, infrastructure and business metrics.
  6. Conversation Lifecycle – both actors complete the conversation, perform post-processing tasks and continue participating in the workload until the test ends.

Although these phases appear sequential in the diagram, several of them execute concurrently throughout the test.


Stage 1 — Setup Phase

The workflow begins with the Client Virtual User creating a new conversation.

Instead of communicating directly with the application, the Client first submits the initial message through a Node-RED webhook.

Node-RED works as connector with its own internal logic.

This logic cannot be influenced or interacted with from outside.

The application processes the webhook, creates the conversation and other entities in PostgreSQL and publishes an internal event announcing that a new customer message has arrived.

At the same time, Agent Virtual Users perform their own initialization:

  • open the login page
  • authenticate
  • enter the dashboard
  • receive an authentication token and JWT identifier

After a successful login, the Agent stores its authentication information inside a Redis pairing queue.

This allows the WebSocket Virtual User to establish the connection independently from the HTTP workflow.


Stage 2 — Agent Assignment

The Agent begins polling application until an Agent has been assigned.

The assignment is performed asynchronously by the application itself.

Neither participant knows when the assignment will happen.

Client does not know which Agent or when he will be assigned.

Agent does not know which conversation or when it will be assigned.

In a real contact center, the Agent repeatedly checks the dashboard:

  • "Has a new conversation been assigned to me?"
  • if no:
    • wait
    • check dashboard again
  • if yes:
    • opens the assigned conversation
    • reviews the customer's information and message history
    • takes conversation into work

This waiting period belongs to the business workflow rather than the transport layer.

Even when every HTTP request is fast, conversation assignment may still become a bottleneck due to routing logic, Agent availability or application processing.

The framework not only reproduces this behaviour but also evaluates its efficiency through Meta Metrics.

These metrics describe the business workflow itself rather than individual requests and help identify bottlenecks that would remain invisible when analysing HTTP latency alone.

Metrics collected during this phase include:

  • Agent utilization
  • idle conversation count
  • successful assignments
  • assignment failures

Stage 3 — WebSocket Pairing

Diagram: Cross-VU Coordination via Redis

graph TB
    subgraph AgentVU["Agent VU"]
        ALive["Agent Active"]
        AMsg["Send Message"]
        AEvent["Record Event:<br/>ws:stream:event:agentId"]
    end

    subgraph WSVu["WS Listener VU"]
        WSWait["Wait for Token"]
        WSOpen["Open WebSocket"]
        WSRecv["Receive Notification"]
        WSEvent["Record Event:<br/>ws:queue:event:agentId"]
    end

    subgraph Monitor["Event Stream Checker VU"]
        Check["Compare Events"]
        Calc["Calculate Latency"]
        Record["Record Success/Error"]
    end

    subgraph Redis["Redis"]
        Queue1["ws:queue:pairing:ws<br/>(LIST)"]
        Stream["ws:stream:event:agentId<br/>(STREAM)"]
        Queue2["ws:queue:event:agentId<br/>(LIST)"]
        Queue3["ws:queue:pairing:stream<br/>(LIST)"]
    end

    ALive -->|1. Login| Queue1
    ALive -->|Push Token| Queue1
    Queue1 -->|2. BLPOP Token| WSWait
    WSWait -->|Extract Token| WSOpen
    WSOpen -->|3. Open WS| WSRecv

    AMsg -->|4. Send Message<br/>+ Timestamp| Stream
    WSRecv -->|5. Receive on WS<br/>+ Timestamp| WSEvent
    WSEvent -->|Push Event| Queue2

    Queue2 -->|6. Read Actual| Check
    Stream -->|7. Read Expected| Check

    Check -->|8. Compare| Calc
    Calc -->|9. Latency| Record
    Record -->|10. Record Metrics| Monitor

    WSOpen -->|11. Push AgentId| Queue3

    style AgentVU fill:#E8F5E9,stroke:#333
    style WSVu fill:#F3E5F5,stroke:#333
    style Monitor fill:#FFF3E0,stroke:#333
    style Redis fill:#E1F5FE,stroke:#333
Loading

The Agent Virtual User never opens a WebSocket connection itself.

Instead, after login it publishes its authentication data into a Redis queue.

The dedicated WebSocket Virtual User blocks on that queue, retrieves the authentication payload and establishes the WebSocket session independently.

Once connected, the WebSocket scenario publishes the Agent identifier into another Redis queue so that monitoring scenarios know which WebSocket streams should be observed.

Keeping HTTP and WebSocket responsibilities in separate scenarios provides several advantages:

  • cleaner separation of concerns
  • independent reconnect logic
  • isolated WebSocket latency measurements
  • easier debugging of asynchronous communication

Moreover, this does tackle one of the k6 limitations: the VU scenario is sequentual.

This is not a JMeter where you can casually spawn a branching parallel thread.


Stage 4 — Message Exchange

After both participants are ready, the conversation enters its main workload loop.

Each iteration consists of:

  1. Client sends a new message.
  2. Application stores the message.
  3. Agent periodically refreshes conversation data.
  4. WebSocket pushes a notification announcing the new message.
  5. Agent sends a reply.
  6. Application stores the reply.
  7. Conversation state is updated.

Each real action - message sending - does introduce a think time. Different and configurable for each actor.

A human being cannot formulate, write and send message in span of miliseconds.

We require time to read incoming messages, check documents, decide on the appropriate response, etc.

Iteration repeats for the configured CONV_MSG_CYCLES value before the scenario completes.

Unlike synthetic request loops, every iteration represents one complete and realistic business interaction.


Stage 5 — Metrics Recording

While the business workflow executes, several independent components collect different categories of metrics.

The Client and Agent scenarios record:

  • HTTP performance
  • business transactions
  • activity counters

The WebSocket scenario records:

  • notification timestamps
  • successful deliveries
  • connection state

The Event Stream Checker continuously compares expected events written by the Agent with actual WebSocket events received by the listener.

The timestamp difference between those two events becomes the measured WebSocket delivery latency.

At the same time, monitoring scenarios collect infrastructure information including PostgreSQL connection counts and other system-level metrics.

Rather than relying on a single source of truth, the framework correlates metrics produced by multiple independent actors.


Stage 6 — Conversation Lifecycle

Unlike many example k6 scenarios, both the Client and the Agent are long-lived Virtual Users.

Completing a conversation does not terminate the scenario.

Agent doesn't go home after one conversation.

Contact center doeen't have only one Client.

Instead, both actors continue participating in the workload until the configured test duration has elapsed.

This better reflects production behaviour where users continuously interact with the system rather than performing a single isolated action.

At the end of each conversation, both participants perform their own completion workflow.

Agent

The Agent completes the business process by:

  • sending the final reply
  • marking the conversation as Resolved
  • becoming available for the next assignment

Client

The Client finishes its interaction by:

  • receiving the CSAT (Customer Satisfaction) request
  • submitting a satisfaction score
  • becoming ready to create the next conversation

Each actor then applies its configured think time before entering the next iteration of the workload.

The complete conversation lifecycle repeats until the configured test duration expires.

Why this approach?

For test:

This continuous lifecycle produces a steady-state workload that more closely resembles production environments than fire-once scenarios, where Virtual Users terminate immediately after completing a single business transaction.

For me:

Provides VU traceability where I am in full control.


Failure Handling

Real systems rarely behave perfectly.

The framework therefore expects failures rather than assuming success.

Examples include:

  • assignment timeout
  • failed authentication
  • missing WebSocket events
  • delayed synchronization
  • HTTP retries
  • unavailable infrastructure

Whenever possible, failures are reported as metrics rather than immediately terminating execution.

This allows performance reports to explain not only what failed, but also how frequently failures occurred throughout the test.


Why Separate Scenarios?

One question often appears during code reviews:

Why not perform everything inside a single Virtual User?

Let's imagine this is technically possible in this case.

While this approach would reduce the number of scripts, it would also introduce several problems.

A single scenario would become responsible for:

  • business logic
  • synchronization
  • monitoring
  • WebSocket management
  • reporting

Combining unrelated responsibilities quickly leads to large, difficult-to-maintain scripts.

Instead, every scenario performs one clearly defined role while Redis coordinates collaboration between them.

This design follows the same separation-of-concerns principles commonly applied in production software.


Lessons Learned

The biggest lesson that implementing realistic workflows can provide: business processes are significantly more valuable than isolated requests.

Endpoints rarely operate independently in production.

Users authenticate, wait for assignments, receive notifications, exchange messages and interact asynchronously.

Modelling those interactions required more engineering effort, but the resulting workloads behave much closer to real production traffic.

The additional complexity also made performance investigations considerably more meaningful because metrics now describe complete user journeys rather than disconnected HTTP requests.

Dear reader remember one thing: "endpoint is holding 100k RPS" is useless if production does require 100