Double Charge trong Telco: Khi hệ thống trừ cước hai lần và tưởng mình đang đúng

Double Charge trong Telco: Khi hệ thống trừ cước hai lần và tưởng mình đang đúng

Table of Contents

Double Charge trong Telco

Ai làm vận hành Telco chắc đều biết kiểu tin nhắn trực ca mà chỉ cần đọc qua là biết đêm đó khó ngủ: khách đã kích hoạt gói thành công nhưng bị trừ tiền hai lần hoặc bị trừ sai số tiền khi đăng ký gói mới.

Với Telco, double charge, tức tình trạng khách hàng bị trừ tiền hai lần cho cùng một giao dịch, không chỉ là sai số. Nó là mất uy tín, mất niềm tin và đôi khi là mất luôn khách hàng.

Nhưng điều khó nhất thường không phải sửa lỗi. Cái khó nhất là ngay lúc sự cố đang diễn ra, hệ thống thường không trả lời được một câu rất cơ bản: giao dịch này đã thực sự trừ cước chưa?

Tôi từng mất gần nửa ngày chỉ vì thiếu đúng một bản ghi log ở đoạn đó. Truy vết vòng qua đủ hệ thống, nhìn thấy đủ dấu vết, nhưng vẫn thiếu một mảnh để kết luận. Có những ca truy vết xong vẫn không chắc hoàn toàn, nhưng ít nhất mình biết mình đang đoán ở đâu và đang thiếu bằng chứng ở đâu.

Bài viết này nằm trong series chia sẻ kinh nghiệm thiết kế và vận hành hệ thống Telco. Ở bài trước, tôi nói về Observability và hành trình giao dịch. Bài này đi sâu hơn vào một điểm cụ thể mà observability bắt buộc phải kiểm soát được: ranh giới giao dịch tại mốc trừ cước.

Có một nguyên tắc tôi gần như luôn nhắc lại với đội của mình:

Timeout không nguy hiểm. Cái nguy hiểm là khi hệ thống gặp timeout, nó tự động gửi lại yêu cầu mà không biết lần trước đã trừ tiền thành công hay chưa.

Từ timeout đến double charge diễn ra như thế nào?

Time out và retry double charge trong telco

Các luồng mua gói data, thoại, gói bổ sung hay gia hạn nhìn bề ngoài thường rất đơn giản. Người dùng bấm mua, hệ thống trừ tiền, gói được kích hoạt.

Nhưng ở tầng kỹ thuật, yêu cầu thường đi qua nhiều lớp khác nhau, gồm cả xử lý đồng bộ và bất đồng bộ. Yêu cầu có thể đi từ ứng dụng, web hoặc USSD vào backend qua REST API, sau đó tiếp tục được đẩy sang các luồng xử lý bất đồng bộ bằng microservice và message broker như Kafka.

Trong thực tế vận hành, tôi thường gặp hai nhóm nguyên nhân dễ dẫn tới double charge.

  • Nhóm đầu tiên nằm ở luồng đồng bộ. Logic cộng trừ mới chồng lên logic cũ sau các lần nâng cấp hệ thống. Đội triển khai đánh giá chưa hết ảnh hưởng, dẫn tới một số trường hợp đặc biệt bị chạy qua hai nhánh xử lý hoặc áp sai điều kiện tính cước.

  • Nhóm thứ hai nằm ở luồng bất đồng bộ. Thiếu lock đúng chỗ, người dùng thao tác nhanh hoặc yêu cầu được gửi lên hai lần. Hai tiến trình xử lý nhận cùng một bản tin và chạy song song. Nếu bước trừ cước không có cơ chế chống trùng thực sự, cả hai lần xử lý đều trông hợp lệ.

Điều khó chịu là thiệt hại kiểu này thường không bùng nổ ngay lập tức. Ban đầu có thể chỉ thấy vài trăm giao dịch lệch. Nhưng sau đó mới lộ ra phần mệt nhất: hàng nghìn sai lệch nhỏ, rải rác, khó đối soát, và rất khó kết luận chính xác giao dịch nào cần hoàn lại hay bù trừ.

Khi lệnh trừ tiền đã gửi sang OCS, không rút lại được

OCS (Online Charging System) là hệ thống tính cước thời gian thực, cho phép tính phí ngay lập tức cho các dịch vụ thoại, data, SMS, VAS trên nền tảng 2G, 3G, 4G, 5G. OCS hỗ trợ quản lý và tùy chỉnh gói cước linh hoạt cho từng thuê bao, đảm bảo chính xác và tối ưu chi phí.

Theo trải nghiệm của tôi, khoảnh khắc hệ thống gửi lệnh sang OCS để cộng hoặc trừ tiền là điểm không thể rút lại. Khi đó khách đã nhận được SMS biến động số dư hoặc nhìn thấy tài khoản thay đổi. Câu chuyện không còn là hệ thống lỗi nữa mà là tiền đã đi đâu.

Đây cũng là chỗ mà kiến trúc xử lý theo sự kiện thường bị hiểu sai. Trong kiến trúc này, các hệ thống không gọi trực tiếp cho nhau mà giao tiếp bằng cách phát ra sự kiện rồi hệ thống khác nhận và xử lý tiếp. Nhiều người sợ mô hình này vì nghĩ luồng bất đồng bộ khó kiểm soát. Nhưng thực ra bất đồng bộ không phải thứ đáng sợ. Cái đáng sợ là ở mốc trừ cước, không có điểm kiểm soát nào để quyết định gửi lại hay không, chống trùng, và bù trừ.

Vì sao cần một lớp điều phối riêng cho các thao tác liên quan đến tiền?

Journal layer transaction control

Một tình huống kinh điển trong vận hành thực tế: phía sau đã trừ tiền thành công, nhưng phía trước không nhận được kết quả. Cổng trung gian báo quá thời gian chờ cho người dùng, trong khi nghiệp vụ phía sau thực tế đã chạy xong. Nếu hệ thống quyết định gửi lại yêu cầu chỉ dựa trên trạng thái quá thời gian chờ và số lần gửi lại được cấu hình sẵn, thì chính hệ thống đang tự biến một giao dịch thành hai lần trừ cước.

Nếu chỉ dựa vào log rải rác ở nhiều hệ thống, mỗi đội một định dạng, bạn sẽ luôn rơi vào trạng thái ghép rồi cãi. Cuộc họp xử lý sự cố sẽ kết thúc bằng tranh luận thay vì kết luận.

Theo góc nhìn cá nhân của tôi, cần có một lớp điều phối và ghi nhận hành trình giao dịch riêng, hay còn gọi là orchestration/journal layer. Lớp này giữ trạng thái tối thiểu của hành trình giao dịch liên quan tới tiền, không cần ôm hết nghiệp vụ của từng hệ thống. Nó chỉ cần đủ để trả lời nhanh, có bằng chứng, cho ba câu hỏi.

  1. Giao dịch đang ở mốc nào trong hành trình.

  2. Mốc nào đủ điều kiện tính cước.

  3. Nếu quá thời gian chờ hoặc gửi lại xảy ra, lần xử lý tiếp theo dựa vào đâu để tránh nhân đôi giao dịch.

Nghe thì giống như thêm một lớp vào kiến trúc. Nhưng nếu thiếu lớp này, ranh giới giao dịch quanh tiền gần như bị đứt. Và mọi thứ phía trên sẽ chỉ còn suy luận.

Chống trùng từ phía client là chưa đủ nếu backend không ràng buộc

Request (Request Identifier) là một giá trị định danh duy nhất được gán cho mỗi yêu cầu gửi đến máy chủ. Nó hoạt động giống như một mã số đơn hàng, giúp hệ thống theo dõi, quản lý và xác định chính xác từng yêu cầu cụ thể.

Một cách chống trùng phổ biến là để phía client sinh ra một requestId riêng cho mỗi yêu cầu gửi lên hệ thống. Về nguyên tắc, cách làm này hoàn toàn hợp lý. Nhưng trong nhiều hệ thống thực tế, requestId chỉ được dùng để truy vết chứ không được dùng để chặn yêu cầu trùng lặp, vì backend không ràng buộc tính duy nhất của nó.

Khi đó sẽ xuất hiện những tình huống rất thực tế. Một kênh nào đó quên truyền requestId. Lần gửi lại sinh ra requestId mới. Bản tin được xử lý lại nhưng đi theo một luồng khác và mất ngữ cảnh ban đầu.

Kết quả là giao dịch tới sau nhìn hoàn toàn hợp lệ. Không có gì đánh dấu đây là giao dịch trùng. Chỉ tới khi khách phản ánh bị trừ tiền thì đội vận hành mới bắt đầu truy vết ngược lại.

Nói thẳng ra, nếu cơ chế chống trùng chỉ hoạt động trong điều kiện lý tưởng thì vận hành thực tế sớm muộn cũng sẽ tìm cách phá vỡ điều kiện lý tưởng đó.

Ba điểm cần làm chắc để idempotency thực sự bảo vệ được tiền

Idempotency

Idempotency là khả năng đảm bảo rằng một thao tác dù thực thi nhiều lần thì kết quả cuối cùng vẫn chỉ tương đương một lần thực thi duy nhất.

Với hệ thống động đến tiền, đây gần như là tính chất sống còn.

Tôi không cố làm một checklist đẹp, nhưng theo trải nghiệm thực tế, có ba thứ làm chắc được thì rủi ro giảm đi rất nhiều.

Thứ nhất, cần có một bản ghi bền vững đại diện cho thao tác cộng trừ tiền, trước khi gọi sang OCS. Tên gọi là bảng giao dịch, journal hay charging trigger log không quan trọng. Quan trọng là bản ghi đó đủ để dùng làm bằng chứng: giao dịch này đã gọi OCS chưa, kết quả ra sao, và nếu yêu cầu được gửi lại lần nữa thì nên trả kết quả cũ hay thực thi lại.

Thứ hai, idempotency phải được ràng buộc ở backend chứ không chỉ tin vào phía client. Client sinh requestId duy nhất là điều kiện tốt để bắt đầu. Nhưng backend phải đóng đinh bằng dữ liệu thật: lưu idempotency key, trạng thái xử lý và kết quả cuối cùng để đảm bảo không tồn tại hai lần trừ cước cho cùng một ý định mua hàng.

Thứ ba, việc chạy lại luồng xử lý phải an toàn với tiền. Trong vận hành thực tế sẽ luôn có lúc phải phát lại bản tin, chạy lại job, tiến trình xử lý nhận lại bản tin đã xử lý trước đó, hoặc cần bổ sung dữ liệu thiếu. Nếu những luồng này không đi qua cùng journal hoặc cùng idempotency record tại mốc tính cước, thì chính cơ chế cứu hệ sẽ biến thành cơ chế nhân đôi giao dịch.

Và điều quan trọng là nhánh bù trừ cũng phải chịu cùng kỷ luật đó. Vì bù trừ chạy trùng thường còn nguy hiểm hơn trừ tiền trùng.

Đi nhanh luôn có cái giá của đi nhanh

Thiết kế theo hướng này không miễn phí.

Bạn sẽ phải trả giá bằng thêm một lớp điều phối và ghi nhận hành trình cho các giao dịch liên quan tới tiền. Thêm dữ liệu bền vững để lưu kết quả xử lý phục vụ việc gửi lại. Thêm công sức để ràng buộc tính duy nhất ở backend. Log nhiều hơn, lưu trữ nhiều hơn và đội vận hành cũng phải kỷ luật hơn.

Nhưng đổi lại, bạn mua được những thứ rất thực dụng: giảm double charge, giảm thời gian họp khẩn xử lý sự cố và giảm những cuộc truy vết kéo dài hàng giờ mà vẫn không chắc tiền đã đi đâu. Quan trọng nhất là không phải chờ khách phản ánh mới biết hệ thống đang xử lý sai tiền.

Tóm lại double charge trong Telco không phải lỗi hiếm gặp. Nó thường là hệ quả trực tiếp của việc thiếu một điểm kiểm soát rõ ràng tại mốc trừ cước.

Transaction-id, journal layer, idempotency và thiết kế an toàn khi chạy lại không phải lý thuyết kiến trúc cho đẹp slide. Đó là những thứ quyết định hệ thống có thực sự kiểm soát được dòng tiền hay không.

Và trong Telco, nếu không chứng minh được tiền đã đi như thế nào, thì mọi kết luận sau đó vẫn chỉ là suy đoán.

Nếu bạn đang vận hành hoặc phát triển hệ thống Telco và từng gặp những tình huống tương tự, tôi rất muốn nghe thêm góc nhìn thực tế từ phía bạn.

Cùng theo dõi BiPlus để đón chờ các bài tiếp theo trong series Inside Telco Systems nhé!

Về tác giả

quoc-dinh-tac-gia

Quoc Dinh – Co-founder | Phó Giám đốc

Bạn có một ý tưởng phần mềm nhưng chưa biết nên bắt đầu từ đâu, cũng không chắc phải làm thế nào để hiện thực hóa nó. Đây là giai đoạn then chốt, bởi chỉ cần đưa ra những quyết định sai ngay từ đầu, cả doanh nghiệp có thể bị ảnh hưởng nghiêm trọng.

Vì vậy, hãy để tôi đồng hành cùng bạn. Tôi có hơn chín năm kinh nghiệm làm Software Development Engineer, chuyên phát triển, nâng cấp và triển khai các hệ thống phần mềm trong ngành viễn thông, đặc biệt là mảng BCCS (Billing, Charging and Customer Care System). Ngoài ra, tôi cũng đã có hơn hai năm làm việc tại thị trường quốc tế, trong đó có Peru.

“Quoc là một Tech PM vừa có trách nhiệm vừa có năng lực. Anh ấy rất thành thạo các dự án Samsung 3PD và luôn là người đầu tiên mọi người nghĩ đến khi gặp sự cố. Được làm việc cùng anh ấy thật sự rất thoải mái.”
— Mr. Minsu Jang, PMO Consultant tại S-Core.

Bạn đã sẵn sàng sở hữu sản phẩm phần mềm đột phá tiếp theo chưa? Let’s connect!

Table of Contents

Đừng bỏ lỡ!

Cập nhật thông tin mới nhất hàng tuần về các xu hướng công nghệ, kiến thức, tài liệu về các sản phẩm của Atltassian qua hòm thư của bạn!

Theo chính sách bảo mật của chúng tôi, chúng tôi cam kết bảo mật dữ liệu cá nhân của bạn.

 

Theo chính sách bảo mật của chúng tôi, chúng tôi cam kết bảo mật dữ liệu cá nhân của bạn.

 

Mời bạn tham gia nhóm Cộng đồng Atlassian Việt Nam
Theo dõi BiPlus tại

Theo chính sách bảo mật của chúng tôi, chúng tôi cam kết bảo mật dữ liệu cá nhân của bạn.

 

Scroll to Top