Notion là một ứng dụng năng suất hoạt động như một không gian làm việc tất cả trong một. Đội ngũ kỹ sư của Notion đã triển khai một thay đổi quan trọng trong kiến trúc caching phía client cho trang web của họ bằng cách sử dụng SQLite, mang lại cải thiện 20% về tốc độ điều hướng trang.
Bài viết này sẽ phân tích sâu về quá trình triển khai, những thách thức kỹ thuật và giải pháp mà đội ngũ Notion đã áp dụng.

Cách Notion sử dụng SQLite để caching
Notion là một ứng dụng năng suất, tích hợp không gian làm việc all-in-one, cho phép người dùng ghi chú, làm spreadsheet, quản lý lịch, theo dõi timeline dự án và nhiều thứ khác. Một trong những thách thức của các kỹ sư Notion là phải đảm bảo trải nghiệm người dùng mượt mà trên mọi nền tảng, từ web, desktop đến mobile.
Vào năm 2021, Notion đã bắt đầu áp dụng SQLite để cache dữ liệu trong các ứng dụng cho Windows và macOS. Thay đổi này đã giúp load trang ban đầu nhanh hơn 50% và tốc độ điều hướng giữa các trang cũng nhanh hơn 50%. Thấy cách này hiệu quả, họ quyết định mang chiến lược tương tự lên trình duyệt web.
Notion đã sử dụng bản implement SQLite bằng WebAssembly và đạt được kết quả cải thiện 20% thời gian điều hướng trang cho website.
SQLite là gì và tại sao nó lại hay ho?
SQLite là một database quan hệ, mã nguồn mở, dạng embedded. Nó cực kỳ mạnh mẽ, hỗ trợ ACID transactions, full-text search, truy vấn không gian địa lý và nhiều thứ khác.
Điểm cộng của SQLite là nó rất nhẹ và serverless. Nó không cần một process server riêng để chạy. Thay vào đó, toàn bộ hệ thống database nằm trong một thư viện duy nhất và tích hợp thẳng vào code. Tất cả dữ liệu được lưu trữ local.
Nó được sử dụng rộng rãi trong các lĩnh vực như: Embedded Systems, Desktop & Mobile Apps và Phát triển web
Notion đã sử dụng SQLite trên website như thế nào?
Notion sử dụng bản implement SQLite bằng WebAssembly. WASM cho phép mã C, C++, Rust được biên dịch thành một ngôn ngữ cấp thấp giống assembly để chạy trên trình duyệt với hiệu suất gần như gốc (near-native).
Về cơ bản, thư viện WASM SQLite sử dụng File System API của trình duyệt để ghi trạng thái của database SQLite xuống ổ cứng, cụ thể hơn là Origin Private File System (OPFS).
Đây là cách cơ chế caching của họ hoạt động:
- Mỗi tab có một Web Worker riêng: Mỗi tab Notion được mở sẽ có một Web Worker riêng. Đây là một luồng nền chạy JavaScript mà không làm ảnh hưởng đến luồng UI chính, giúp các tác vụ nặng không làm đơ trình duyệt.
- Web Locks để đồng bộ: Chỉ có “tab active” mới được phép dùng web worker của mình để ghi vào SQLite. Notion quản lý việc này bằng Web Locks API và một
SharedWorker
riêng biệt tương tác với tất cả các tab Notion đang mở. - SharedWorker điều phối các truy vấn:: SharedWorker sẽ theo dõi xem tab nào là “active tab”. Bất cứ khi nào nó nhận được một yêu cầu ghi database từ một tab không active, và SharedWorker sẽ chuyển hướng yêu cầu đó đến Web Worker của tab active để xử lý.
Những thách thức mà team Notion đã gặp phải
Trong quá trình triển khai, team Notion đã gặp phải một vài vấn đề nan giải:
-
Database Corruption: Trước khi dùng kiến trúc SharedWorker, Notion gặp vấn đề hỏng dữ liệu SQLite. Một số người dùng thấy dữ liệu sai trên trang, ví dụ như comment bị gán nhầm người, hoặc preview trang không đúng.
- Nguyên nhân: Lỗi concurrency. Trước đó, mỗi tab Notion đều tự ghi vào database SQLite cùng một lúc. OPFS API lúc đó xử lý concurrency không tốt, dẫn đến corruption.
- Giải pháp: Chuyển sang kiến trúc dựa trên
SharedWorker
, đảm bảo chỉ có một tab duy nhất được phép ghi vào đĩa tại một thời điểm.
-
Đọc disk chậm hơn cả gọi API: Sau khi thêm caching, team Notion thấy performance trên các thiết bị yếu nhất (P95) lại tệ đi.
- Nguyên nhân: Các điện thoại Android cũ có tốc độ đọc từ đĩa cực kỳ chậm. Việc tải dữ liệu từ cache trên đĩa đôi khi còn chậm hơn cả việc tải cùng dữ liệu đó từ API của Notion.
- Giải pháp: Họ giải quyết bằng cách áp dụng cơ chế race giữa hai request bất đồng bộ: một từ cache đĩa qua SQLite và một từ API của Notion. Request nào về trước thì dùng, đảm bảo người dùng luôn có trải nghiệm nhanh nhất có thể.
-
Không load SQLite bất đồng bộ: Ban đầu, thư viện WASM SQLite không được tải một cách bất đồng bộ. Điều này có nghĩa là quá trình tải và xử lý thư viện đã chặn quá trình tải trang, làm chậm thời gian tải trang ban đầu.
- Nguyên nhân: Tải đồng bộ (synchronous loading) chặn luồng chính.
- Giải pháp: Tải WASM SQLite một cách bất đồng bộ (asynchronously). Điều này có một sự đánh đổi: dữ liệu cho lần tải trang đầu tiên sẽ không được lấy từ SQLite phải thông qua yêu cầu mạng. Tuy nhiên, sự đánh đổi này vẫn mang lại thời gian tải trang ban đầu tổng thể nhanh hơn cho người dùng
- Nguyên nhân: Tải đồng bộ (synchronous loading) chặn luồng chính.
Sau khi triển khai SQLite để caching trên trình duyệt, Notion đã ghi nhận cải thiện 20% về thời gian điều hướng giữa các trang mà không làm suy giảm bất kỳ chỉ số nào khác. Đây là một thành công đáng kể trong việc tối ưu hóa trải nghiệm người dùng trên nền tảng web.