1. NODE.JS
Dùng JavaScript cho cả backend, chạy trên V8 Engine với mô hình event-driven và non-blocking I/O, giúp web server và API nhẹ, nhanh. Callback hell là điểm trừ ban đầu, nhưng async/await giải quyết tốt. npm mang lại kho package khổng lồ.
8/14/2025 / by Phuong Nguyen / in Open Source / with No comments /
1. NODE.JS
Dùng JavaScript cho cả backend, chạy trên V8 Engine với mô hình event-driven và non-blocking I/O, giúp web server và API nhẹ, nhanh. Callback hell là điểm trừ ban đầu, nhưng async/await giải quyết tốt. npm mang lại kho package khổng lồ.
8/29/2024 / by Phuong Nguyen / in SQL Perfomance, SQL Tip / with No comments /
4/25/2024 / by Phuong Nguyen / in MySQL, MySQL tips, SQL Perfomance / with No comments /
Về việc thiết kế cơ sở dữ liệu cần lưu ý các vấn đề sau
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
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).
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 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.
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).
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
4/23/2018 / by Phuong Nguyen / in Tản mạn / with No comments /
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.
3/17/2016 / by Phuong Nguyen / in Quản trị cơ sở dữ liệu SQL Server, SQL server, SQL Tip / with 1 comment /
ALTER DATABASE [mydatabase] SET RECOVERY SIMPLE
DBCC SHRINKFILE(<log_file_name_Log>)
ALTER DATABASE [mydatabase] SET RECOVERY FULL
3/15/2016 / by Phuong Nguyen / in Quản trị cơ sở dữ liệu SQL Server, SQL server, SQL Tip / with 2 comments /
SELECT name, recovery_model_desc FROM sys.databases GO
USE master; ALTER DATABASE DEMO SET RECOVERY FULL ;
9/05/2015 / by Phuong Nguyen / in Tản mạn / with No comments /
9/01/2015 / by Phuong Nguyen / in Database, Multi-Tenant, SQL Tip, System Architecture Design / with 1 comment /
7/24/2015 / by Phuong Nguyen / in SQL Tip / with No comments /
6/27/2015 / by Phuong Nguyen / in SQL Tip / with No comments /
-- ============================================= -- 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