Một lỗi duy nhất của ChatGPT khiến doanh thu thất thoát hơn 15 triệu won
(asim.bearblog.dev)- Một startup vừa bật tính năng kiếm tiền đã gặp sự cố thanh toán đăng ký, nhưng nội bộ không tái hiện được nên việc xác định nguyên nhân bị trì hoãn 5 ngày
- Sự cố bắt đầu khi sao chép định dạng chuyển đổi Prisma/TypeScript→Python/SQLAlchemy do ChatGPT tạo ra, khiến một chuỗi ID được hard-code được đưa vào như giá trị mặc định thay vì hàm tạo UUID
- Do cấu trúc gồm 8 task AWS ECS và mỗi task có 5 instance, người dùng có thể gặp một trong tối đa 40 nhóm ID duy nhất, còn ban ngày vấn đề bị che khuất bởi việc triển khai thường xuyên
- Vào ban đêm, khi việc triển khai dừng lại, ID duy nhất của từng server bị dùng hết, và các lần thử đăng ký mới sau đó thất bại vì xung đột ID unique
- Ước tính thiệt hại dựa trên 50 khiếu nại mỗi ngày, 5 ngày, phí đăng ký $40/tháng là $10.000 doanh thu hằng tháng; việc thiếu test, logging, cảnh báo và sao chép code đã khiến ứng phó sự cố trở nên nghiêm trọng hơn
Sự cố đăng ký lộ ra ngay sau khi bật kiếm tiền
- Startup lần đầu bật kiếm tiền vào tháng 5 và có khách hàng đầu tiên trong vòng 1 giờ sau khi ra mắt
- Sáng hôm sau, Gmail đã chất đống hơn 40 phản hồi phàn nàn của người dùng
- Người dùng không thể hoàn tất đăng ký
- Họ báo rằng khi bấm nút đăng ký, một spinner tải vô hạn xuất hiện
- Nhóm tự tạo tài khoản mới để kiểm tra, nhưng nội bộ đăng ký vẫn hoạt động bình thường nên không thể tái hiện nguyên nhân
- Trong giờ làm việc hầu như không có phàn nàn, sự cố chủ yếu tích tụ qua đêm
Triển khai kiếm tiền dưới áp lực thời gian
- Tháng 5 là thời điểm batch YC S23 bắt đầu, và nhóm chưa chắc chắn sau khi ra mắt thì hướng đi nào là tối ưu
- Group partner của YC, Dalton, khuyên lấy người đăng ký trả phí làm chỉ số định hướng và tăng gấp đôi mức giá hằng tháng đã nghĩ tới
- Mức giá cuối cùng được đặt là $40/tháng
- Dự án ban đầu là full-stack NextJS, nhưng trước và sau công việc kiếm tiền, nhóm tiến hành chuyển sang Python/FastAPI
- Quá trình chuyển đổi có sử dụng ChatGPT
- Tích hợp Stripe cũng được hoàn tất
- Sau đó trong 5 ngày, thời gian ngủ giảm mạnh và mỗi ngày phải xử lý 30–50 email phàn nàn
Định dạng chuyển đổi model do ChatGPT tạo
- Trong quá trình migration backend, các database model được chuyển từ Prisma/TypeScript sang Python/SQLAlchemy
- Việc chuyển đổi model khá tẻ nhạt, và nhóm đánh giá ChatGPT làm tốt nên đã dùng nó cho gần như toàn bộ migration
- Code được tạo ra được copy-paste rồi kiểm tra hoạt động, và trên production cũng trông như bình thường nên tiếp tục sử dụng
- Khi đó việc insert vào database vẫn do Next API đảm nhiệm, còn backend Python chỉ đọc database
- Khi triển khai tính năng đăng ký, Python lần đầu bắt đầu insert record vào DB
- Model SQLAlchemy mới được tự viết, nhưng định dạng do ChatGPT tạo ở các model hiện có được sao chép nguyên xi
- Cùng một vấn đề đã đi vào cách tạo ID của mọi model
Nguyên nhân thực sự và vì sao ban ngày không thấy
- Lỗi cốt lõi là không truyền vào hàm hoặc lambda để tạo UUID, mà truyền một chuỗi ID duy nhất được hard-code
- Khi một người dùng hoàn tất đăng ký bằng ID đó trên một backend instance cụ thể, các lần thử đăng ký sau đó trên cùng instance sẽ gây xung đột ID duy nhất
- Cấu hình backend đã che giấu vấn đề lâu hơn
- Chạy 8 ECS task trên AWS
- Mỗi task chạy 5 backend instance
- Người dùng về mặt tiềm năng có thể đi tới một trong 40 ID khác nhau
- Ban ngày, nhóm commit trực tiếp vào nhánh main 10–20 lần mỗi ngày, và mỗi lần đều kích hoạt triển khai backend mới
- Mỗi lần triển khai diễn ra, lại có 40 ID mới mà khách hàng có thể sử dụng
- Ban đêm, khi commit và triển khai dừng lại, ID duy nhất của từng server nhanh chóng bị dùng hết
- Ban đầu còn gần 40 server có thể đăng ký, nhưng theo thời gian con số giảm gần về 0
Quy mô thiệt hại và các biện pháp sau đó
- Thiệt hại được tính là
50 emails/day x 5 days x $40/month, ước tính mất $10.000 doanh thu hằng tháng- Cách tính này chỉ dựa trên những người dùng đã gửi phàn nàn
- Để tìm ra nguyên nhân cần 5 ngày, vô số email, hàng trăm log Sentry, một cuộc trò chuyện Discord dài với kỹ sư Stripe, và rà soát năm file cốt lõi
- Sau khi phát hiện nguyên nhân, Adam nhanh chóng đưa bản sửa lên
- Sau đó nhóm bổ sung unit/integration test mạnh hơn, cảnh báo và logging
- Sự kiện này cho thấy khi sai sót của con người, test thiếu, sao chép code và push trực tiếp lên main chồng lên nhau, chỉ một dòng nhỏ cũng có thể dẫn tới thiệt hại doanh thu lớn
2 bình luận
Ủa, code được AI tự động tạo ra thì nhất định phải review chứ, sao lại dùng nguyên xi như vậy.
Ý kiến trên Hacker News
Thiếu giám sát mới là thứ làm mất 10.000 đô la. Ứng dụng liên tục phát sinh ngoại lệ cơ sở dữ liệu với số lượng lớn, nhưng không ai nhận được cảnh báo
Nếu có những cảnh báo như vậy thì cuộc điều tra kéo dài 5 ngày đã kết thúc trong 5 phút. Nếu chưa sửa hệ thống cảnh báo thì thực ra vẫn chưa sửa được gì cả
Lập trình thì dễ khi mọi thứ chạy tốt; phần khó là xử lý khi có vấn đề
Ngay từ lúc có khách hàng trả tiền, cần có người có kiến thức và kinh nghiệm xử lý logging, monitoring, cảnh báo, bảo mật, v.v. Không thể làm DevOps kiểu nghiệp dư
Nhưng việc cơ sở dữ liệu không có ghi log lỗi và cảnh báo mới là phần thật sự điên rồ. Đây không phải code legacy 20 năm tuổi, mà là một sản phẩm mới; cũng không phải code từ thời người ta dùng lỗi DB như một kiểu xác thực dữ liệu
Bài blog bị 404 nên để lại link Web Archive
https://web.archive.org/web/20240610032818/https://asim.bear...
Tác giả đã thêm một chỉnh sửa quan trọng: các thực hành ở đây rất tệ và đáng xấu hổ, và sau đó họ đã bổ sung unit/integration test vững chắc cùng cảnh báo/logging. Rốt cuộc đây là lỗi của con người, và nhìn lại thì rõ ràng hoàn toàn có thể tránh được
Tác giả cũng nói thêm rằng chuyện này xảy ra trong vài tuần đầu của công ty dưới áp lực thời gian rất lớn, và mong mọi người xem nó như một câu chuyện hài hước về việc một bug có tính tái hiện rất kỳ lạ trong production
Dù là một sai lầm ngớ ngẩn, con người, dù là cá nhân hay tập thể, vốn vẫn sẽ mắc những sai lầm ngớ ngẩn
https://0912i390129ionkjan.bearblog.dev/how-a-single-chatgpt...
https://webcache.googleusercontent.com/search?q=cache%3Ahttp...
Lỗi đã hiện ra ngay. Dù tôn trọng đội ngũ, chuyện này không liên quan mấy đến ChatGPT mà liên quan nhiều hơn đến việc đội đã dùng một mô hình lập trình mà họ chưa đủ quen thuộc
Ngay cả khi đã qua review code, chỉ cần có những công cụ giám sát có thể thiết lập trong vòng 5 phút thì khả năng cao cũng đã bắt được lỗi
Một tiêu đề kiểu “Đã mắc lỗi lập trình khi dùng LLM, và vì không đảm bảo chất lượng nên tốn 10 nghìn đô” sẽ không tạo ra phản ứng kiểu “Nếu ChatGPT phá hỏng thì mức phơi nhiễm của chúng ta là bao nhiêu?” từ ban điều hành. Sẽ có rất nhiều quản lý cấp trung và cấp cao đăng bài này lên LinkedIn
LLM không thể “mắc lỗi”. Nó không có tính quyết định, không suy luận, không suy nghĩ, cũng không thực hiện logic. Nó là một trình tạo word salad rất hào nhoáng dùng xác suất thống kê, và vì không có gì đảm bảo đầu ra đúng hay chính xác, nên theo định nghĩa dùng từ sai lầm cũng không đúng
Sửa: Sau khi bài viết bị downvote mạnh vì những lý do hiển nhiên, thứ hạng của nó đột ngột vọt lên, có vẻ nghĩa là moderator đã boost: https://hnrankings.info/40627558/
Thật buồn cười khi một bài clickbait đáng ra theo quy định phải bị đổi tiêu đề lại được moderator boost. Hơn nữa, việc tác giả có vẻ thuộc một công ty của Y Combinator chắc hẳn cũng hoàn toàn là trùng hợp: https://news.ycombinator.com/item?id=40629998
Với việc tạo UUID cho khóa chính, không nên dùng
str(uuid.uuid4())mà nên dùng trực tiếp callableuuid.uuid4, và SQLAlchemy sẽ gọi hàm khi tạo giá trị. Nó cũng nói giá trị mặc định cho ngày thángserver_default=text("(now())")có thể không hoạt động như mong đợi nên hãy dùngfunc.now(), đồng thời kiểm tra import củauuidvàtexttrong SQLAlchemy, và cân nhắcDateTime(timezone=True)để xử lý múi giờSau đó nó đề xuất code đã sửa là
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()), unique=True, nullable=False), trong đó việc thêmlambda:đã khắc phục vấn đềuuid.uuid4()giống như định nghĩa schema ở nơi nào đó như Prisma. Vì vậy bản thân bug này không làm tôi ngạc nhiên, và tôi cũng có thể đã mắc lỗi tương tựDù vậy, chỉ một lần chạy
kubectl logslà đã bắt được ngay. Hơn nữa, đang từ Next.js và Prisma mà chuyển sang Python ư? Tại sao?Bản thân sai sót thì có thể hiểu được. Ngay cả khi viết code không dùng ChatGPT, lỗi này trông cũng tương đối dễ bị bỏ qua
Nhưng tôi không hiểu vì sao sau lần thất bại đầu tiên lại không bị phát hiện. Công ty này không có logging à? Việc backend đang cố tái sử dụng UUID lẽ ra phải lộ rõ ngay khi nhìn lỗi
Việc vô tình truyền một chuỗi vào một hàm cần một đối tượng callable thay vì
Stringlà chuyện thường gặp. Nếu không dùng ORM thì có lẽ đã tránh được vấn đề cụ thể này, nhưng đó có thể chỉ là thiên kiến cá nhân của tôi về ORM. Những bug tương tự hoàn toàn có thể xảy ra cả trong ngữ cảnh không phải cơ sở dữ liệuNhững người lớn tiếng nói rằng chắc chắn họ đã bắt được bug này hoặc là kỹ sư giỏi hơn tôi rất nhiều, hoặc nhiều khả năng hơn là đang hơi ảo tưởng về năng lực của mình
Tuy nhiên, việc không có log hoặc không xem log thì thật sự khó hiểu. Nếu là ECS, ngoại lệ Duplicate Key có lẽ đã được đẩy sang CloudWatch mà không cần cấu hình riêng; tôi thắc mắc là chuyện đó không xảy ra, hay có xảy ra nhưng chẳng ai kiểm tra xem qua đêm đã có ngoại lệ gì
Trong tình huống như vậy, việc hỏi vì sao phát hiện chậm và vì sao chẩn đoán mất nhiều thời gian là rất hữu ích
Tôi đã thấy cùng một lỗi này nhiều lần ngay cả trong code do con người viết. Đặc biệt trong React / TypeScript / JavaScript, việc ai đó quên lambda xảy ra khá thường xuyên
Bài blog có cảm giác chưa giải thích đúng nguyên nhân gốc rễ của vấn đề mà đã chuyển ngay sang đổ lỗi cho ChatGPT. Nếu làm gấp, đưa các commit có thay đổi lớn hoặc không được đồng nghiệp review vào main thì chuyện này sẽ xảy ra
Vấn đề thật sự là: nếu vội vàng, chọn đường tắt, không kiểm thử đầy đủ và không review code với đồng nghiệp thì lỗi sẽ phát sinh. Chỉ cần có test thử nhiều tùy chọn đăng ký khác nhau là có lẽ đã phát hiện ngay
Nếu để một người như vậy ở gần code quan trọng về tài chính, các vấn đề tương tự sẽ xảy ra, và tôi sẽ nghi ngờ phán đoán của người đã quyết định deploy đoạn code đó với gần như không có test
Thật ngạc nhiên là không có lint rule cho trường hợp này
Hy vọng không phải vậy
Phần “dự án ban đầu là full-stack NextJS, nhưng trước tiên tôi muốn migration mọi thứ sang Python/FastAPI” khiến tôi mở mắt
Không hiểu một startup chưa có khách hàng thì biện minh thế nào cho việc viết lại
Dù có khách hàng hay không, tôi không hiểu vì sao ở giai đoạn sớm như vậy lại làm một chuyển dịch ngang về bản chất từ Node sang Python. Nếu đã có hàng trăm khách hàng và định chuyển sang thứ như Go thì còn có thể hiểu phần nào, nhưng như vậy vẫn đáng nghi
Ví dụ phải tạo cả đống object DTO, nhưng AutoMapper không hoạt động với tổ hợp phiên bản và thiết lập dự án tôi dùng; Entity Framework và việc serialize/deserialize JSON cũng gây đau nhiều hơn lợi
Tất nhiên có thể giải quyết dần dần. Có thể đào sâu tài liệu, pha thêm hack, nâng cấp package và viết lại cấu hình. Nhưng nếu là con người, bạn sẽ muốn cầm một can xăng ẩn dụ, đốt hết mọi thứ rồi làm hệ thống thứ hai tốt hơn. Tất nhiên thực tế không phải nó sẽ tốt hơn, mà chỉ phát sinh những điểm đau khác, và có khi còn không làm được đầy đủ hoặc làm đúng những việc hệ thống đầu tiên từng làm
Ở chỗ làm cũng vậy, mỗi khi nhìn thấy legacy hoặc một hệ thống phiền phức, tôi lại có cùng thôi thúc đó. Cần nỗ lực chủ động và liên tục để thắng bộ não đang gào lên đòi viết lại. Đôi khi các thay đổi kiến trúc như viết lại hoặc đưa container vào có kết quả tốt, nhưng thường thì hoặc lao vào biển lửa, hoặc kéo theo công việc bất tận
Trừ khi có mức độ tin tưởng cao rằng nó sẽ cải thiện việc vận hành hệ thống hoặc trải nghiệm phát triển của các developer khác, thật may là không chiều theo thôi thúc đó
ChatGPT thực ra là phía đã giúp ứng dụng kiếm được tiền. Vì nếu không có ChatGPT thì họ không có khả năng triển khai
Việc thiếu năng lực coding, debugging, logging và monitoring mới là thứ làm bay 10 nghìn đô la; còn trong câu chuyện này, tác động ròng của ChatGPT là dương
Mọi commit message đều có emoji. Khỉ, chuối, tên lửa, pháo hoa, thứ gì cũng có
https://grook.ai/share?id=e269e88a7b1a71eff4f176c864b30161&x...
Nghe nói ban đầu là full-stack NextJS, và trong lúc migration backend sang Python/FastAPI, họ đang dịch mô hình database của Prisma/Typescript sang Python/SQLAlchemy. Nội dung là công việc này nhàm chán, và thấy ChatGPT làm khá tốt nên đã dùng nó cho gần như toàn bộ migration
Nếu ngay từ đầu không có ChatGPT thì có lẽ họ đã không thử làm migration trước này, nên khó nói tác động ròng là dương. Stack cũ có thể đã có logging lỗi tốt hơn, cũng có thể không; và vì là code tự viết nên họ có thể hiểu cấu trúc rõ hơn, khiến nhu cầu ít hơn
Bản thân quyết định “viết lại toàn bộ code lần thứ hai” trước khi bật kiếm tiền cũng khá thú vị
Có câu: “Trước hết tôi muốn nói rằng các thực hành ở đây là tệ và lẽ ra có thể tránh được. Chuyện này xảy ra trong một giai đoạn khác, khi có áp lực thời gian lớn. Xin hãy đọc với điều đó trong đầu”
Chính những ràng buộc như thế này khiến subscription phần mềm trở nên đáng sợ
Có lần do race condition mà user bị tính phí hai lần. Vì vậy khi thấy timeout hoặc lỗi liên quan đến tiền, tôi trở nên hoang tưởng đến mức trước tiên giả định rằng thanh toán đã được thực hiện, rồi sau đó kiểm tra lại
Code viết bằng TypeScript và Python, framework như Next.js, chạy 5 instance cho mỗi trong 8 task AWS mà doanh thu chỉ 40 USD, thời gian phát triển chỉ vài tuần thôi sao?
Thật không hiểu chuyện gì đang xảy ra. Họ sửa lại rằng do hạn chế thời gian nên code rất lộn xộn, nhưng tệ hơn là họ lại dành thời gian cho việc refactor giữa các ngôn ngữ và dựng hệ thống phân tán chẳng vì lý do gì
Đây là kiểu độ phức tạp tự hại mình, vừa phải tung hứng tính năng vừa phải gánh độ phức tạp kỹ thuật vô lý. Không hiểu họ đã nghĩ gì
Sửa: Đây là công ty YC mùa hè 2023, nhưng đến mùa hè 2024 có vẻ sản phẩm vẫn nằm sau danh sách chờ. Có lẽ vì họ đang viết lại bằng Rust
Có vẻ người này chưa viết nổi 1000 dòng Python trong đời, nhưng đã chỉ ra đúng vấn đề
Python có một khiếm khuyết là không sao chép đúng chiến lược đánh giá của Common Lisp. Nếu trong biểu thức giá trị mặc định của tham số hàm tùy chọn có đối số kiểu
foo=obj.whatever(), thìobj.whatever()được đánh giá tại thời điểm phần định nghĩa hàm được xử lý, chứ không phải lúc gọi hàmTôi nghi là họ cố ý làm vậy vì hiệu năng. Python còn một khiếm khuyết khác: không có cú pháp literal thật sự cho các đối tượng phổ biến như list.
[1, 2, 3]không phải literal mà gần với constructor hơn; mỗi lần được đánh giá, nó phải tạo list mới và điền giá trị vàoNhà thiết kế hẳn không muốn một tham số như
list=[]tạo một list rỗng mới mỗi lần đối số bị bỏ qua. Trong Lisp,'(1 2 3)và'()là literal thật sự, và mỗi lần được tham chiếu đều trỏ tới cùng một đối tượng. Lập trình viên có thể chọn dùng(list 1 2 3)hay'(1 2 3)làm biểu thức giá trị mặc địnhCái trước tạo một đối tượng mới có thể thay đổi mỗi lần, giống
[1, 2, 3]; cái sau gần như chắc chắn trả về cùng một đối tượng và không thể sửa đổi một cách đáng tin cậy, có tính di động. Các ngôn ngữ phổ biến hiện đại đã có hầu hết tính năng của Lisp, nên nghe như câu đùa rằng chẳng có gì để mấtfoo=obj.whatever(),obj.whatever()được đánh giá tại thời điểm xử lý định nghĩa hàm chứ không phải lúc gọi hàmKhông rõ sẽ thế nào nếu
.whatever()phụ thuộc vào trạng thái nội bộ thay đổi sau khi khởi tạo đối tượng