20 thg 7, 2025·8 phút đọc

SQLite vs Realm cho lưu trữ offline-first trong ứng dụng hiện trường

So sánh SQLite và Realm cho lưu trữ offline-first trong ứng dụng hiện trường: migrations, tùy chọn truy vấn, xử lý xung đột, công cụ debug và mẹo lựa chọn thực tế.

SQLite vs Realm cho lưu trữ offline-first trong ứng dụng hiện trường

Những gì ứng dụng hiện trường hướng offline-first thực sự cần

Offline-first không chỉ có nghĩa là “hoạt động khi không có internet.” Nó nghĩa là app có thể tải dữ liệu hữu ích, nhận thông tin mới và giữ mọi chỉnh sửa an toàn cho đến khi có thể đồng bộ.

Công việc hiện trường đặt ra một tập ràng buộc dễ dự đoán: sóng chập chờn, phiên làm việc dài, thiết bị có thể cũ, và chế độ tiết kiệm pin phổ biến. Mọi người di chuyển nhanh. Họ mở việc, cuộn qua danh sách dài, chụp ảnh, điền form và nhảy sang công việc tiếp theo mà không nghĩ về lưu trữ.

Những gì người dùng nhận ra là đơn giản. Họ mất niềm tin khi chỉnh sửa biến mất, khi danh sách và tìm kiếm ì ạch khi offline, khi app không trả lời rõ “công việc của tôi đã được lưu chưa?”, khi bản ghi bị trùng hoặc mất khi kết nối lại, hoặc khi một bản cập nhật gây hành vi lạ.

Đó là lý do việc chọn giữa SQLite và Realm chủ yếu là về hành vi hàng ngày, không phải benchmark.

Trước khi bạn chọn cơ sở dữ liệu cục bộ, hãy làm rõ bốn điểm: mô hình dữ liệu của bạn sẽ thay đổi, truy vấn phải phù hợp với luồng công việc thực, đồng bộ offline sẽ tạo xung đột, và công cụ sẽ quyết định bạn chẩn đoán lỗi hiện trường nhanh đến đâu.

1) Dữ liệu của bạn sẽ thay đổi

Ngay cả ứng dụng ổn định cũng tiến hóa: thêm trường mới, đổi tên trạng thái, màn hình mới. Nếu thay đổi mô hình đau đầu, bạn hoặc là gửi ít cải tiến hơn, hoặc mạo hiểm làm hỏng thiết bị thực với dữ liệu thực.

2) Truy vấn phải phù hợp với luồng công việc thực

Ứng dụng hiện trường cần bộ lọc nhanh như “việc hôm nay”, “công trình gần đây”, “form chưa đồng bộ”, và “mục chỉnh sửa trong 2 giờ qua.” Nếu cơ sở dữ liệu khiến những truy vấn đó lằng nhằng, UI sẽ chậm hoặc code sẽ trở nên rối.

3) Đồng bộ offline tạo xung đột

Hai người có thể sửa cùng một bản ghi, hoặc một thiết bị sửa dữ liệu cũ trong nhiều ngày. Bạn cần kế hoạch rõ về cái gì thắng, cái gì hợp nhất, và cái gì cần quyết định của con người.

4) Công cụ quan trọng

Khi có sự cố ở hiện trường, bạn cần kiểm tra dữ liệu, tái tạo lỗi và hiểu chuyện gì đã xảy ra mà không phỏng đoán.

Migrations: thay đổi mô hình dữ liệu mà không làm hỏng người dùng

Ứng dụng hiện trường hiếm khi đứng yên. Sau vài tuần, bạn thêm checkbox, đổi tên trạng thái hoặc tách trường “notes” thành các trường có cấu trúc. Migrations là nơi ứng dụng offline thường thất bại, vì điện thoại đã chứa dữ liệu thực.

SQLite lưu dữ liệu trong bảng và cột. Realm lưu dưới dạng object với thuộc tính. Sự khác biệt này hiện ra nhanh:

  • Với SQLite, bạn thường viết thay đổi schema rõ ràng (ALTER TABLE, tạo bảng mới, copy dữ liệu).
  • Với Realm, bạn thường tăng phiên bản schema và chạy hàm migration để cập nhật object khi chúng được truy cập.

Thêm trường thì dễ ở cả hai hệ thống: thêm cột trong SQLite, thêm thuộc tính với giá trị mặc định trong Realm. Đổi tên và tách trường mới là phần đau đầu. Trong SQLite, đổi tên có thể bị giới hạn tùy cài đặt, nên đội thường tạo bảng mới và copy dữ liệu. Trong Realm, bạn có thể đọc thuộc tính cũ và ghi sang thuộc tính mới trong migration, nhưng phải cẩn thận với kiểu dữ liệu, giá trị mặc định và null.

Cập nhật lớn trên dữ liệu trên thiết bị cần thận trọng. Một migration ghi lại mọi bản ghi có thể chậm trên điện thoại cũ, và kỹ thuật viên không nên bị kẹt nhìn spinner trong bãi đỗ. Lên kế hoạch thời gian migration, và cân nhắc chia các biến đổi nặng qua nhiều bản phát hành.

Để kiểm thử migrations công bằng, hãy coi chúng như sync:

  • Cài build cũ, tạo dữ liệu thực tế, rồi nâng cấp.
  • Kiểm thử với dataset nhỏ và lớn.
  • Kill app giữa lúc migration và khởi chạy lại.
  • Kiểm thử kịch bản bộ nhớ thấp.
  • Giả sử bạn có thể tiến lên (roll forward) ngay cả khi không thể rollback.

Ví dụ: nếu “equipmentId” thành “assetId” rồi sau đó tách thành “assetType” và “assetNumber”, migration nên giữ cho các kiểm tra cũ vẫn dùng được, không ép người dùng logout hay wipe dữ liệu.

Linh hoạt truy vấn: bạn có thể hỏi gì từ dữ liệu

Ứng dụng hiện trường sống hoặc chết dựa vào màn hình danh sách: việc hôm nay, tài sản gần đó, khách hàng có ticket mở, phụ tùng dùng trong tuần. Lựa chọn lưu trữ nên khiến những câu hỏi đó dễ diễn đạt, chạy nhanh và khó bị hiểu sai sáu tháng sau.

SQLite cho bạn SQL, vẫn là cách linh hoạt nhất để lọc và sắp xếp tập dữ liệu lớn. Bạn có thể kết hợp điều kiện, join giữa các bảng, group kết quả và thêm index khi màn hình chậm. Nếu app cần “tất cả kiểm tra cho tài sản ở Vùng A, giao cho Đội 3, có bất kỳ mục kiểm tra nào bị fail”, SQL thường diễn đạt được một cách gọn.

Realm nghiêng về object và API truy vấn cao hơn. Với nhiều app, cảm giác tự nhiên: truy vấn Job object, lọc theo trạng thái, sắp xếp theo ngày hạn, theo quan hệ tới object liên quan. Đổi lại, một số câu hỏi đơn giản trong SQL (nhất là truy vấn kiểu báo cáo qua nhiều quan hệ) có thể khó diễn đạt, hoặc bạn phải định hình lại dữ liệu để phù hợp truy vấn.

Tìm kiếm và quan hệ

Với tìm kiếm text một phần qua nhiều trường (tiêu đề công việc, tên khách hàng, địa chỉ), SQLite thường thúc bạn dùng index cẩn thận hoặc giải pháp full-text chuyên dụng. Realm cũng có lọc text, nhưng bạn vẫn phải nghĩ về hiệu năng và ý nghĩa của “contains” ở quy mô lớn.

Quan hệ là một điểm đau thực tế khác. SQLite xử lý one-to-many và many-to-many với bảng liên kết, làm mẫu như “tài sản có tag A và B” dễ thực hiện. Liên kết trong Realm dễ điều hướng trên code, nhưng many-to-many và kiểu “query qua” thường cần lên kế hoạch thêm để giữ đọc nhanh.

Truy vấn thô vs bảo trì dễ đọc

Một mẫu dễ bảo trì là giữ một tập truy vấn được đặt tên nhỏ map trực tiếp tới màn hình và báo cáo: bộ lọc danh sách chính, truy vấn view chi tiết (một bản ghi cộng các bản ghi liên quan), định nghĩa tìm kiếm, vài bộ đếm (badge và tổng offline), và các truy vấn xuất/báo cáo.

Nếu bạn mong câu hỏi ad hoc thường xuyên từ bộ phận nghiệp vụ, sức mạnh truy vấn thô của SQLite khó bị đánh bại. Nếu bạn muốn phần lớn truy cập dữ liệu đọc như làm việc với object bình thường, Realm có thể xây dựng nhanh hơn, miễn là nó trả lời được các màn hình khó nhất mà không phải lách quá nhiều.

Giải quyết xung đột và sync: hỗ trợ bạn nhận được là gì

Ứng dụng offline-first thường cho phép các hành động lõi khi ngắt kết nối: tạo bản ghi, cập nhật bản ghi, xóa cái sai. Phần khó không phải lưu cục bộ. Là quyết định điều gì xảy ra khi hai thiết bị thay đổi cùng một bản ghi trước khi bất kỳ ai sync.

Xung đột xuất hiện trong tình huống đơn giản. Một kỹ thuật viên cập nhật kiểm tra trên tablet trong tầng hầm không có sóng. Sau đó, trưởng nhóm sửa cùng kiểm tra trên laptop. Khi cả hai reconnect, server nhận hai phiên bản khác nhau.

Hầu hết đội chọn một trong các cách sau:

  • Last write wins (nhanh, nhưng có thể ghi đè dữ liệu tốt một cách âm thầm)
  • Merge theo trường (an toàn hơn khi các trường khác nhau thay đổi, nhưng cần luật rõ)
  • Hàng đợi duyệt thủ công (chậm nhất, tốt nhất cho thay đổi rủi ro cao)

SQLite cho bạn cơ sở dữ liệu cục bộ tin cậy, nhưng nó không cung cấp sync tự thân. Bạn thường phải tự xây phần còn lại: theo dõi hoạt động chờ, gửi lên API, retry an toàn và thi hành quy tắc xung đột trên server.

Realm có thể giảm khối lượng plumbing nếu bạn dùng tính năng sync của nó, vì nó được thiết kế quanh object và tracking thay đổi. Nhưng “sync tích hợp” vẫn không chọn quy tắc nghiệp vụ cho bạn. Bạn quyết định cái gì là xung đột và dữ liệu nào được phép thắng.

Lên kế hoạch một trail audit từ đầu. Đội hiện trường thường cần trả lời rõ “ai thay đổi gì, khi nào và từ thiết bị nào.” Ngay cả khi bạn chọn last write wins, hãy lưu metadata như user ID, device ID, timestamps và (khi có thể) lý do. Nếu backend của bạn sinh nhanh, ví dụ dùng nền tảng no-code như AppMaster, bạn dễ dàng lặp các quy tắc này sớm trước khi có hàng trăm thiết bị offline ngoài hoang dã.

Debug và kiểm tra: phát hiện lỗi trước khi hiện trường thấy

Xây dựng giải pháp toàn diện
Khởi tạo backend, bảng quản trị và ứng dụng di động như một hệ thống thống nhất, không tách rời.
Tạo ứng dụng

Lỗi offline khó vì xảy ra khi bạn không thể theo dõi app nói chuyện với server. Trải nghiệm debug thường phụ thuộc vào một câu hỏi: bạn có thể nhìn thấy gì trên thiết bị và nó thay đổi như thế nào theo thời gian dễ đến đâu?

SQLite dễ kiểm tra vì nó là một file. Trong dev hoặc QA bạn có thể kéo database từ thiết bị test, mở bằng công cụ SQLite phổ biến, chạy truy vấn ad hoc và xuất bảng ra CSV hoặc JSON. Điều này giúp xác nhận “những hàng nào tồn tại” so với “UI hiển thị gì”. Hạn chế là bạn cần hiểu schema, joins và bất kỳ scaffolding migration nào bạn tạo.

Realm có thể cho cảm giác dễ duyệt như một app. Dữ liệu lưu dưới dạng object, và tooling của Realm thường là cách dễ nhất để duyệt class, thuộc tính và quan hệ. Nó tốt để phát hiện vấn đề đồ thị object (liên kết thiếu, null bất ngờ), nhưng phân tích ad hoc kém linh hoạt nếu đội bạn quen với kiểm tra dựa trên SQL.

Ghi log và tái tạo lỗi offline

Hầu hết thất bại ở hiện trường liên quan đến lỗi ghi im lặng, batch sync một phần, hoặc migration chỉ hoàn thành nửa vời. Dù sao, hãy đầu tư vào vài thứ cơ bản: timestamp “last changed” cho mỗi bản ghi, nhật ký hoạt động phía thiết bị, log có cấu trúc quanh migration và ghi nền, cách bật logging chi tiết trong build QA, và một hành động “dump và chia sẻ” xuất snapshot đã được tẩy bớt nhạy cảm.

Ví dụ: một kỹ thuật viên báo rằng kiểm tra hoàn thành biến mất sau khi pin tụt. Một snapshot chia sẻ giúp bạn xác nhận bản ghi chưa bao giờ được ghi, đã ghi nhưng không được truy vấn, hay bị rollback khi khởi động.

Chia sẻ snapshot lỗi

Với SQLite, chia sẻ thường đơn giản như chia sẻ file .db (cộng với file WAL nếu có). Với Realm, bạn thường chia file Realm cùng các file phụ. Trong cả hai trường hợp, định nghĩa quy trình lặp để loại bỏ dữ liệu nhạy cảm trước khi bất cứ thứ gì rời khỏi thiết bị.

Độ tin cậy trong thực tế: lỗi, reset và nâng cấp

Ứng dụng hiện trường thất bại theo những cách tẻ nhạt: pin chết giữa lúc lưu, OS kill app nền, hoặc bộ nhớ đầy sau vài tuần ảnh và log. Lựa chọn cơ sở dữ liệu cục bộ ảnh hưởng tần suất những lỗi đó biến thành mất việc.

Khi crash xảy ra giữa lúc ghi, cả SQLite và Realm có thể an toàn nếu dùng đúng cách. SQLite đáng tin khi bạn bọc thay đổi trong transaction (chế độ WAL giúp bền và hiệu năng). Realm viết theo giao dịch theo mặc định, nên thường được “all or nothing” mà không cần thao tác thêm. Rủi ro phổ biến không phải engine DB, mà là code app ghi nhiều bước mà không có điểm commit rõ ràng.

Corruption hiếm, nhưng bạn vẫn cần kế hoạch phục hồi. Với SQLite, bạn có thể chạy integrity check, khôi phục từ backup tốt, hoặc xây lại từ resync server. Với Realm, khi file bị nghi ngờ bị hỏng, đường phục hồi thực tế thường là “xóa cục bộ và resync” (ổn nếu server là nguồn chân lý, đau nếu thiết bị giữ dữ liệu duy nhất).

Tăng trưởng bộ nhớ là bất ngờ khác. SQLite có thể phình sau khi xóa trừ khi bạn vacuum định kỳ. Realm cũng có thể to lên và cần chính sách compact, cùng với pruning object cũ (như công việc đã hoàn thành) để file không phình mãi.

Nâng cấp và rollback là bẫy khác. Nếu update thay đổi schema hoặc định dạng lưu trữ, rollback có thể bỏ lại người dùng trên file mới mà bản cũ không đọc được. Lên kế hoạch nâng cấp một chiều, với migration an toàn và tùy chọn “reset local data” không phá vỡ app.

Thói quen độ tin cậy mang lại hiệu quả:

  • Xử lý “đĩa đầy” và lỗi ghi với thông báo rõ và đường retry.
  • Lưu input người dùng ở checkpoint, không chỉ ở cuối form dài.
  • Giữ nhật ký audit nhẹ phía cục bộ cho khôi phục và hỗ trợ.
  • Prune và archive bản ghi cũ trước khi DB phình quá lớn.
  • Kiểm thử upgrade OS và bị kill nền trên thiết bị cấu hình thấp.

Ví dụ: app kiểm tra lưu checklist và ảnh có thể đạt bộ nhớ thấp trong một tháng. Nếu app nhận diện sớm bộ nhớ ít, nó có thể tạm dừng chụp ảnh, upload khi có thể, và giữ lưu checklist an toàn, bất kể bạn dùng DB gì.

Từng bước: cách chọn và thiết lập lưu trữ

Kiểm thử xung đột sync càng sớm càng tốt
Định nghĩa quy tắc xung đột sớm và kiểm thử chỉnh sửa trên hai thiết bị end-to-end.
Bắt đầu

Hãy coi lưu trữ là phần của sản phẩm, không chỉ quyết định thư viện. Tùy chọn tốt nhất là cái giữ app dùng được khi sóng rớt và predictable khi nó quay lại.

Lộ trình quyết định đơn giản

Viết ra luồng người dùng offline trước. Cụ thể: “mở việc hôm nay, thêm ghi chú, đính kèm ảnh, đánh dấu hoàn thành, ký tên.” Mọi thứ trong danh sách đó phải hoạt động mạng tắt, mọi lúc.

Rồi đi qua chuỗi ngắn: liệt kê màn hình offline-critical và lượng dữ liệu mỗi màn cần (việc hôm nay vs lịch sử đầy đủ), phác thảo mô hình dữ liệu tối thiểu và quan hệ bạn không thể giả (Job -> ChecklistItems -> Answers), chọn quy tắc xung đột cho từng thực thể (không phải một quy tắc cho tất cả), quyết định cách kiểm thử lỗi (migrations trên thiết bị thực, retry sync, force logout/reinstall), và xây nguyên mẫu nhỏ với dữ liệu thực tế để đo (load, search, save, sync sau một ngày offline).

Quy trình này thường lộ ra hạn chế thực sự: bạn cần truy vấn ad hoc linh hoạt và kiểm tra dễ dàng, hay truy cập theo object và kiểm soát mô hình chặt chẽ?

Những gì cần xác thực trong nguyên mẫu

Dùng một scenario thực tế, ví dụ kỹ thuật viên hoàn thành 30 kiểm tra offline rồi lái xe về vùng có sóng. Đo thời gian tải lần đầu với 5.000 bản ghi, xem một thay đổi schema sống sót sau update không, có bao nhiêu xung đột xuất hiện sau khi reconnect và bạn có giải thích được mỗi xung đột không, và bạn kiểm tra một “bản ghi xấu” nhanh cho support mất bao lâu.

Nếu muốn xác thực nhanh trước khi cam kết, nguyên mẫu no-code trong AppMaster có thể giúp bạn khoá luồng và mô hình dữ liệu sớm, ngay trước khi quyết định database trên thiết bị.

Sai lầm phổ biến làm hại ứng dụng offline-first

Lên kế hoạch thay đổi schema
Mô hình hóa dữ liệu một lần và lặp an toàn khi yêu cầu thực tế thay đổi.
Bắt đầu xây dựng

Hầu hết thất bại không đến từ engine DB. Chúng đến từ bỏ qua những phần tẻ nhạt: nâng cấp, quy tắc xung đột và xử lý lỗi rõ ràng.

Một bẫy là nghĩ xung đột hiếm. Trong công việc hiện trường chúng là bình thường: hai kỹ thuật viên sửa cùng tài sản, hoặc trưởng nhóm thay đổi checklist trong khi thiết bị offline. Nếu bạn không định nghĩa quy tắc (last write wins, merge theo trường, hoặc giữ cả hai phiên bản), cuối cùng bạn sẽ ghi đè công việc thực.

Một thất bại thầm lặng khác là coi mô hình dữ liệu là “xong” và không luyện tập nâng cấp. Schema thay đổi ngay cả trong app nhỏ. Nếu bạn không version schema và kiểm thử nâng cấp từ build cũ, người dùng có thể bị kẹt sau update với migration lỗi hoặc màn hình trắng.

Vấn đề hiệu năng cũng xuất hiện muộn. Đội đôi khi tải về mọi thứ “để phòng khi cần”, rồi thắc mắc vì sao tìm kiếm chậm và app mất phút để mở trên điện thoại tầm trung.

Các mẫu cần theo dõi:

  • Không có chính sách xung đột viết rõ, nên chỉnh sửa bị ghi đè im lặng.
  • Migrations chạy tốt trên cài mới nhưng lỗi khi nâng cấp thực tế.
  • Cache offline phình không giới hạn khiến truy vấn ì ạch.
  • Lỗi sync ẩn sau spinner, người dùng tưởng dữ liệu đã gửi.
  • Debug theo phỏng đoán thay vì script repro lặp được và dữ liệu mẫu.

Ví dụ: kỹ thuật viên hoàn thành kiểm tra offline, bấm Sync nhưng không thấy xác nhận. Upload thực tế thất bại do token auth. Nếu app giấu lỗi, họ rời đi nghĩ công việc xong và niềm tin mất.

Dù chọn gì, chạy bài test “chế độ hiện trường” cơ bản: airplane mode, pin thấp, update app và hai thiết bị sửa cùng một bản ghi. Nếu bạn xây nhanh với nền tảng no-code như AppMaster, nhồi các test này vào nguyên mẫu trước khi luồng tới đội lớn hơn.

Checklist nhanh trước khi cam kết

Trước khi chọn engine, định nghĩa “tốt” cho app hiện trường của bạn rồi kiểm thử với dữ liệu và thiết bị thực. Đội tranh luận về tính năng, nhưng hầu hết lỗi đến từ cơ bản: nâng cấp, màn hình chậm, quy tắc xung đột mơ hồ và không có cách kiểm tra trạng thái cục bộ.

Dùng làm cổng go/no-go:

  • Chứng minh nâng cấp: lấy ít nhất hai build cũ, nâng lên build hiện tại và xác nhận dữ liệu vẫn mở, sửa và đồng bộ.
  • Giữ màn hình lõi nhanh ở khối lượng thực: load dữ liệu thực và đo màn hình chậm nhất trên điện thoại tầm trung.
  • Viết chính sách xung đột per loại bản ghi: inspections, signatures, parts used, comments.
  • Làm dữ liệu cục bộ có thể kiểm tra và logs có thể thu thập: định nghĩa cách support và QA chụp trạng thái khi offline.
  • Làm phục hồi dự đoán được: quyết định khi nào rebuild cache, tải lại hay yêu cầu đăng nhập lại. Đừng lấy “cài lại app” làm kế hoạch.

Nếu bạn nguyên mẫu trong AppMaster, áp cùng kỷ luật: kiểm thử nâng cấp, định nghĩa xung đột và diễn tập phục hồi trước khi giao cho đội không thể chịu downtime.

Ví dụ tình huống: app kiểm tra kỹ thuật viên với sóng kém

Tránh nợ kỹ thuật
Sinh mã ứng dụng sẵn sàng production và giữ code sạch khi yêu cầu thay đổi.
Bắt đầu ngay

Một kỹ thuật viên bắt đầu ngày bằng tải 50 work order xuống điện thoại. Mỗi công việc gồm địa chỉ, checklist yêu cầu và vài ảnh tham chiếu. Sau đó, sóng chập chờn cả ngày.

Trong mỗi lần thăm, kỹ thuật viên sửa cùng vài bản ghi lặp lại: trạng thái công việc (Arrived, In Progress, Done), phụ tùng sử dụng, chữ ký khách hàng và ảnh mới. Một số chỉnh sửa nhỏ và thường xuyên (trạng thái). Một số lớn (ảnh) và không được mất.

Khoảnh khắc sync: hai người đều chạm đến cùng một việc

Lúc 11:10, kỹ thuật viên đánh dấu Job #18 Done và thêm chữ ký khi offline. Lúc 11:40, dispatcher giao lại Job #18 vì nó vẫn hiện là mở ở văn phòng. Khi kỹ thuật viên reconnect lúc 12:05, app upload thay đổi.

Luồng xung đột tốt không giấu điều này. Nó hiện ra. Một supervisor nên thấy thông báo đơn giản: “Tồn tại hai phiên bản của Job #18,” với các trường then chốt đặt cạnh nhau (trạng thái, kỹ thuật viên được giao, timestamp, có chữ ký hay không) và các lựa chọn rõ ràng: giữ cập nhật từ hiện trường, giữ cập nhật văn phòng, hoặc merge theo trường.

Đây là nơi quyết định lưu trữ và sync hiện lên đời thực: bạn có theo dõi lịch sử thay đổi sạch sẽ không, và có thể replay chúng an toàn sau nhiều giờ offline không?

Khi một việc “biến mất”, debug chủ yếu là chứng minh chuyện gì đã xảy ra. Log đủ để trả lời: mapping local record ID và server ID (kể cả khi nó được tạo), mọi thao tác ghi với timestamp/user/device, nỗ lực sync và lỗi, quyết định xung đột và người thắng, và trạng thái upload ảnh tách biệt khỏi bản ghi công việc.

Với những logs đó, bạn có thể tái tạo vấn đề thay vì đoán mò từ một lời phàn nàn.

Bước tiếp theo: xác thực nhanh, rồi xây giải pháp hiện trường đầy đủ

Trước khi bạn xung đột trong tranh luận SQLite vs Realm, viết một spec một trang cho luồng offline của bạn: màn hình kỹ thuật viên thấy, dữ liệu nào sống trên thiết bị, và những gì phải hoạt động khi không có sóng (tạo, sửa, ảnh, chữ ký, hàng đợi upload).

Rồi nguyên mẫu toàn hệ thống sớm, không chỉ phần database. Ứng dụng hiện trường thất bại ở chỗ nối: một form di động lưu cục bộ không giúp nếu team admin không thể xem và sửa bản ghi, hoặc backend sau đó từ chối update.

Kế hoạch xác thực thực dụng:

  • Xây lát cắt end-to-end mỏng: một form offline, một view danh sách, một lần sync, một màn admin.
  • Chạy test thay đổi: đổi tên trường, tách trường thành hai, phát hành build test và xem nâng cấp thế nào.
  • Mô phỏng xung đột: sửa cùng bản ghi trên hai thiết bị, sync theo thứ tự khác nhau và note chỗ hỏng.
  • Luyện debug hiện trường: quyết định cách bạn kiểm tra dữ liệu cục bộ, logs và payload sync lỗi trên thiết bị thực.
  • Viết chính sách reset: khi nào wipe cache cục bộ, và người dùng hồi phục sao mà không mất việc.

Nếu tốc độ quan trọng, một bước no-code đầu tiên giúp bạn xác thực luồng nhanh. AppMaster (appmaster.io) là một trong các lựa chọn để xây toàn bộ giải pháp (backend services, bảng quản trị web và app di động) sớm, rồi sinh lại mã nguồn sạch khi yêu cầu thay đổi.

Chọn bước xác thực tiếp theo dựa trên rủi ro. Nếu form thay đổi hàng tuần, test migrations trước. Nếu nhiều người chạm cùng một việc, test xung đột. Nếu lo “đã chạy tốt ở văn phòng”, ưu tiên luồng debug hiện trường.

Câu hỏi thường gặp

Offline-first thực sự nghĩa là gì cho một ứng dụng hiện trường?

Offline-first nghĩa là ứng dụng vẫn hữu ích khi không có kết nối: nó có thể tải dữ liệu cần thiết, nhận vào thông tin mới và giữ mọi thay đổi an toàn cho đến khi có thể đồng bộ. Lời hứa chính là người dùng không mất việc làm hay mất niềm tin khi sóng yếu, hệ điều hành đóng app, hoặc cạn pin giữa chừng.

Khi nào nên chọn SQLite thay vì Realm (và ngược lại)?

SQLite thường là lựa chọn an toàn khi bạn cần bộ lọc phức tạp, truy vấn kiểu báo cáo, quan hệ many-to-many và khả năng kiểm tra ad hoc bằng công cụ phổ biến. Realm phù hợp khi bạn muốn truy cập dữ liệu theo hướng đối tượng, ghi giao dịch mặc định và có thể giữ các nhu cầu truy vấn phù hợp với điểm mạnh của Realm.

Làm sao tránh làm hỏng người dùng khi mô hình dữ liệu thay đổi?

Xử lý migrations như một tính năng cốt lõi, không phải việc làm một lần. Cài bản cũ, tạo dữ liệu thực tế trên thiết bị, rồi nâng cấp và xác nhận app vẫn mở, sửa và đồng bộ; đồng thời kiểm thử tập dữ liệu lớn, bộ nhớ thấp, và việc kill app giữa lúc đang migrate.

Những thay đổi schema nào rủi ro nhất trên thiết bị thực?

Thêm trường thường đơn giản ở cả hai hệ thống, nhưng việc đổi tên và tách trường là rủi ro nhất. Lên kế hoạch rõ ràng, đặt giá trị mặc định hợp lý, xử lý null cẩn thận, và tránh migration ghi đè toàn bộ mọi bản ghi một lần trên thiết bị yếu.

Những truy vấn nào quan trọng nhất cho ứng dụng hiện trường và làm sao lập kế hoạch cho chúng?

Màn hình danh sách và bộ lọc đúng với công việc thực là tiêu chí chính: “việc hôm nay”, “form chưa đồng bộ”, “chỉnh sửa trong 2 giờ qua” và tìm kiếm nhanh. Nếu diễn đạt những truy vấn này khó khăn, UI sẽ chậm hoặc code sẽ khó duy trì.

Nên xử lý xung đột sync thế nào khi hai người chỉnh sửa cùng một bản ghi?

Cả SQLite lẫn Realm không tự giải quyết xung đột cho bạn; bạn vẫn cần quy tắc nghiệp vụ. Bắt đầu bằng cách chọn một quy tắc rõ ràng cho từng loại thực thể (last write wins, merge theo trường, hoặc hàng đợi duyệt thủ công) và làm cho app có khả năng giải thích điều gì đã xảy ra khi hai thiết bị chỉnh sửa cùng một bản ghi.

Tôi nên ghi log gì để support chẩn đoán báo cáo “công việc của tôi biến mất”?

Ghi đủ metadata để giải thích và phát lại thay đổi: user ID, device ID, timestamps và một mốc “last changed” cho mỗi bản ghi. Giữ nhật ký hoạt động phía thiết bị để thấy điều gì đã được xếp hàng, điều gì đã gửi, điều gì thất bại và điều gì server chấp nhận.

Cái nào dễ debug trên thiết bị thực hơn: SQLite hay Realm?

SQLite dễ kiểm tra vì nó là một file; bạn có thể kéo DB từ thiết bị, mở bằng công cụ SQLite phổ biến, chạy truy vấn ad hoc và xuất CSV/JSON. Realm thường cho trải nghiệm duyệt đồ thị đối tượng tự nhiên hơn, tốt để phát hiện liên kết thiếu hoặc null bất ngờ, nhưng nếu quen với SQL thì phân tích sâu có thể kém linh hoạt hơn.

Những gì gây mất dữ liệu trong ứng dụng offline, ngay cả khi dùng cơ sở dữ liệu tốt?

Rủi ro lớn nhất thường nằm ở logic app: ghi nhiều bước không có điểm cam kết rõ ràng, ẩn lỗi sync, và không có đường phục hồi khi đầy đĩa hoặc hỏng. Dùng transaction/checkpoint, hiển thị trạng thái lưu và đồng bộ rõ ràng, và có tùy chọn “reset & resync” dự đoán được thay vì bắt người dùng cài lại app.

Trước khi quyết định engine lưu trữ, tôi nên nguyên mẫu gì?

Xây một scenario end-to-end thực tế và đo: tải đầu tiên với hàng nghìn bản ghi, tìm kiếm, lưu form dài và đồng bộ sau một ngày offline. Xác thực nâng cấp từ ít nhất hai build cũ, mô phỏng xung đột bằng hai thiết bị và xác nhận bạn có thể kiểm tra trạng thái cục bộ và log khi có lỗi.

Dễ dàng bắt đầu
Tạo thứ gì đó tuyệt vời

Thử nghiệm với AppMaster với gói miễn phí.
Khi bạn sẵn sàng, bạn có thể chọn đăng ký phù hợp.

Bắt đầu