0
0
Lập trình
Hưng Nguyễn Xuân 1
Hưng Nguyễn Xuân 1xuanhungptithcm

Loop: Mô hình DWH và Các Mô Hình Dữ Liệu

Đăng vào 6 ngày trước

• 9 phút đọc

Loop: Mô hình DWH và Các Mô Hình Dữ Liệu

Loop là dự án cá nhân của tôi. Dự án cá nhân là những không gian thử nghiệm nơi tôi có thể xây dựng bất kỳ điều gì với chu trình lặp lại nhanh chóng. Để học các tài liệu mới, tôi luôn cần những ví dụ thực tế và thực hành.

Nếu tôi đọc một cuốn sách giải thích về 3NF, cách tốt nhất để tôi hiểu nó là kiểm tra một số bảng vật lý thực tế. Nói chung, kiến thức trở nên giá trị khi được áp dụng vào công việc thực tế. Tuy nhiên, như tôi đã đề cập trước đó, dự án cá nhân là phòng thí nghiệm nơi tôi thực hiện các thí nghiệm và thử nghiệm những ý tưởng mới.

Tôi đã cần một kho dữ liệu cá nhân dành riêng cho việc học trong một thời gian dài và quyết định bắt đầu với ý tưởng sau:

  • Tôi có đồng hồ Garmin và đang theo dõi thời gian dành cho công việc, dự án cá nhân, học tập và chơi game.
  • Vậy hãy thu thập, mô hình và biến đổi dữ liệu này trong một kho dữ liệu được xây dựng từ đầu.
  • Sau đó, hãy xây dựng một số bảng điều khiển dựa trên dữ liệu này.

Dưới đây là một số câu hỏi mà tôi thường muốn tìm câu trả lời:

  1. Tôi đã dành bao nhiêu thời gian mỗi ngày/tuần/tháng cho công việc, học tập hoặc chơi game?
  2. Tôi đã có bao nhiêu buổi tập luyện mỗi ngày/tuần/tháng?
  3. Khi nào tôi bắt đầu tập luyện lần đầu tiên? Tôi có nhất quán không?
  4. Thời gian đi ngủ và thức dậy trung bình của tôi theo tuần/tháng là gì?

Với tư cách là một Kỹ sư Dữ liệu, tôi sẽ sử dụng những công cụ sau:

  • S3 và Postgres cho các lớp dữ liệu thô và chi tiết
  • Apache Spark cho các biến đổi dữ liệu
  • delta.io cho lớp staging
  • Dagster cho việc điều phối
  • Metabase để xây dựng bảng điều khiển

Trong bài viết này và các bài tiếp theo, tôi sẽ chia sẻ cách mà dự án này được xây dựng. Dưới đây là kết quả cuối cùng - một bảng điều khiển trông giống như thế này (ảnh chụp từ Metabase của tôi):

Bạn có thể thấy một số biểu đồ không ổn định như "Ngủ và Thức dậy", tôi quyết định để nguyên như vậy, nếu không tôi sẽ không bao giờ công bố bài viết này. Không có giới hạn cho sự hoàn hảo.

Tại sao lại gọi là "Loop"?

Đừng nhầm lẫn với thiết bị Loop của Polar! Tôi không có thời gian để đăng ký thương hiệu cho tên dự án, vì vậy tôi giữ nguyên "Loop" nghe giống như "Whoop". Tôi đã sử dụng một vòng Whoop trong gần 9 tháng và rất thích nó.

Thiết bị này rất ấn tượng: nó hoạt động hơn 1-2 ngày (chào Apple Watch), không có màn hình, và hoàn toàn tập trung vào những "thông tin chi tiết" kỳ diệu.

Dù sao, bây giờ tôi có Garmin và tôi hoàn toàn hài lòng. Một ngày nào đó, tôi sẽ thực hiện điều gì đó mà Whoop làm, chẳng hạn như phân tích mối tương quan giữa các chỉ số Garmin và giờ làm việc. Tôi cũng dự định thêm Google Forms để theo dõi tâm trạng và thói quen hàng ngày trong tương lai.

Nguồn dữ liệu

Hãy bắt đầu bằng cách điều tra những dữ liệu mà chúng ta có thể thu thập.

Theo dõi thời gian – EARLY

Tôi đang theo dõi thời gian của mình. Những gì tôi theo dõi: thời gian làm việc, thời gian dành cho dự án cá nhân, thời gian đọc sách kỹ thuật, học ngôn ngữ và chơi video game.

Tôi chỉ theo dõi những công việc sâu sắc và tập trung, hoàn toàn cam kết vào những gì tôi đang làm trong thời điểm đó. Nếu tôi dành 8 giờ ở văn phòng thì có thể 5-6 giờ sẽ được theo dõi. Đây chỉ là một chỉ số. Có thể thời gian nhiều hơn có thể dẫn đến kết quả tốt hơn, có thể không, không có gì đảm bảo.

Nó giống như thời gian giữ bóng trong bóng đá, số lượng commit git đã thực hiện, HRV hoặc "mức độ căng thẳng".

Dù sao, với dữ liệu này, các xu hướng có thể được phát hiện:

  • Tôi đã dành bao nhiêu thời gian chơi video game tuần này
  • Thời gian dành cho các cuộc họp hoặc trong chế độ tập trung
  • Tôi đã đọc một cuốn sách mới về cơ sở dữ liệu, mất bao lâu

EARLY (trước đây là Timeular) là ứng dụng theo dõi thời gian mà tôi sử dụng, trả phí cho một gói đăng ký. Nó đơn giản, tối giản và thực hiện 95% những gì tôi mong đợi. Một trong những điểm bán hàng của EARLY là API. Họ cũng bán Time Tracking Cube – một khối vật lý (tôi có một cái), mà bạn có thể sử dụng nếu muốn có phản hồi vật lý về việc theo dõi.

Đây là cấu trúc thư mục và hoạt động hiện tại của tôi. Cấu trúc khá đơn giản và hoạt động hoàn hảo với tôi, bao gồm mọi thứ tôi làm hàng ngày.

Để lấy dữ liệu từ API, tôi sẽ triển khai một client HTTP đơn giản với xác thực pydantic. Những gì chúng tôi sẽ tải xuống:

  1. Thư mục
  2. Hoạt động
  3. Thẻ
  4. Các mục thời gian

Tập luyện, giấc ngủ, bước đi và nhiều hơn nữa – Đồng hồ Garmin

Tôi là một chủ sở hữu tự hào của đồng hồ Forerunner 265. Những chiếc đồng hồ này là tốt nhất mà tôi từng có. Tính năng hàng đầu đối với tôi là thời gian sử dụng pin – 5-7 ngày. Garmin thu thập nhiều chỉ số, nhưng ở điểm khởi đầu, chúng ta sẽ tập trung vào:

  1. Giấc ngủ
  2. Tập luyện

Để tải dữ liệu Garmin, tôi sẽ sử dụng thư viện Python garminconnect.

Mô hình Logic

Phần này sử dụng nhiều khái niệm từ cuốn sách Thiết kế Cơ sở Dữ liệu của Alexey Makhotkin, mà tôi đã đọc với sự thích thú trước khi tạo mô hình logic cho dự án. Cuốn sách này rất đáng đọc. Bạn cũng có thể quan tâm đến các bài viết của tác giả và Substack của tác giả Minimal Modeling. Tôi cũng đang viết một bài đánh giá về cuốn sách này.

Bạn có thể nhận thấy tôi sử dụng nhiều liên kết đến các bài viết của Alexey trong bài viết này vì chúng rất hữu ích và tác giả đề cập đến nhiều khái niệm mô hình hóa dữ liệu.

Trích dẫn từ cuốn sách: "Mô hình logic tồn tại ngay cả khi bạn từ chối công nhận nó. Khi không được viết rõ ràng, nó phân tán ở 3 nơi: 1) lược đồ db vật lý 2) mã hệ thống 3) trí nhớ của mọi người."

Mô hình Logic sẽ trở thành nguồn thông tin duy nhất cho kho dữ liệu của tôi. Nếu tôi muốn xây dựng một tính năng mới, tôi sẽ bắt đầu từ Mô hình Logic trước và sau đó chuyển sang Thực hiện Vật lý. Bạn có thể nghĩ điều này là sự quan liêu thừa thãi cho một dự án cá nhân, nhưng tôi cố gắng không trở thành "cơn lốc chiến thuật" ở đây.

Sau khi kiểm tra API EARLY và thư viện garminconnect để xem dữ liệu mà họ cung cấp, chúng ta có thể trích xuất các thực thể, thuộc tính và liên kết cho mô hình logic.

Thực thể

Chúng ta bắt đầu bằng cách trích xuất các thực thể hoặc thực thể (danh từ). Tôi cũng thêm cột "nguồn" để rõ ràng về nơi dữ liệu đến từ.

Tên Ví dụ ID Tên bảng (vật lý) Nguồn
Thư mục Thời gian "280326" time_folder API EARLY
Hoạt động Thời gian "2052354" time_activity API EARLY
Thẻ Thời gian 14831771 time_tag API EARLY
Mục Thời gian "106227041" time_entry API EARLY
Loại Tập luyện 21 workout_type Garmin Connect
Tập luyện 20150174780 workout Garmin Connect
Giấc ngủ 1752277343000 sleep Garmin Connect

Thuộc tính

Tôi đã trích xuất bộ thuộc tính tối thiểu này bằng cách trả lời các câu hỏi.

Thực thể Câu hỏi Kiểu dữ liệu Logic Ví dụ Tên cột và kiểu (vật lý)
Thư mục Thời gian Tên của thư mục là gì? string "0. Vio" time_folder.name TEXT NOT NULL
Thư mục Thời gian Mô tả của thư mục là gì? string "Công việc" time_folder.description TEXT NOT NULL
Hoạt động Thời gian Tên của hoạt động là gì? string "Chơi game" time_activity.name TEXT NOT NULL
Hoạt động Thời gian Trạng thái của hoạt động là gì? enum "active", "inactive", "archived" time_activity.status ENUM NOT NULL
Hoạt động Thời gian Mô tả của hoạt động là gì? string "Chơi video game" time_activity.description TEXT NOT NULL
Thẻ Thời gian Nhãn của thẻ là gì? string "crafting_interpreters" time_tag.label TEXT NOT NULL
Mục Thời gian Mục bắt đầu khi nào? timestamp in UTC "2025-08-16 06:08:43+00:00" time_entry.start_at TIMESTAMPTZ NOT NULL
Mục Thời gian Mục kết thúc khi nào? timestamp in UTC "2025-08-16 07:22:27+00:00" time_entry.end_at TIMESTAMPTZ NOT NULL
Loại Tập luyện Tên của loại là gì? string "yoga" workout_type.name TEXT NOT NULL
Tập luyện Tên của buổi tập là gì? string "Yoga" workout.name TEXT NOT NULL
Tập luyện Buổi tập bắt đầu khi nào? timestamptz "2025-08-23 10:19:05+02:00" workout.start_at TIMESTAMPTZ NOT NULL
Tập luyện Buổi tập kết thúc khi nào? timestamptz "2025-08-23 10:31:56+02:00" workout.end_at TIMESTAMPTZ NOT NULL
Giấc ngủ Giấc ngủ bắt đầu khi nào? timestamptz "2025-08-22 21:49:46+02:00" sleep.start_at TIMESTAMPTZ NOT NULL
Giấc ngủ Giấc ngủ kết thúc khi nào? timestamptz "2025-08-23 05:57:46+02:00" sleep.end_at TIMESTAMPTZ NOT NULL

Liên kết

Hãy xác định các mối quan hệ giữa các thực thể.

Thực thể 1:Thực thể 2 Độ Cardinality Câu Tên bảng hoặc cột (vật lý)
Thư mục Thời gian : Hoạt động Thời gian 1:M Thư mục Thời gian chứa nhiều Hoạt động Thời gian. Hoạt động Thời gian thuộc về chỉ một Thư mục Thời gian time_activity.time_folder_id
Thư mục Thời gian : Thẻ Thời gian 1:M Thư mục Thời gian chứa nhiều Thẻ Thời gian. Thẻ Thời gian thuộc về chỉ một Thư mục Thời gian time_tag.time_folder_id
Hoạt động Thời gian : Mục Thời gian 1:M Hoạt động Thời gian chứa nhiều Mục Thời gian. Mục Thời gian thuộc về chỉ một Hoạt động Thời gian time_entry.time_activity_id
Mục Thời gian : Thẻ Thời gian M:N Mục Thời gian được gán với nhiều Thẻ Thời gian. Thẻ Thời gian có thể gán cho nhiều Mục Thời gian link_time_entry__time_tag
Loại Tập luyện : Tập luyện 1:M Loại Tập luyện chứa nhiều Tập luyện. Tập luyện chỉ có thể thuộc về một Loại Tập luyện workout.workout_type_id

Sơ đồ ERD vật lý

Tôi sử dụng d2 để hình dung sơ đồ ERD. Đây là một ngôn ngữ vẽ sơ đồ hỗ trợ các bảng SQL và ký hiệu Crow's foot.

Tôi khuyên bạn nên đọc các bài viết được viết bởi Alexey:

  • Sơ đồ ERD, phần I: các mối quan hệ nhiều-nhiều.
  • Sơ đồ ERD, phần II: các sơ đồ vật lý.

Tôi không thêm cột ký hiệu ERD vào thiết kế logic.

Dưới đây là một đoạn mã d2 cho thấy cách sơ đồ này được xây dựng:

Copy
time_folder: {
  shape: sql_table
  id: text {constraint: primary_key}
  name: text not null
  description: text not null
}

time_activity: {
  shape: sql_table
  id: text {constraint: primary_key}
  name: text not null
  status: enum not null
  description: text not null
  time_folder_id: text {constraint: foreign_key}
}

time_activity.time_folder_id <-> time_folder.id: {
  source-arrowhead.shape: cf-many
  target-arrowhead.shape: cf-one-required
}

# ...

Lưu ý rằng chúng ta sử dụng Khóa ngoại trong thiết kế. Sau này chúng ta sẽ nới lỏng yêu cầu này. Tại sao điều này nên được nới lỏng được nêu rõ trong bài viết Khóa ngoại @ Alexey Makhotkin. Vấn đề là chúng ta cần tải hai thực thể liên kết trong một giao dịch duy nhất. Nếu chúng ta tải lại các thư mục chỉ bằng cách ghi đè bảng, chúng ta sẽ cắt ngắn nó, có nghĩa là tất cả các bài viết và thẻ liên quan phụ thuộc vào các thư mục sẽ bị xóa. Nếu có một lỗi trong việc tải các thư mục, thì các bảng hoạt động và thẻ sẽ trống rỗng.

Tóm tắt

Chúng ta đã có những gì cho đến nay:

  • Các nguồn dữ liệu đã được khám phá (EARLY và Garmin)
  • Mô hình logic đã được xây dựng với các thực thể, thuộc tính và liên kết
  • Thiết kế vật lý đã được tạo và sơ đồ ERD đã được vẽ

Trong bài viết tiếp theo, tôi sẽ nói về các lớp DWH và việc nạp dữ liệu thô. Hãy theo dõi và đăng ký!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào