Designing a Scalable One-to-One Chat System: Architecture & Data Strategies

This article breaks down the functional requirements, data models, layered architecture, push‑pull messaging choices, and message flow for building a robust one‑to‑one instant‑messaging service using Redis, MySQL and asynchronous queues.

IT Architects Alliance
IT Architects Alliance
IT Architects Alliance
Designing a Scalable One-to-One Chat System: Architecture & Data Strategies

Function Decomposition

The one‑to‑one instant‑messaging (IM) module consists of four functional components:

Conversation list ordered by the timestamp of the latest message.

Chronological chat window that displays messages in order.

Unread‑message counters for each conversation.

User profile data (avatar, nickname) displayed alongside the conversation.

These components map to five storage groups: conversation list, message records, offline‑message list, unread‑count data, and user profiles.

Data Structures

Redis sorted sets (SortedSet) are used for ordered collections, while MySQL tables provide durable persistence for message records.

1. Conversation List

Key : prefix_xxx:{uid} Value : {conversationId} Score : {msgId} (ID of the latest message)

The user ID forms the Redis key; each member is a conversation ID, and the score is the latest message ID, which automatically keeps the list sorted by recency.

2. Single‑Chat Message List

Key : prefix_session_list:{sessionId} Value : {msgId} Score : {msgId}

Messages can also be persisted in MySQL for long‑term storage:

create table t_msg_record_list (
  id bigint not null primary key,
  sessionId bigint not null comment 'Conversation ID',
  msgId bigint not null comment 'Message ID',
  isRead tinyint not null default 0 comment 'Read flag',
  recordStatus smallint not null default 0 comment 'Message status',
  createTime datetime not null,
  key `sessionId` (`sessionId`)
) engine=innodb;

Paginated retrieval example (newest first):

SELECT msgId FROM t_msg_record_list
WHERE sessionId = 1 AND recordStatus = 0 AND msgId > 1
ORDER BY id DESC
LIMIT 10;

3. Offline Messages

Two Redis structures are required: an index of senders and a list of message IDs per sender.

Sender index key : prefix_xxx:{uid} Value : {senderUid}
Message list key : prefix_offline_msg:{uid}:{senderUid} Value : {msgId}

4. Unread Count

Unread = total received – read

Store both totals per conversation per user:

Total count key : prefix_session_count:{conversationId}:{uid} Value : total message count
Read count key : prefix_session_read_count:{conversationId}:{uid} Value : read message count

5. User Profile

Design a MySQL table for user information (e.g., avatar URL, nickname) and synchronize updates to Redis when needed.

Layered Architecture

The system can be divided into five logical layers:

Client Layer : SDKs for web, mobile, etc., hide low‑level details from the application.

Connection Layer : Maintains long‑lived TCP/WebSocket connections, tracks online status, and isolates connection handling from business logic.

Business Layer : Split into long‑connection services (message send/receive, conversation list, unread push) and short‑connection services (profile queries, updates).

Service Layer : Micro‑services that provide core capabilities such as sensitive‑content filtering, data read/write, and message persistence.

Data Layer : Physical storage using MySQL, Redis, or other databases.

Layered architecture diagram
Layered architecture diagram

Push‑Pull Mode Selection

A hybrid approach balances bandwidth and latency:

Use pull for device initialization and historical pagination.

Use push for real‑time delivery and unread‑notification pushes.

Message Flow

Message flow diagram
Message flow diagram

Sending

Message filtering in the logic layer according to defined rules.

Enrich the message with sender information (e.g., senderId, avatar, nickname).

Enqueue the message to an asynchronous task queue.

The asynchronous task performs four actions:

Persist the chat record in MySQL.

Update the total message count for unread‑count calculation.

Push the message to online recipients or store it in the offline list if the recipient is offline.

Refresh the conversation‑list score in Redis.

Queues can be separated into real‑time, delayed, and retry queues, each with dedicated thread pools.

Receiving

Client appends the message to the chat UI and marks the conversation as having unread messages.

If the chat window is open, the client sends an ACK to the server to mark the message as read.

Server updates the read‑count data in Redis.

When the client pulls offline messages, the server clears the corresponding offline entries.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

redisIMmysqlMessage Queuepush-pullChat Architecture
IT Architects Alliance
Written by

IT Architects Alliance

Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.