SQL Antipatterns: Phần 1 - Tối ưu hóa thiết kế cơ sở dữ liệu
4/25/2024 / by Phuong Nguyen / in MySQL, MySQL tips, SQL Perfomance / with No comments /
Thiết kế cơ sở dữ liệu
Về việc thiết kế cơ sở dữ liệu cần lưu ý các vấn đề sau
- Tránh lưu trữ giá trị dạng đa giá trị
- Không sử dụng các trường lưu trữ dạng 1 cột nhiều giá trị
- VD: bảng USER không nên tạo 1 trường PHONG_BAN_IDS chứa giá trị dạng ID1, ID2 … kiểu 1,2,3
- Không sử dụng các trường lưu trữ dạng 1 cột nhiều giá trị
- Tránh lưu trữ bảng dạng đệ quy phụ thuộc
- Không sử dụng kiểu trong 1 bảng, khóa ngoại lại trỏ vào khóa chính của bảng đó
- Ví dụ:
- Bảng Category thường sẽ là CategoryID, ParentCategoryID, Name
- Bạn có thể tạo ra 1 bảng
- Bảng Category (CategoryID, Name)
- Và bảng Category_Xref (CategoryID, ParentCategoryID) làm bảng quan hệ cha-con
- Khóa chính không tồn tại
- Bất kỳ bảng nào cũng nên có khóa chính
- Mục tiêu là khi truy cập vào 1 bản ghi trong bảng rất dễ dàng theo khóa chính để thao tác như cập nhật, xóa.
- Kiểm tra trùng lặp bản ghi (dựa vào khóa chính đã có)
- Không đặt tên khóa chính tên dạng chung chung như (ID)
- Thay vì đặt tên khóa chính cho một bảng tên là Id thì chúng ta nên đặt tên có ý nghĩa hơn như: CategoryID, UserID, DepartmentID
- Mục đích là khi tạo các mối quan hệ giữa các bảng thì bạn có thể nhìn thấy luôn khóa ngoại là của bảng nào, tương ứng với khóa chính ở bảng nào
- VD:
- Không nên: Category (ID, Name, Note)
- Nên: Category (CategoryID, CategoryName, Note), Product (ProductID, ProductName, CategoryID)
- Xem xét thêm khóa ngoại cho bảng nếu cần thiết
- Một số trường hợp bỏ khóa ngoại thì có vẻ sẽ giúp cho DB nhẹ hơn, linh hoạt hơn, tuy nhiên đánh đổi lại là chúng ta phải tự quản lý tính toàn vẹn dữ liệu. Đôi khi cái giá phải trả là rất đắt.
- Thêm khóa ngoại sẽ giúp việc ràng buộc dữ liệu chặt chẽ, nên thêm đối với những chức năng cần tính ràng buộc dữ liệu chặt chẽ.
- Không tạo nhiều cột để lưu các giá trị khác nhau của một thuộc tính
- Ví dụ:
- Không nên: USER (UserID, Active, InActive)
- Nên USER (UserID, STATUS_ID), USER_STATUS (STATUS_ID, STATUS_NAME) với giá trị STATUS_ID là 1: Active, 2: InActive
- Ví dụ:
- Chia nhỏ dữ liệu của một bảng hoặc theo năm
- Có thể đánh partition theo năm cho bảng dữ liệu
SQL Tips: Tối ưu hóa câu lệnh MySQL - Phần 2
2/11/2023 / by Phuong Nguyen / in Database, MySQL, MySQL tips, SQL Tip / with No comments /
Tiếp phần 1 đã viết được khá lâu. Nay chúng ta sẽ tiếp tục viết phần 2 trong loạt bài tối ưu hóa câu lệnh MySQL
Vấn đề dư thừa dữ liệu trong câu truy vấn
Một nguyên tắc đơn giản nhất khi truy vấn dữ liệu là tránh sử dụng SELECT * trong bất kỳ trường hợp nào. Vì khi gọi lệnh này thì hệ quản trị sẽ tốn thêm tài nguyên để định danh những cột sẽ được lấy ra. Hãy chỉ rõ cột nào mình muốn lấy ra, giảm tải cho hệ thống và câu lệnh trở nên sáng sủa hơn.
Trong câu lệnh JOIN nhiều bảng thì cũng có chuyện dư thừa dữ liệu trong câu join. Bạn tìm hiểu thêm về tích descartes trong SQL thì bạn sẽ hiểu rõ hơn phần này. ví dụ đơn giản dưới đây:
VD:
Bảng đơn hàng tb_Order có 300 bản ghi.
Bảng người mua tb_User có 1.000.000 bản ghi.
Bảng công ty tb_Company có 100.000 bản ghi.
Bạn cần lấy thông tin bản ghi thông tin mã hóa đơn, tài khoản người mua, tên người mua, tuổi, tên công ty của những khách hàng lớn hơn 18 tuổi trong năm 2021.
Giả sử người dùng có thể thuộc hoặc không thuộc công ty nào cả. Ta sẽ phải viết query sử dụng câu lệnh JOIN như sau:
Select a.OrderNumber,
a.OrderDate,
b.UserName,
b.FullName,
b.Age,
c.CompanyName
from tb_Order as a
inner join tb_User as b
on a.UserID = b.UserID
left join tb_Company as c
on b.CompanyID = c.CompanyID
where b.Age > 18
and a.OrderDate >= '202-01-01' and a.OrderDate <= '202-12-31'
Nhìn câu lệnh này thì sau khi Join bảng a, b lại, số bản ghi câu lệnh phải duyệt ở mệnh đề where là
300 * 1.000.000 = 30.000.000 bản ghi. Để giảm bớt số bản ghi lọc bởi where thì chúng ta nên viết lại thế này sẽ tối ưu hơn:
Select a.OrderNumber,
a.OrderDate,
b.UserName,
b.FullName,
b.Age,
c.CompanyName
from tb_Order as a
inner join tb_User as b
on a.UserID = b.UserID
and b.Age > 18
left join tb_Company as c
on b.CompanyID = c.CompanyID
where a.OrderDate >= '202-01-01' and a.OrderDate <= '202-12-31'
Trong một số trường hợp mà Left join thêm 1 loạt bảng khác để lấy thông tin thêm trên SELECT mà trong điều kiện WHERE không chứa điều kiện của bảng đó thì ta có thể viết dạng subquery để tăng tốc độ cho câu query. Chẳng hạn câu query:
Select top 50 a.OrderNumber,
a.OrderDate,
b.UserName,
b.FullName,
b.Age,
c.CompanyName,
d.CityName as CompanyCityName
from tb_Order as a
inner join tb_User as b
on a.UserID = b.UserID
and b.Age > 18
left join tb_Company as c
on b.CompanyID = c.CompanyID
left join tb_City as d
on d.CityID = c.CityID
where a.OrderDate >= '202-01-01' and a.OrderDate <= '202-12-31'
Câu lệnh này nếu rơi vào trường hợp bảng Company và bảng City có rất nhiều dữ liệu (từ triệu bản ghi trở lên) sẽ chạy chậm hơn câu lệnh sau:
select ab.OrderNumber,
ab.OrderDate,
ab.UserName,
ab.FullName,
ab.Age,
c.CompanyName,
d.CityName as CompanyCityName
from (
Select a.OrderNumber,
a.OrderDate,
b.UserName,
b.FullName,
b.Age,
b.CompanyID
from tb_Order as a
inner join tb_User as b
on a.UserID = b.UserID
and b.Age > 18
where a.OrderDate >= '202-01-01' and a.OrderDate <= '202-12-31'
order by a.OrderDate desc
limit 50
) as ab
left join tb_Company as c
on c.CompanyID = ab.CompanyID
left join tb_City as d
on d.CityID = c.CityID
Triển khai một hệ thống trên IIS Server với mô hình Web Garden
9/16/2022 / by Phuong Nguyen / in Asp.net, IIS Config, Scale, System Performance / with No comments /
Đối với một lập trình viên dotnet, nếu bạn đã từng tham gia phát triển hệ thống web thì chắc bạn sẽ từng biết đến khái niệm máy chủ web IIS (Internet Information Service).
IIS là một máy chủ web do Microsoft phát triển và được sử dụng để triển khai và lưu trữ ứng dụng Web. IIS có Công cụ xử lý ASP.NET của riêng mình để xử lý yêu cầu. Vì vậy, khi một yêu cầu đến từ máy khách đến máy chủ, IIS sẽ nhận yêu cầu đó và xử lý nó và gửi phản hồi trở lại máy khách.
Mặc định khi tạo một ứng dụng chạy trên IIS sẽ tạo ra một application pool chạy với một worker process (W3Wp.exe).
Ứng dụng web sau khi được tạo
Khi để chế độ mặc định này thì mọi thao tác và yêu cầu (request) gửi vào ứng dụng được IIS xử lý đồng bộ và nhất quán.
Nhưng nhược điểm là khi có nhiều request gửi vào thì tổng thời gian để trả về kết quả có thể bị chậm đi nhiều vì chỉ có một Worker chạy.
Ví dụ:
+ 2 request thì 60 mili giây trả về
+ 10 request thì mất đến 1200 mili giây trả về
+ 100 request thì mất 12000 mili giây trả về
Tức là với lượng truy cập đồng thời tăng thì thời gian trả về càng lớn.
Bài toán này chắc khá nhiều bạn đã gặp phải rồi. CPU lúc nào cũng chiếm lượng % rất lớn, server thường xuyên bị đơ.
Để giải quyết bài toán này người ta đưa ra hai khái niệm là Web Garden và Web Farm. Vậy ta đi tìm hiểu từng loại khái niệm và ứng dụng trong thực tế của nó.
WEB GARDEN
Web Garden là khái niệm để chỉ những ứng dụng web được cài đặt sử dụng nhiều hơn một worker để chạy. Ta có thể mượn cái ảnh từ internet để minh họa cho dễ hiểu
Ưu điểm của Web Garden:
Do có nhiều worker được thiết lập có thể chia sẻ cùng nhau để xử lý các request, nên việc xử lý nhiều request cùng lúc được diễn ra nhanh chóng hơn và trả về kết quả trong thời gian nhanh hơn so với một worker.
Ví dụ:
+ 2 request thì 60 mili giây trả về
+ 10 request thì mất đến 90 mili giây trả về
+ 100 request thì mất 3600 mili giây trả về
Như vậy so với Application Pool (Normal) thì Application Pool (Web Garden) thực thi nhanh có vẻ ổn hơn nhiều.
Nhưng với những cách triển khai những hệ thống nhỏ, đơn giản, ít lưu lượng truy cập thì có thể dùng cách này. Nhưng đối với hệ thống có lượng truy cập lớn thì cách này không đáp ứng được.
Mình sẽ viết một series về thiết kế ứng dụng triển khai đáp ứng khả năng scale out.
Cách sử dụng patch-package để tạo ra một bản fix lỗi thư viện cho dự án ReactJs
9/26/2021 / by Phuong Nguyen / in aws-amplify, Cách sử dụng patch-package, patch-package, ReactJs, Shopify App / with No comments /
Ai từng sử dụng ReactJs thì cũng hiểu rằng nó là một bộ thư viện tuyệt với cho công việc triển khai một dự án theo mô hình single page.
Bởi sự tiện lợi và đặc biệt là cực kỳ nhiều thư viện. Khi bạn có chức năng, biết yêu cầu rồi thì vấn đề của bạn bây giờ là từ khóa tìm kiếm để có thể tìm ra đúng thư viện viết sẵn của người ta. Sau đó bạn chỉ cần install vào project UI là sử dụng thôi, quá tiện lợi.
Bên cạnh sự tiện lợi thì cũng có những vấn đề khá củ chuối, trong đó có một vấn đề khá nhức nhối là chủ sử hữu thư viện đã thêm quá nhiều package phụ thuộc vào bên trong thư viện. Tức là muốn chạy thư viện này phải bắt buộc phải cài một loạt thư viện khác, điều đó gây ra không ít rắc rối cho người phát triển.
Chẳng hạn trường hợp của mình là sử dụng thư viện @aws-amplify để phát triển một Shopify app Embedded. Tức là App của mình sẽ được đưa vào Shopify Admin dưới dạng nhúng trong một IFRAME.
"aws-amplify": "^3.0.22",
"aws-amplify-react": "^4.1.21",
"aws-appsync": "^4.0.0",
"aws-appsync-react": "^4.0.0",
Mọi thứ đều rất tốt đẹp cho đến khi cụ Chrome đưa ra cơ chế tự động block cookies và localStorage của những third-party khi chạy ở chế độ Private Mode (ẩn danh).
Thế là khi người dùng ở chế độ Private Mode thì app của mình lăn ra chết vì cái lỗi
Fails to load in an private/incognito browser session within an iframe
Mở chế độ debugger thì cụ thể là lỗi
Failed to read the 'localStorage' property from 'Window': Access is denied for this document
Mình đã dò trong Project của mình xem có sử dựng cookies, localStorage không thì chẳng thấy sử dụng. Điều tra kỹ hơn thì do trong thư viện @aws-amplify bắt buộc cài đặt thêm package paho-mqtt, thư viện này lại sử dụng đến localStorage.
Thế là ăn cám, phải tìm phương án để xử lý cho vấn đề này, không thể bắt người sử dụng đi tắt chế độ block cookies và localStorage ở private mode được.
Cách thứ nhất phải xóa cái localStorage đó khỏi thư viện paho-mqtt hoặc cách thứ 2 là bỏ hẳn cái thư viện này ra khỏi @aws-amplify.
Cách thứ 2 có vẻ không khả thi nên mình chọn cách thứ nhất, tìm và loại bỏ từ khóa localStorage khỏi các file js trong thư viện node_modules\paho-mqtt\paho-mqtt.js.
Sau khi tìm và loại bỏ thì vấn đề đặt ra là làm thế nào để sử dụng được bản hot fix này. Và sau một ngày mò mẫm thì tìm ra được cách là sử dụng patch-package để tạo ra một version hot-fix cho riêng dự án của mình.
Mình code trên môi trường window nên cách làm như sau:
Bước 1. Mở project bằng Visual Studio Code, sau đó mở file node_modules\paho-mqtt\paho-mqtt.js sửa và lưu lại.
Bước 2. Sử dụng terminal của Visual Studio Code chạy lệnh
npx patch-package paho-mqtt
Trong thư mục project sẽ xuất hiện một thư mục patches, và trong thư mục sẽ là một version paho-mqtt+1.1.0.patch (máy mình nó hiện vậy)
Bước 3. Chạy tuần tự tiếp các lệnh trên terminal, để add và commit code bạn vừa sửa lên git
git add patches/paho-mqtt+1.1.0.patch
git commit -m "fix paho-mqtt.js in paho-mqtt"
Bước 4. Thêm đoạn Script sau vào file package.json nếu trong file đó chưa có scripts
"scripts": {
"postinstall": "patch-package"
}
Nếu trong file package.json đã có sẵn "scripts", bạn chỉ cần thêm vào trong scripts đoạn sau
"postinstall": "patch-package"
Bước 5. Chạy lệnh patch-package tiếp trên terminal để hoàn thành công đoạn cuối
npm i patch-package
Thế là xong, debug thấy chạy ầm ầm, build code lên Shopify app, chạy mượt mà ở cả chế độ Normal và Private Mode (ẩn danh).
SQL Tips: Tối ưu hóa câu lệnh MySQL - Phần 1
8/31/2021 / by Phuong Nguyen / in MySQL, MySQL tips, SQL Index, SQL Perfomance, SQL server, SQL Tip / with No comments /
Comeback to write my blog
Qua quá trình làm việc liên tục với hệ quản trị cơ sở dữ liệu MySQL, mình note lại một số tips, câu lệnh SQL hữu ích. Tất nhiên ngoài những câu lênh riêng biệt thì những tips này có thể sử dụng cho cả SQL Server hay một hệ quản trị cơ sở dữ liệu quan hệ phổ biến khác như PosgreSQL
1. Vấn đề sử dụng JOIN, UNION, WHERE
Cố gắng sử dụng nhiều nhất có thể INNER JOIN, vì Inner join là câu lệnh tránh dư thừa dữ liệu nhất trong các loại JOIN.
Và đặc biệt là nên sử dụng WHERE ở chỗ cần thiết:
Câu query A:
Select a.UserName,
a.FullName,
a.Age,
b.CompanyName
from tb_User as a
inner join tb_Company as b
on b.CompanyID = a.CompanyID
where a.Age > 18
Câu query B:
Select a.UserName,
a.FullName,
a.Age,
b.CompanyName
from tb_User as a, tb_Company as b
where b.CompanyID = a.CompanyID
and a.Age > 18
Trong thực tế thì nếu lượng dữ liệu nhỏ thì 2 câu query A, B mất thời gian như nhau. Nhưng câu lệnh viết kiểu query A sẽ tốt hơn kiểu viết query B.
2. UNION, UNION ALL
Tránh sử dụng Union đơn thuần, nếu bắt buộc sử dụng Union thì nên sử dụng UNION ALL.
3. EXPLAIN
Sau khi viết câu lệnh query để lấy dữ liệu ra, có một bước khá quan trọng để tăng trải nghiệm người dùng là kiểm tra tốc độ nhanh hay chậm của tính năng vừa xây dựng. Bạn có thể sử dụng câu lệnh EXPLAIN để kiểm tra xem câu lệnh của mình tối ưu hay chưa
Ví dụ:
Bạn có câu lệnh lấy danh sách tên, tuổi và cơ quan của người dùng lớn hơn 18 tuổi:
Select a.UserName,
a.FullName,
a.Age,
b.CompanyName
from tb_User as a
inner join tb_Company as b
on b.CompanyID = a.CompanyID
where a.Age > 18
Sau khi hoàn thành câu lệnh nên thử kiểm tra lại xem câu lệnh tối ưu chưa bằng cách chạy lệnh EXPLAIN
EXPLAIN
Select a.UserName,
a.FullName,
a.Age,
b.CompanyName
from tb_User as a
inner join tb_Company as b
on b.CompanyID = a.CompanyID
where a.Age > 18
Để hiểu câu lệnh này sử dụng như thế nào thì bạn tham khảo trang chủ MySQL có nói. Hoặc mình sẽ nêu ra trong một bài khác.
4. DISTINCT, GROUP BY
Hạn chế sử dụng DISTINCT, thay vì dùng nó thì chuyển sang GROUP BY nếu có thể, vì câu lệnh Distinct chạy khá tốn bộ nhớ.
5. Vấn đề INDEX dữ liệu
Sử dụng Index có thể tăng tốc độ câu truy vấn, nhưng nó sẽ làm cho câu lệnh Update/Insert/Delete bị chậm hơn.
Bản chất đánh Index là hệ quản trị sẽ lưu trữ thêm dữ liệu vào bộ nhớ theo một logic để dễ dàng truy xuất các cột có index. Nên dung lượng dữ liệu sẽ phình lên nhiều hơn, và ổ cứng nhanh đầy hơn.
Một bảng không nên đánh quá nhiều index.
6. Đối với câu lệnh phức tạp, hay phân tích và tách nhỏ thành các câu lệnh đơn giản
...
Nghĩ ra sẽ viết tiếp phần 2
Tôi đến với nghề IT như thế nào
4/23/2018 / by Phuong Nguyen / in Tản mạn / with No comments /
Tôi đến với nghề IT như thế nào?
Tôi, đích thị là một tên từ trên núi xuống. Thời gian đầu đi học ở Hà Nội mấy bạn cứ tưởng tôi là người dân tộc thiểu số.
Nếu bạn có một mentor dẫn đường cho bạn đi thì thật tốt, có thể bạn sẽ đi đúng đường và đạt được nhiều thành quả sớm hơn những người tự mò mẫm.
Cách giảm dung lượng file transaction log trong SQL Server
3/17/2016 / by Phuong Nguyen / in Quản trị cơ sở dữ liệu SQL Server, SQL server, SQL Tip / with 1 comment /
Khi tạo một CSDL trong SQL Server, tự động sẽ sinh ra hai tệp là tệp đuôi .MDF và .LDF
Thí dụ mình tạo một CSDL tên là Demo thì sẽ sinh ra: Demo.mdf và Demo_log.ldf
Khi để chế độ Recovery Model mặc định là Full hoặc Bulk-logged thì sau một thời gian sử dụng, dung lượng file log (.ldf) sẽ phình lên rất nhiều, có thể dung lượng sẽ file log sẽ gấp nhiều lần file data. Bạn có thể chuột phải vào CSDL và chọn properties để xem
Với dung lượng file log lớn như thế này vừa tốn dung lượng ổ cứng HDD, vừa làm chậm thao tác trên CSDL đó. Vì vậy chúng ta phải giảm dung lượng file log đi là điều tất yếu.
Cách giảm dung lượng file log
Cách 1. Sử dụng SQL management tool để xóa log
Cách 2. Xóa log bằng cách sử dụng câu lệnh SHRINKFILE
ALTER DATABASE [mydatabase] SET RECOVERY SIMPLE
DBCC SHRINKFILE(<log_file_name_Log>)
ALTER DATABASE [mydatabase] SET RECOVERY FULL
- B1. Chuyển CSDL về simple model, thì việc xóa log mới có hiệu quả.
- B2. Chạy lệnh xóa log file
- B3. Chuyển về Recovery Full Model để CSDL có thể thực hiện log các trạng thái
KẾT QUẢ
Sau khi thực hiện một trong hai cách trên thì dung lượng của file log của mình giảm xuống đang ngạc nhiên
PS: Các bạn nếu thấy hay thì share giúp nhé hoặc để lại comment.
Cách phân biệt giữa các loại Recovery Model trong MSSQL Server
3/15/2016 / by Phuong Nguyen / in Quản trị cơ sở dữ liệu SQL Server, SQL server, SQL Tip / with 2 comments /
1. Các loại Recovery Model
Hệ quản trị cơ MSSQL Server có 3 loại Recovery model:
- Full,
- Simple
- Bulk-logged.
Khi chúng ta tạo một CSDL mới thì hệ thống tự động chọn model là: Full.
Chúng ta có thể sử dụng SQL management tool để xem như sau:
- Click chuột phải vào database, chọn Properties
- Trên cửa số thông tin về database, chọn tab Options
- Recovery Model: Full, Bulk-logged, Simple
Hoặc có thể dụng câu lệnh SQL để truy vấn
SELECT name, recovery_model_desc FROM sys.databases GO
2. Cách sử dụng các loại Recovery Model
Full Recovery Model:
- Sao lưu gần như tất cả những thay đổi trong cơ sở dữ liệu. Nên việc bạn có thể phục hồi được tất cả dữ liệu.
- Nếu cơ sở dữ liệu có chứa nhiều nhóm tập tin, và bạn muốn khôi phục từng phần đọc / ghi filegroups thứ cấp và tùy chọn, chỉ đọc filegroups.
- Người quản trị có thể phục hồi được cơ sở dữ liệu đến trước thời điểm db bị hỏng hóc.
- Dung lượng file data và file log sẽ phình ra rất nhanh, và file log có thể lớn hơn rất nhiều so với file data. Dẫn đến chi phí cho phần cứng sẽ đội lên khủng khiếp.
- Và tất nhiên khi file phình lên thì truy vấn SQL sẽ chậm đi, thao tác với CSDL cũng chậm đi. Bạn phải thường xuyên xóa bớt file log để hệ thống chạy nhanh hơn.
Bulk-Logged Recovery Model:
- Gần như bạn có thể phục hồi mọi thứ nếu chọn Model này. Tương tự Full Recovery Model
Simple Recovery Model
- Không tự động lưu lại những sự thay đổi của CSDL. Nên có thể mất toàn bộ dữ liệu nếu người quản trị không backup thường xuyên.
- Do không lưu lại các thay đổi vào file log nên dung lượng file log sẽ rất nhỏ so với file data.
- Đây là lựa chọn hợp lý cho admin có túi tiền không được rủng rỉnh.
3. Lời khuyên
Nếu dữ liệu của bạn có thể mất mát tại một số thời điểm thì nên sử dụng Simple Recovery Model, với ưu điểm là dung lượng file data và log sẽ nhỏ, nên hệ thống sẽ chạy mượt mà hơn. Tất nhiên chúng ta có thể tự tạo ra SQL Job Backup tự động cho CSDL theo thời gian để tránh mất mát dữ liệu (ngày backup 2 lần chẳng hạn) các bạn có thể tham khảo tại đây.
Chúng ta có thể thay đổi Recovery Model bằng cách sử dụng SQL Management Tool hoặc sử dụng câu lệnh sau:
USE master; ALTER DATABASE DEMO SET RECOVERY FULL ;
Chúc các bạn thành công khi làm việc với SQL Server.
Một ngày đi học ...
9/05/2015 / by Phuong Nguyen / in Tản mạn / with No comments /
Hôm nay đi dự hội thảo Tech Insider Expo 2015 do Vietnamworks tổ chức. Qua những lời giới thiệu trong email mời hoành tráng của ban tổ chức. Sau khi tham gia hội thảo mình có một số cảm nhận như sau.
Thứ nhất, về cách thức tổ chức, lựa chọn khách mời có thể nói là tạm được. Cũng hơi buồn vì mình làm công nghệ, biết khá nhiều cao thủ nhưng khi tham dự hội thảo lại chẳng bắt gặp cao thủ nào.
Thứ hai, mình đến để tham gia hội thảo nhưng khi đến phòng hội thảo thì vừa đúng giờ chuẩn bị bắt đầu nhưng lại hết chỗ ngồi, thế là lại chưng hửng ngồi ngoài. Buồn vì với số khách mời là hơn 1.5k sao cái phòng hội thảo lại bé tý... Đành đợi buổi hội thảo vào 1h30 vậy.
Thứ ba, ghé qua các lán trại tuyển dụng, đọc thấy thông tin tuyển dụng hấp dẫn ghê (toàn công ty lớn theo như profile là những công ty top của châu á) mặc dù chưa có nhu cầu xin việc nhưng cũng thử ghé vào xem thế nào. Nhưng hơi bất ngờ vì không thấy người đến ứng tuyển mà chỉ thấy các HR đang ngồi chém gió là nhiều. Có một vài người ghé vào, đi ra với gương mặt thỏa mãn vì có quà xách về...
Phải nói ý tưởng về một Tech Insider Expo là rất hay. Nhưng cần quảng bá rộng hơn trong cộng đồng lập trình viên việt nam. Dù sao cũng rất cảm ơn Vietnamworks đã tổ chức 1 chương trình khá hay dành cho những người đang có nhu cầu tìm việc làm.
Thiết kế database theo hướng multi-tenancy, SaaS
9/01/2015 / by Phuong Nguyen / in Database, Multi-Tenant, SQL Tip, System Architecture Design / with 1 comment /
Bài toán hướng multi-tenancy trong thực tế gặp rất nhiều, nhưng có rất nhiều developer chưa nắm được khái niệm và cách thức hoạt động của các hệ thống thiết kế theo hướng này. Qua một thời gian nghiên cứu và phát triển các hệ thống, mình đúc rút một số kinh nghiệm muốn chia sẻ cho mọi người.
Thực tế ta bắt gặp rất nhiều hệ thống sử dụng multi-tenacy
vd:
- Hệ thống quản lý cửa hàng cho phép nhiều đại lý có thể truy cập với những tài khoản độc lập, dữ liệu độc lập, nhưng cùng chung 1 hệ thống site.
- Hệ thống quản lý công văn sử dụng trong tổng công ty và nhiều công ty con, cùng site nhưng dữ liệu độc lập.
- Hệ thống quản lý dự án Jira
- Hệ thống CRM của zoho, saleforce...
Nhiều hệ thống sử dụng SQL server, Oracle ... thiết kế hệ thống multi-tenancy theo một trong các kiến trúc sau.
Phương án I. Cùng chung một cơ sở dữ liệu (database), chia sẻ bảng (table)
Ví dụ:
Một hệ thống quản lý cửa hàng, có bảng shop, bảng sản phẩm (product), bảng acccount
Bảng shop
Shop (
Id,
Name,
Notes)
Bảng user
User(
Id,
Name,
UserName,
Password,
ShopId
)
Bảng product
Product (
Id int,
Code varchar(50),
Name varchar(255),
ShopId)
Tất cả các bảng liên quan đều có 1 khóa ngoại là ShopId. Dữ liệu sản phẩm của từng shop đều được lưu chung trong bảng Product, nhưng được phân biệt nhau bởi trường ShopId.
Điểm mạnh:
- Thiết kế lưu trữ đơn giản.
- Dễ cho việc phát triển.
- Không gặp phải vấn đề đồng bộ cấu trúc bảng trong quá trình phát triền.
Nhược điểm:
- Không độc lập database nên việc một shop có thể xem dữ liệu của shop khác nếu có quyền truy cập SQL, phân quyền trên SQL thực sự là vấn đề lớn.
- Vấn đề backup, restore dữ liệu cho từng shop là gần như không thể, chỉ có thể backup cho tất cả.
- Vấn đề phát sinh thực sự phức tạp khi dữ liệu phình to, rất khó khăn trong việc backup, restore...
- Khó khăn khi scale hệ thống.
Lời khuyên: Phương án này chỉ dùng làm những hệ thống nhỏ, ít dữ liệu, phát sinh dữ liệu không lớn.
Phương án II. Cùng chung database, chia sẻ schema
Hướng thiết kế này sử dụng một cơ sở dữ liệu, mỗi tenant tương ứng 1 schema. Có một schema chung để quản lý những các dữ liệu chung, quản lý thông tin về tenants. Cấu trúc các bảng ở tất cả các tenant đều giống nhau.
Cần 1 schema chuẩn để dựa vào đó tạo ra tenant mới trong quá trình thêm mới tenant.
Điểm mạnh:
- Thiết kế theo hướng này thì có thê thay đổi các cấu trúc, hàm, thủ tục riêng rẽ giữa các tenant.
- Dễ phân quyền hơn phương án 1.
- Tiết kiệm được chi phí khi triển khai (do số lượng database chỉ là rất ít)
Nhược điểm:
- Phương án backup độc lập từng tenant là vấn đề nan giải, lập trình viên sẽ phải tự quản lý việc backup/restore cho từng tenant bằng code.
- Việc đồng bộ những thay đổi trong cấu schema là vấn đề cần phải quan tâm.
- Dữ liệu trong database sẽ phình ra nhanh chóng.
- Số lượng schema trong 1 database là có giới hạn.
- Khó khăn khi scale hệ thống.
Phương án III. Mỗi tenant một database.
Phương án này sẽ thực hiện như sau: hệ thống sẽ gồm 1 database chung (chuyên để quản lý các phần như danh sách tenant, user, role ...), 1 database tenant chuẩn (chứa dữ liệu chuẩn), và các tenant khác.
Mỗi tenant sẽ là 1 database, người dùng sẽ có quyền truy cập vào database chung và database tenant của user đó.
Mình sẽ đính kèm script sql server để tạo databases cho các phương án trên, phương án 3 giống như phương án 2, nhưng thay vì dùng schema thì chuyển sang dùng database.
Google Drive: SQL Script Mutil-tenancy
SQL Tips: Hướng dẫn sử dụng SQL Profiler
7/24/2015 / by Phuong Nguyen / in SQL Tip / with No comments /
SQL Tips: Create SQL Job to backup database everyday
6/27/2015 / by Phuong Nguyen / in SQL Tip / with No comments /
Today, I will make a backup manual database daily work by creating SQL Job.
First we have to install the SQL Server Agent SQL.
Set mode to run automatically when Windows startup.
- Setup SQL Server Agent
- Run Sql Server Agent services
Step 2.
- Create database: "Demo"
- Create store procedure jb_Backup_Database
-- ============================================= -- Author: Phuong Nguyen -- Create date: YYYY-MM-DD -- Description: Backup database -- ============================================= ALTER PROCEDURE [dbo].[jb_Backup_Database] AS BEGIN DECLARE @DBName NVARCHAR(50) -- database name DECLARE @BKPath NVARCHAR(256) -- path for backup files DECLARE @BKFileName NVARCHAR(256) -- filename for backup DECLARE @BKFileDate NVARCHAR(20) -- used for file name -- Set dbname is current db SET @DBName = DB_NAME() -- Database backup directory SET @BKPath = 'C:\DB\Backup\' -- Filename format SELECT @BKFileDate = CONVERT(NVARCHAR(20),getDate(),112) SET @BKFileName = @BKPath + @DBName + '_' + @BKFileDate + '.BAK' BACKUP DATABASE @DBName TO DISK = @BKFileName END
- Run proc to test resultexec jb_Backup_Database
USE [msdb] GO /****** Object: Job [jb_Backup_database_demo] Script Date: 6/28/2015 4:18:14 PM ******/ BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** Object: JobCategory [[Uncategorized (Local)]]] Script Date: 6/28/2015 4:18:14 PM ******/ IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'jb_Backup_database_demo', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'No description available.', @category_name=N'[Uncategorized (Local)]', @owner_login_name=N'sa', @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [RunBackupDemo] Script Date: 6/28/2015 4:18:15 PM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'RunBackupDemo', @step_id=1, @cmdexec_success_code=0, @on_success_action=1, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'exec jb_Backup_Database', @database_name=N'Demo', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'Everyday', @enabled=1, @freq_type=4, @freq_interval=1, @freq_subday_type=1, @freq_subday_interval=0, @freq_relative_interval=0, @freq_recurrence_factor=0, @active_start_date=20150627, @active_end_date=99991231, @active_start_time=234700, @active_end_time=235959, @schedule_uid=N'624f7aee-128d-403b-8037-4bde1c1a792d' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION EndSave: GO