
LLM Inevitabilism: Cái bẫy tâm lý 'sự tất yếu' và cuộc khủng hoảng danh tính kỹ sư
Mục lục
Cái tranh luận mà bạn thua trước khi kịp mở miệng
Bạn đã bao giờ tranh luận với một người cực giỏi debate chưa? Mình rồi. Cảm giác rất tệ. Bạn liên tục bị đẩy khỏi thế chủ động, phải phản ứng với một luận điểm mà bạn không lường trước. Bạn thấy mình đang cố bảo vệ mấy cái rìa yếu ớt của lập luận, trong khi cái luận điểm chính đã bị bỏ lại phía sau từ lúc nào. Rồi bạn mất đà, mất tự tin, và cuối cùng là thua.
Một người bạn thân của mình hồi đại học từng đi thi debate quốc tế cho vui (giờ anh ấy là barrister hình sự khá thành công). Anh ấy nói với mình một câu mà mình nhớ mãi: bí quyết duy nhất của bộ môn này, sau khi bóc hết mọi kỹ thuật, là làm sao để cuộc hội thoại được định khung theo cách của mình. Khi cái khung đó đã hình thành, phần còn lại chỉ là thủ tục.
Tom Renner viết bài “The sound of inevitability” đúng vào cái khoảnh khắc anh ấy nhận ra mình đang ở trong một cuộc tranh luận đã bị định khung sẵn, và người định khung không phải kỹ sư.
Inevitabilism: một từ vay mượn, một vũ khí hoàn hảo
Đầu năm nay Tom đọc cuốn The Age of Surveillance Capitalism của Giáo sư Shoshana Zuboff (Harvard). Một trong những thành tựu lớn nhất của cuốn sách này là nó đặt tên được cho những hiện tượng trừu tượng mà rất nhiều người cảm thấy nhưng không diễn đạt nổi. Khi bạn có tên gọi cho một thứ, bạn có thể xây dựng lập luận về nó, giải thích nó cho người lạ, và quan trọng nhất là tập hợp được phe đối lập xung quanh nó.
Từ khóa ở đây là Inevitabilism (chủ nghĩa tất yếu). Khi bạn đọc The Age of Surveillance Capitalism, bạn nên thấy rằng cái bẫy này không phải phát minh của giới LLM. Nó đã được vận hành trơn tru suốt hai thập kỷ trước đó, chỉ là mục tiêu khác: thay vì “dữ liệu hành vi sẽ bị thu thập, hãy chấp nhận đi”, thì giờ là “AI sẽ viết code của bạn, hãy chấp nhận đi”.
Cơ chế của Inevitabilism gồm hai tầng, và tầng thứ hai mới là tầng nguy hiểm:
- Tầng một - tuyên bố tương lai: Người theo chủ nghĩa tất yếu khẳng định rằng tương lai họ nhìn thấy nhất định sẽ đến. Không phải “có thể”, không phải “nếu chúng ta chọn như vậy”. Nhất định.
- Tầng hai - đóng lại không gian thảo luận: Vì tương lai đó là tất yếu, cách phản ứng duy nhất còn được coi là hợp lý là chuẩn bị cho nó tốt nhất có thể. Câu hỏi “chúng ta có thực sự muốn tương lai này không?” bị thay bằng “làm sao để thích nghi với nó?”.
Tầng hai chính là cái khiến Inevitabilism trở thành một kỹ thuật tranh luận gần như hoàn hảo. Bất kỳ ai nhìn tương lai khác đi đều bị gạt phắt sang một bên bằng một câu duy nhất: “anh đang phớt lờ thực tế”. Và những cuộc hội thoại duy nhất còn đáng tham gia là những cuộc hội thoại đã chấp nhận tiền đề của bạn từ đầu. Cái khung đã được cài đặt. Phần còn lại chỉ là thủ tục.
Note
Để ý tầng tâm lý thứ ba, loại tinh vi hơn: giọng điệu. Inevitabilism luôn đi kèm một áp lực tâm lý nhẹ nhàng nhắc bạn rằng hãy thuận theo dòng chảy, vì nếu không bạn đang đùa giỡn với những thứ vượt xa tầm hiểu biết của bạn. Cảm giác “bị bỏ lại phía sau” không phải là hệ quả phụ của thông điệp. Nó chính là thông điệp.
Ba câu nói định khung cả một ngành
Tom trích nguyên văn vài phát ngôn. Mình muốn treo chúng lên đây để bạn nhìn kỹ cấu trúc, không phải để chỉ trích cá nhân ai:
“Chúng ta đang bước vào một thế giới nơi chúng ta sẽ học cách cùng tồn tại với AI, không phải với tư cách chủ nhân, mà với tư cách cộng sự.” - Mark Zuckerberg
“AI là điện lực mới.” - Andrew Ng
“AI sẽ không thay thế con người, nhưng những người biết dùng AI sẽ thay thế những người không dùng.” - Ginni Rometty
Và phiên bản mới sắc hơn cả: Sam Altman với “AGI trong vài nghìn ngày”, một con số đủ xa để không ai kiểm chứng được, nhưng đủ gần để tạo cảm giác đếm ngược.
Đọc lại cấu trúc bên trong ba câu này. “AI là điện lực mới” không phải là một mô tả kỹ thuật. Điện lực là thứ tất yếu, không thể tránh khỏi, ai không dùng điện thì bị loại khỏi nền kinh tế. Câu này mượn uy tín của một lịch sử đã kết thúc để áp đặt một tương lai chưa xảy ra. “Những người biết dùng AI sẽ thay thế những người không dùng” là công thức của chủ nghĩa tất yếu ở dạng nguyên chất: nó không nói về việc ngành này sẽ đi tới đâu, nó nói với bạn rằng bạn sẽ bị thay thế nếu không lên tàu. Còn “cùng tồn tại như cộng sự” thì thú vị ở chỗ nó là hình ảnh hai bên ngang hàng, trong khi thực tế bạn là người trả tiền cho token.
Kinh tế học bên dưới: vì sao phải tạo ra ‘sự tất yếu’
Đây là chỗ cần bóc tách bằng con số khô khan, vì Inevitabilism không phải là một trò chơi tâm lý vô cớ. Nó là một chiến lược kinh doanh có bảng cân đối kế toán.
Big Tech đã đổ hàng trăm tỷ USD CapEx vào datacenter và GPU trong vài năm trở lại đây: Nvidia H100/H200, các cụm Blackwell, quang học interconnect, hạ tầng làm mát bằng nước. Đây không phải chi phí nhỏ. Chi phí khấu hao của GPU rất tàn nhẫn vì thiết bị này vừa mất giá theo thời gian, vừa mất giá theo tiến bộ thế hệ (một Blackwell của năm nay không chỉ cũ đi, nó còn bị thay thế bởi thế hệ tiếp theo). Một chiếc đầu máy xe lửa có thể chạy 40 năm. Một cụm H100 có đúng vài năm để sinh lời trước khi bị thế hệ mới làm cho cạnh tranh không nổi.
Điều đó tạo ra một ràng buộc rất vật lý, không hề mang tính triết học: phải lấp đầy công suất đó bằng nhu cầu compute liên tục.
Với các mảng kinh doanh cũ của họ, việc bán hàng đã ổn định:
- Quảng cáo: tìm kiếm và display vẫn có trần tăng trưởng.
- Cloud truyền thống: gần như commodity, biên lợi nhuận bị ép.
- Điện thoại, hệ điều hành, sàn ứng dụng: không còn dư địa tăng trưởng.
Mảng duy nhất còn chỗ để tăng gấp nhiều lần là bán hoặc cho thuê compute AI. Thế nên sự tất yếu trở thành công cụ marketing rẻ nhất và hiệu quả nhất mà họ có. Nếu mọi kỹ sư tin rằng AI là tương lai không thể đảo ngược, thì:
- Doanh nghiệp bắt đầu mua subscription LLM cho toàn bộ nhân viên, không cần ai đánh giá ROI.
- Kỹ sư tự nguyện chuyển khối lượng lớn công việc lên cloud của họ (vì model mạnh nhất chạy ở đó), mở ra dòng doanh thu tính theo token có biên lợi nhuận cao hơn hẳn cloud IaaS.
- Các startup và đối thủ cạnh tranh buộc phải cũng làm theo, tạo hiệu ứng đua vũ trang, và chi phí đó lại đổ về cùng một vài cái datacenter.
Nói gọn: Inevitabilism không chỉ là tâm lý lãnh đạo, nó là hàng tồn kho đang cần hấp thụ. Ai hiểu điều này sẽ hiểu vì sao những bài kiểu “liệu chúng ta có nên dùng LLM cho việc này không?” hiếm khi được tiếp nhận như một cuộc thảo luận kỹ thuật. Nó bị tiếp nhận như một sự kháng cự.
Trong khi đó, bên phía kỹ sư
Tháng 6/2026, một bài viết lan truyền: “LLMs are eroding my software engineering career and I don’t know what to do”. Tác giả là một kỹ sư backend, làm việc chuyên sâu trong domain finance và payment processing. Mình khuyên bạn nên đọc nguyên bài, vì đây không phải một bài than thở. Nó là một case study có cấu trúc: tác giả mô tả ba trụ cột tạo nên danh tính chuyên môn của mình, và từng trụ cột sụp đổ theo thứ tự, có mốc thời gian.
Để ý sự trùng hợp đáng sợ: ba trụ cột này chính là ba thứ mà cuốn sách của Zuboff và bài của Renner đang nói tới ở tầng vĩ mô, được đo ở tầng vi mô của một người. Một bên là định khung tương lai, một bên là tương lai đó đang diễn ra trong một career.
Trụ cột một: kiến thức domain trở thành commodity
Tác giả học được rất nhiều về domain: PCI-DSS compliance, double-entry ledger, escrow, reconciliation, payment lifecycle, idempotency cho bank transfer. Những thứ này không phải đọc blog cuối tuần là biết. Đó là loại hiểu biết chỉ hình thành sau nhiều năm tự tay xử lý các situation thực tế, thường là sau khi đã làm sai vài lần.
Điểm gãy đến từ một cuộc nói chuyện với manager: “anh deliver code với tốc độ tốt, nhưng anh viết Design Docs lâu quá. Anh có dùng AI không? Anh nên dùng AI nhiều hơn.” Một yêu cầu quản lý hoàn toàn hợp lý về hình thức. Nhưng nó thúc đẩy tác giả dùng AI vào đúng chỗ mà anh ấy tin là bất khả thay thế: cái phần kết nối các mảnh rời rạc của domain lại với nhau để tạo ra một thiết kế đúng.
Kết quả: model làm được. Không phải làm được một cách hoàn hảo, nhưng làm được ở mức đủ tốt để cái lợi thế cạnh tranh biến mất. Và đây là dòng quan trọng nhất của cả bài viết, mình xin dịch nguyên ý tác giả: toàn bộ kiến thức anh ấy tích lũy - những trade-off giữa các cách triển khai, cách hoạt động của acquiring, cách cấu trúc idempotency để chống double-charge, tất cả - trở nên vô dụng. Giờ nó là thứ có thể prompt được.
Chữ “promptable” này đáng để dừng lại. Về mặt kinh tế học, một kỹ năng có giá trị vì nó khan hiếm. Promptable có nghĩa là nó có thể được tái tạo với chi phí cận biên gần bằng tiền token. Mọi thứ promptable đều bị ép giá về sàn của chi phí inference.
Trụ cột hai: debug hệ thống phân tán
Tác giả nhận ra điều này trước cả khi AI viết được code, nên anh ấy nghĩ về nó như cái phao cuối cùng và giữ nó làm vé đảm bảo employability. Debug race conditions, deadlocks trong production, các hệ thống distributed thiếu observability, những bug mà anh nói rõ là “trước đây tôi không giải được”.
Rồi đường cong diễn ra theo đúng cái cách mà các bạn backend nên chú ý:
- Claude 4.5: giải được khoảng 60% bug khi cho stack trace kèm ngữ cảnh, thường là một Sentry link cộng Sentry MCP. Đôi khi đưa ra giải pháp nghe rất hợp lý nhưng sai hoàn toàn.
- Đến 4.6, 4.7, GPT 5.5, Opus 4.8, cộng MCP cho DataDog: tỉ lệ one-shot nhảy lên mức 90%, bao gồm race conditions kỳ dị, corner case không lường trước, và đặc biệt là third-party integration issues - đúng cái vùng mà tài liệu không bao giờ đầy đủ nên LLM lẽ ra không thể “học” được. Chính đây là điểm phản trực giác: tài liệu có sẵn trên internet. Crawl được. Fine-tune được.
Ghi chú của tác giả - “bugs across distributed systems that lack distributed observability” - là một chi tiết thú vị, vì nó nói rằng model không chỉ giải bug có log tốt. Nó giải cả những bug mà con người phải mò. Mình vẫn nghĩ kết luận này cần đọc cẩn thận hơn tác giả trình bày: model không có intuition về hệ thống, nó có một dạng tương quan rất mạnh giữa pattern code và symptom. Nhưng với mục đích kinh tế, hai thứ đó tạo ra cùng một kết quả. Tác giả tự kết luận về bản thân: “Tôi giờ chỉ là một kỹ sư mua sẵn ngoài kệ. Tôi không còn domain expertise nào mà một Sr. engineer đứng steer LLM không sánh được.”
Trụ cột ba: chất lượng code và kiến trúc, bị hạ cấp thành “taste”
Trụ cột cuối là chỗ mình thấy quan trọng nhất về mặt kỹ thuật, và cũng là chỗ dễ bị bỏ qua nhất. Tác giả dành cả sự nghiệp cho Clean Architecture, Hexagonal, DDD, SOLID, refactor, viết ADR. Rồi anh nhận ra hai điều cùng lúc:
- Agent làm rất kém việc giữ codebase có tổ chức. Nếu không steer, chúng sẽ đâm vào circular dependency rất nhanh, duplicate code, comment vô nghĩa, trộn lẫn pure function với side effect, phớt lờ SOLID.
- Nhưng ngành không còn đánh giá cao việc chặn điều đó bằng một codebase hạng A. C code hay D code giờ đã đủ tốt. Vì code được viết ra cho LLM đọc, không phải cho con người đọc.
Mình không muốn vội vàng gọi cái kết luận thứ hai là đúng. Nhưng có một phiên bản của nó mình đã thấy tận mắt nhiều lần và nó rất đáng sợ: khi reviewer là một agent khác, việc “dễ đọc cho người” mất đi lý do tồn tại kinh tế. Và khi lý do đó mất đi, các ràng buộc về khả năng bảo trì ngắn hạn vẫn còn, nhưng cái động lực để tuân thủ chúng thì không.
Đây là chỗ tác giả chạm vào một vấn đề nhân sự rất thật: anh ấy nhận ra mình vẫn được tuyển, nhưng mình trở thành một commodity. Công ty anh nói chuyện tuyển dụng đã bỏ chữ “Software Engineer - Area” và chuyển thành “Software Engineer” thuần túy, team assignment xác định sau khi ký offer. Đó là dấu hiệu của một thị trường lao động đang chuẩn hóa. Vấn đề không phải là bạn mất việc. Vấn đề là bạn mất lane.
Warning
Cái bẫy ở đây là Inevitabilism không nói với bạn rằng bạn sẽ bị sa thải, nó chỉ nói với bạn rằng bạn nên lo lắng. Nỗi lo đó là thứ dẫn đến chương tiếp theo: nhồi LLM vào mọi ngóc ngách của hệ thống, không phải vì kiến trúc cần, mà vì bản năng sinh tồn cần.
Cái giá kỹ thuật của việc nuốt trọn Inevitabilism
Giờ đến phần mà mình thực sự muốn nói với các bạn backend. Khi một kỹ sư hoảng, họ bắt đầu đặt LLM ở những chỗ mà đúng ra không cần LLM: validator, rule engine, cronjob, search filter, ETL pipeline, chuyển đổi trạng thái. Lý do rất đời thường: “cần bằng chứng là mình đang dùng AI”.
Chuyện này có một cái tên trong system design. Nó là generalization overreach - dùng một công cụ tổng quát xác suất cho một bài toán xác định. Và cái giá thể hiện trên bốn trục đo được:
1. Non-determinism không có trần lỗi
Một regexp hay một finite state machine cho kết quả giống hệt nhau ở cùng input, mãi mãi (bỏ qua vấn đề Unicode normalization và locale). Về mặt nhận thức, đây là một tính chất cực mạnh: bạn có thể suy luận về nó, bạn có thể chứng minh nó, bạn có thể dùng nó như giả định trong các bước lập luận phía sau.
Một LLM call có tỉ lệ sai mà bạn không biết chính xác là bao nhiêu tại từng thời điểm. Temperature 0 không đảm bảo kết quả ổn định: batching trên GPU, thứ tự rút gọn của floating point, và việc provider âm thầm cập nhật model đều phá vỡ tính bất biến. Nói cách khác, bạn đang trao một property thuộc loại “đúng ở mọi lần chạy” cho một thành phần chỉ đúng “phần lớn số lần chạy”. Trong một hệ thống giao dịch, đó không phải đánh đổi về tốc độ viết code. Đó là đánh đổi về loại hệ thống bạn đang vận hành.
2. Latency: từ sub-millisecond lên hàng giây, và cái tail mới là thứ giết bạn
Con số cụ thể: một vòng lặp validation trong Go trên một struct nhỏ chạy ở mức microsecond tới sub-millisecond. Trong cùng máy, không network hop. Một LLM call, dù model nhỏ, là:
- Ít nhất một network round-trip: 20 đến 300ms tùy vị trí, và vị trí đó thường xuyên là cross-region vì model chỉ chạy ở một vài datacenter.
- Queueing ở phía provider: khi provider đầy tải, latency tăng theo bậc chứ không tuyến tính, và bạn không kiểm soát được.
- Thời gian sinh token, scale theo số token output chứ không scale theo độ khó của bài toán.
Trung bình đã đau, nhưng p99 mới là chỗ kiến trúc của bạn sụp. Các nhà cung cấp LLM có tail latency rất nặng, và bạn không có capacity planning nào ở phía mình. Nghĩa là các timeout budget ở caller của bạn, cái mà bạn đã dựng cẩn thận sau nhiều lần postmortem, giờ bị quyết định bởi một hệ thống bên ngoài mà bạn không có SLO. Trong kiến trúc có nhiều tầng gọi nhau, một thành phần có p99 gấp hàng chục lần các thành phần khác sẽ định hình lại toàn bộ con đường tới hạn, kéo theo cả những chỗ mà bạn tưởng “không liên quan”.
3. State drift và cơn ác mộng debug
Đây là hệ quả nghiêm trọng nhất và ít được nói tới nhất. Một deterministic core có thể được kiểm chứng bằng invariant: tổng debit bằng tổng credit, số dư luôn khớp, không có âm tiền âm thầm. Invariant là loại bảo chứng mạnh vì nó kiểm tra được ở mọi thời điểm, không chỉ ở test.
Khi LLM nằm trên đường tới hạn của state transition, bạn mất khả năng đó. Từng bước đơn lẻ có thể trông hợp lý, nhưng sai số tích lũy mà không ai phát hiện. Và cái khó chịu nhất là bạn không reproduce được bug. Bạn có một giao dịch sai trong database, không log nào cho bạn biết model đã “quyết định” gì ở lần chạy đó, và chạy lại cùng input sẽ cho kết quả khác. Với một hệ thống deterministic, debug là tái hiện lại path và đọc trace. Với LLM ở giữa, debug là xác suất. Bạn không debug nữa, bạn thu thập bằng chứng.
4. Khả năng đảo ngược
Một phần của cái giá không nằm ở runtime mà nằm ở migration. Khi bạn đẩy LLM vào một lớp mà lẽ ra là logic xác định, việc thay đổi model, thay đổi prompt, thay đổi version API đều trở thành một cuộc migration đầy rủi ro với blast radius bạn không đo được. Trong kiến trúc tốt, thay đổi implementation của một component không nên làm thay đổi contract. Ở đây, thay đổi model version thay đổi contract một cách mơ hồ.
Nguyên tắc thiết kế: Deterministic Core, Probabilistic Edge
Mình không có ý nói “đừng dùng LLM”. Mình đang nói một điều hẹp hơn và có thể kiểm chứng: LLM rất mạnh ở rìa, rất nguy hiểm ở lõi. Ranh giới giữa hai vùng đó không phải là một ý kiến thẩm mỹ, nó là một quyết định kiến trúc có thể viết ra được, review được, và bảo vệ được trước cả khi có postmortem.
Tiêu chí phân loại mình hay dùng:
| Tiêu chí | Thuộc lõi (deterministic) | Thuộc rìa (probabilistic) |
|---|---|---|
| Tính bất biến | Bắt buộc | Không cần |
| Có bảo chứng bằng invariant | Có | Không |
| Liên quan tới tiền | Có | Không |
| Cần audit trail đầy đủ | Có | Không |
| Chấp nhận được “đúng 95%” | Không | Có |
| Bug ở đây gây mất dữ liệu | Có | Không |
Sơ đồ dưới đây so sánh hai luồng. Bên trái là kiến trúc sai (all-in LLM trên đường tới hạn). Bên trái mô tả một core sạch với LLM nằm ngoài rìa.
graph TD
subgraph "Anti-pattern: All-in LLM"
A1["`HTTP Request
POST /payments`"] -->|"network hop 30-300ms"| A2["`LLM Classifier
quyết định loại giao dịch`"]
A2 -->|"network hop, p99 không đo được"| A3["`LLM Validator
kiểm tra rule nghiệp vụ`"]
A3 -->|"2% hallucination"| A4["`Postgres Write
không có invariant check`"]
A4 --> A5["`State drift
không reproduce được bug`"]
end
graph TD
subgraph "Deterministic Core"
B1["HTTP Request"] --> B2["`Validate & Decode
struct tags, in-memory, ~5us`"]
B2 --> B3["`State Machine
transition hợp lệ theo bảng compile-time`"]
B3 --> B4["`Postgres Transaction
SERIALIZABLE + UNIQUE constraint`"]
B4 --> B5["`Outbox Event
ghi cùng transaction`"]
end
subgraph "Probabilistic Edge"
B5 -->|"async, ngoài đường tới hạn"| C1["`Log Analyzer
tóm tắt bất thường`"]
C2["`NL Query Interface
hỏi bằng tiếng người`"] -->|"read-only, dữ liệu đã tổng hợp"| B4
end
Điểm cốt lõi của sơ đồ thứ hai: LLM vẫn có mặt, vẫn đem lại giá trị, nhưng nó chạm vào hệ thống theo hai cách an toàn. Một, chỉ đọc dữ liệu đã được tổng hợp sẵn. Hai, chỉ ghi ra ngoài đường tới hạn và không ghi vào state của hệ thống tiền tệ. Không có mũi tên nào đi từ rìa xác suất vào lõi xác định theo hướng ghi.
Code thực chiến: state machine cho vòng đời đơn hàng trong Go
Đủ lý thuyết. Đây là ví dụ mình dùng để chỉ cho các bạn junior trong team thấy sự khác biệt giữa “LLM quyết định” và “code quyết định”.
Bắt đầu bằng cái sai. Đây là kiểu code mình thấy trong review nhiều lần nhất trong vài tháng gần đây:
// ĐỪNG LÀM THẾ NÀY.
// LLM quyết định transition có hợp lệ hay không. Non-deterministic ở lõi.
func (s *OrderService) Transition(ctx context.Context, orderID string, action string) error {
order, err := s.repo.Get(ctx, orderID)
if err != nil {
return err
}
prompt := fmt.Sprintf(
"Đơn hàng đang ở trạng thái %q. Hành động yêu cầu là %q. "+
"Trả về JSON {\"allowed\": bool}.",
order.Status, action,
)
resp, err := s.llm.Complete(ctx, prompt)
if err != nil {
return err
}
var d struct{ Allowed bool `json:"allowed"` }
if err := json.Unmarshal([]byte(resp), &d); err != nil {
return err
}
if !d.Allowed {
return ErrInvalidTransition
}
// Không có transaction, không có locking, không có audit trail thật.
return s.repo.UpdateStatus(ctx, orderID, action)
}
Có bốn vấn đề độc lập trong đoạn này, và không vấn đề nào là “chất lượng code”. Chúng là vấn đề semantics:
- Race condition.
GetrồiUpdateStatuskhông phải một transaction. Hai request đồng thời đều có thể đọcStatusPending, cả hai đều thấy hợp lệ, và cả hai ghi. Bug này không liên quan tới LLM, nhưng LLM làm nó khó thấy hơn. - Không có nguồn sự thật duy nhất. Tập transition hợp lệ sống trong prompt, tức là nằm ngoài cả code và cả schema. Không ai review được nó. Không có type checking. Không có test.
- Không kiểm chứng được. Bạn không thể viết một invariant test cho “chỉ chuyển từ Pending sang Canceled nếu chưa capture tiền”, vì cái logic đó chưa bao giờ được viết ra ở dạng kiểm tra được.
- Prompt drift. Provider cập nhật model là nghiệp vụ của bạn đổi âm thầm.
Giờ là phiên bản đúng. Transition table được định nghĩa ở compile-time, đóng xuống database, và bảo vệ bằng optimistic concurrency.
package order
import (
"context"
"database/sql"
"errors"
"fmt"
)
type Status string
const (
StatusPending Status = "pending"
StatusPaid Status = "paid"
StatusShipped Status = "shipped"
StatusDelivered Status = "delivered"
StatusCanceled Status = "canceled"
)
// transitionRules là nguồn sự thật duy nhất.
// Map literal được compiler kiểm tra kiểu. Không có chuỗi magic rải rác.
var transitionRules = map[Status]map[Status]struct{}{
StatusPending: {
StatusPaid: {},
StatusCanceled: {},
},
StatusPaid: {
StatusShipped: {},
StatusCanceled: {},
},
StatusShipped: {
StatusDelivered: {},
},
// delivered và canceled là trạng thái kết thúc.
}
var ErrInvalidTransition = errors.New("order: transition không hợp lệ")
// ApplyOrderTransition thực hiện đúng một transition, một cách nguyên tử.
// Toàn bộ quyết định "có được phép hay không" là deterministic.
func ApplyOrderTransition(
ctx context.Context,
tx *sql.Tx,
orderID string,
from, to Status,
) error {
if legal, ok := transitionRules[from]; ok {
if _, allowed := legal[to]; allowed {
// UPDATE có điều kiện: WHERE status = $from.
// Nếu có request đồng thời ghi trước, rowsAffected = 0 và ta thất bại
// thay vì ghi đè. Đây là optimistic concurrency, không phải lock.
res, err := tx.ExecContext(ctx, `
UPDATE orders
SET status = $1, updated_at = now()
WHERE id = $2
AND status = $3`,
string(to), orderID, string(from),
)
if err != nil {
return fmt.Errorf("order: update status: %w", err)
}
n, err := res.RowsAffected()
if err != nil {
return err
}
if n == 0 {
// Không phải lỗi hệ thống: đây là tín hiệu một writer khác
// đã thắng cuộc đua. Caller nên đọc lại và quyết định.
return ErrConcurrentModification
}
return nil
}
}
return fmt.Errorf("%w: %s -> %s", ErrInvalidTransition, from, to)
}
Lưu ý ba tính chất của phiên bản này. Thứ nhất, transition table là một map, và type system đảm bảo bạn không gõ sai tên status. Thứ hai, việc kiểm tra “có hợp lệ không” và việc ghi được thực hiện trong cùng một câu lệnh UPDATE có điều kiện, nên race condition giữa hai request không còn tồn tại. Thứ ba, nó không tốn mạng, không tốn token, không có tail latency lạ. Nó chỉ là logic.
Còn tính idempotency? Đây là chỗ đáng để ý vì nó đúng là một vấn đề khó mà LLM có thể “trông có vẻ” giải được. Cách deterministic là để database làm việc đó, không phải để model làm:
// IdempotencyKey được lưu trong bảng riêng với UNIQUE constraint.
// Chèn trùng key trả về lỗi unique violation, và caller biết
// request này là một bản retry của request đã xử lý.
const insertIdempotency = `
INSERT INTO idempotency_keys (key, order_id, response_hash, created_at)
VALUES ($1, $2, $3, now())
ON CONFLICT (key) DO NOTHING`
Ở đây, tính bất biến không được đảm bảo bởi code Go, mà bởi constraint của database. Đây là mức bảo chứng mạnh hơn: nó vẫn đúng kể cả khi có process khác, service khác, hoặc một con bug nào đó ở tầng trên ghi vào cùng bảng. Khi bạn phải chọn giữa việc bảo vệ một invariant bằng code và bằng constraint, hãy chọn constraint ở mọi chỗ có thể.
Và đây là chỗ LLM thực sự có ích trong cùng hệ thống đó:
// logDigest là một hàng đợi phía sau Outbox. Nó chỉ ĐỌC dữ liệu
// đã tổng hợp và ghi ra kênh thông báo cho người vận hành.
// Nó không bao giờ ghi vào orders hay idempotency_keys.
func (d *Digester) SummarizeAnomalies(ctx context.Context, window time.Duration) (string, error) {
events, err := d.repo.RecentFailedTransitions(ctx, window)
if err != nil {
return "", err
}
if len(events) == 0 {
return "", nil
}
// Nếu LLM lỗi hoặc trả rác, ta mất một bản tóm tắt.
// Ta không mất dữ liệu, không mất tiền, không sai state.
return d.llm.Summarize(ctx, events)
}
Đây là toàn bộ luận điểm được nén vào ba hàm. ApplyOrderTransition là lõi, và nó không biết LLM tồn tại. SummarizeAnomalies là rìa, và nó không thể viết vào state. Ranh giới này là thứ bạn cần bảo vệ, không phải năng lực của model.
Những gì Inevitabilism khiến bạn quên hỏi
Sau khi đọc hai bài viết gốc, mình thấy có một danh sách câu hỏi mà cuộc thảo luận công khai gần như không bao giờ chạm tới, vì chúng đã bị cái khung “làm sao thích nghi” đẩy ra ngoài. Mình liệt kê lại ở đây vì đây là loại câu hỏi mà một kỹ sư trưởng nên hỏi trong architecture review:
- Chi phí cận biên của một request là bao nhiêu, và ai chịu chi phí đó? Một quy tắc regex không tính tiền. Một LLM call tính tiền, mỗi lần, mãi mãi. Trong một hệ thống chạy hàng trăm triệu request mỗi ngày, cái đó khác biệt ở mức hạ tầng.
- Hệ thống của bạn có còn đúng không nếu provider ngừng hoạt động trong sáu giờ? Nếu câu trả lời là “không”, bạn đã thêm một dependency phụ thuộc hoàn toàn vào một bên bạn không có hợp đồng SLA và không có khả năng tự chủ.
- Ai chịu trách nhiệm khi kết quả sai? Không phải model. Về mặt kỹ thuật, người chịu trách nhiệm là người viết ADR ghi rằng “chúng tôi quyết định đặt LLM ở đây”. Bạn có muốn ký đúng cái ADR đó không?
- Có ai đo chưa, hay chỉ tin? Nếu không ai có số liệu về tỉ lệ hallucination trên chính bài toán của bạn, thì mọi khẳng định “model giải được 90% bug” đều là giai thoại. Giai thoại không phải là cơ sở để thiết kế hệ thống.
- Kiến thức nào bạn đang vứt đi mà sẽ mất mười năm nữa mới dựng lại được?
Câu cuối cùng là cái mình nghĩ tới nhiều nhất. Có một hiệu ứng mà mình thấy lặp lại ở nhiều team: khi khả năng suy luận từ principles bị bỏ không dùng trong vài năm, tổ chức mất khả năng chẩn đoán. Không phải vì con người kém đi, mà vì tri thức kỹ thuật nằm ở dạng tacit. Nó tồn tại trong những người đã từng đi qua một sự cố production lúc ba giờ sáng. Nếu không có ai đi qua nữa, nó mất theo thế hệ.
Về phía chúng ta: công nghệ không phải định mệnh
Đây là chỗ mình muốn nói thẳng, vì Tom Renner kết thúc bài của anh ấy đúng ở điểm này và mình hoàn toàn đồng ý:
Mình không tin LLM là tương lai. Và mình càng không tin nó là tương lai mình muốn. Nhưng điều mình chắc chắn nhất là chúng ta có lựa chọn, về tương lai nên trông thế nào và về việc dùng máy móc để xây nó ra sao.
Cái lập luận “công nghệ là trung tính” nghe hợp lý nhưng nó bỏ qua một bước. Công nghệ không phải định mệnh; công nghệ là tập hợp các công cụ do con người lựa chọn để giải quyết các ràng buộc thực tế. Mỗi lựa chọn có một nhóm được lợi và một nhóm chịu chi phí. Khi ai đó trình bày một lựa chọn như thể nó là quy luật vật lý, họ đang ẩn đi bước lựa chọn đó. Và bước bị ẩn đi chính là chỗ mà ý chí của bạn bị đánh cắp.
Về phần danh tính kỹ sư, mình nghĩ tác giả của bài viral đang nhìn đúng vấn đề nhưng kết luận quá vội theo hướng bi quan. Ba trụ cột anh ấy mô tả bị xói mòn, nhưng cái trục thứ tư mà anh ấy gần chạm tới lại đang mọc lên:
Khả năng phán đoán về cái gì nên và không nên là xác suất.
Đây không phải kỹ năng “taste” theo nghĩa thẩm mỹ. Đây là kỹ năng kỹ thuật cụ thể: đọc một vấn đề và phân loại được nó thuộc vùng nào. Vấn đề này có phải là bài toán xác định bị ngụy trang thành bài toán ngôn ngữ? Vấn đề này có cần một bảo chứng, hay chỉ cần một ước lượng đủ tốt? Sai ở đây thì phải rollback được, hay là mất dữ liệu vĩnh viễn? Người làm được phân loại đó sẽ là người dựng được kiến trúc đúng, và số người làm được việc đó rất ít khi mọi người đang bận nhét LLM vào mọi chỗ.
Người kỹ sư thực thụ không phải người “chống AI”. Cái nhãn đó là chính Inevitabilism tạo ra, để bạn không có chỗ đứng nào ngoài việc thuận theo. Người kỹ sư thực thụ là người biết rõ khi nào KHÔNG được dùng AI, và có đủ tự tin để viết ADR nói rằng ở tầng này, trên đường tới hạn này, với dữ liệu tiền tệ này, chúng ta dùng một state machine 0.2 mili giây và một UNIQUE constraint, chứ không dùng một cuộc gọi model.
Tip
Một bài kiểm tra nhanh khi bạn review một PR có thêm LLM call. Hỏi: “Nếu thành phần này trả về một kết quả ngẫu nhiên hoàn toàn, hệ thống có hỏng theo cách không khôi phục được không?” Nếu câu trả lời là có, LLM đó đang ở sai tầng. Nếu câu trả lời là “chỉ mất một bản tóm tắt”, bạn đang dùng nó đúng chỗ.
Kết
Cái tên Inevitabilism có giá trị ở chỗ nó cho bạn một từ để gọi tên cơ chế. Khi một cuộc thảo luận bị bẻ từ “chúng ta có muốn cái này không?” thành “làm sao thích nghi với nó?”, đó không phải là một khách quan kỹ thuật đang tự khẳng định. Đó là một kỹ thuật tranh luận, và có một bảng cân đối kế toán đứng sau nó.
Ở phía kỹ thuật, câu trả lời không nằm ở việc chống đối công cụ. Nó nằm ở việc giữ một ranh giới rõ ràng giữa cái lõi xác định và cái rìa xác suất, và có đủ kỷ luật để không xê dịch ranh giới đó vì áp lực tổ chức. State machine của bạn, idempotency key của bạn, constraint của bạn, invariant của bạn: đó là những thứ không phụ thuộc vào việc model có cập nhật hay không.
Và ở phía nghề nghiệp: nếu giá trị của mười năm kinh nghiệm của bạn bị hạ cấp, đừng chạy theo cái khung đã hạ cấp nó. Hãy học thứ mà cái khung đó không đo được. Bởi vì cái khung sẽ còn thay đổi vài lần nữa, còn khả năng phán đoán của bạn thì tích lũy.
Công nghệ không phải định mệnh. Đừng để ai định khung cuộc thảo luận thay bạn.


