HTTP tracker phần 4: Sử dụng RDBMS PostgreSQL lưu trữ dữ liệu

Nội dung

Xin chào các bạn đã quay lại chuỗi bài viết thống kê dữ liệu trên website. Hôm nay chúng ta sẽ tiếp tục với phần lưu trữ vào hệ cơ sở dữ liệu quan hệ (RDBMS) PostgreSQL, cũng như cách lưu trữ thống kê thường thấy.

Tìm hiểu vấn đề

Ở các bài trước, chúng ta sử dụng Redis để lưu trữ dữ liệu thống kê các chỉ số theo yêu cầu.

Chắc hẳn bạn cũng thấy rằng, việc thống kê bằng Redis có hơi bất tiện. Ví dụ như bạn muốn xem tất cả lượt đăng ký trên thiết bị desktop thì buộc phải lấy hết các keys trong redis có pattern là *:desktop:*, rồi cộng lại bằng code. Hoặc bạn cần export ra file Excel số liệu theo ngày thì còn bất tiện hơn.

Vậy chúng ta sẽ khắc phục vấn đề trên bằng việc sử dụng RDBMS PostgreSQL vào việc lưu trữ và thống kê dữ liệu. Ở đây chúng ta sẽ sử dụng Postgresql, vì nó tương đối phổ biến trong giới data về vấn đề trên.

Thiết kế

Để giải quyết được vấn đề trên, chúng ta cần thiết kế dữ liệu trong PostgreSQL gồm 02 thành phần sau:

  • Raw table: nơi chúng ta sẽ đẩy dữ liệu từ đầu HTTP trực tiếp vào
  • Fact & Dimension table: chúng ta sẽ vận dụng Star Schema vào trong phần thiết kế. Đồng thời dữ liệu sẽ lưu trữ theo giờ cho phần thống kê (xem yêu cầu).

Raw table sẽ chính là dữ liệu vào từ HTTP, nên chúng ta sẽ lưu trữ như thế, không cần phải thiết kế lại. Riêng với Fact & Dimension tables, chúng ta sẽ thiết kế như sau:

timedevicebrowsersuccessmessagetotal_durationevent_count
datetimeintegerintegerbooleanintegerlonginteger
Fact table

Như vậy, chúng ta sẽ cần 03 bảng Dimension cho device, browsermessage, để đơn giản chúng ta sẽ chỉ sử dụng một vài giá trị sẵn cho mỗi bảng.

idmessage
integerString [OK, Out_of_Slot, Unknown]
Message table
iddevice_name
integerString [Mobile, Desktop, Unknown]
Device table
idbrowser_name
integerString [Chrome, Firefox, Microsoft Edge, Unknown]
Browser table

Thực hành

Khởi tạo database

Chúng ta sẽ khởi tạo một con PostgreSQL database nếu bạn chưa có bằng cách sử dụng docker (hướng dẫn chi tiết tại đây), sau đó chạy các câu lệnh sau:

Docker
docker pull postgres
docker run --name postgres-docker -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres

Hai câu lệnh trên sẽ

  • tải postgres image về local docker của bạn
  • Khởi tạo một container docker với name là postgres-docker, với database là postgres(default), user là postgres (default) và mật khẩu của user là password, đồng thời mapping container port (5432) ra local port cũng là 5432.

Sau đó chúng ta sẽ dùng chương trình DB management bạn hay dùng hoặc bạn có thể sử dụng dbeaver, hoặc vài IDE có hỗ trợ việc kết nối vào DB trực tiếp (IntelliJ IDEA). Để chúng ta sẽ khởi tạo các tables cần thiết.

Docker
CREATE TABLE raw_data (
                          id serial PRIMARY KEY,
                          event_time timestamp,
                          device varchar(20),
                          browser varchar(20),
                          result varchar(20),
                          message varchar(20),
                          duration integer,
                          account_id integer
);

CREATE TABLE fact_tracking_data (
    time timestamp,
    device integer,
    browser integer,
    success boolean,
    message integer,
    total_duration bigint,
    event_count integer
);

CREATE TABLE message_dim (
    id serial PRIMARY KEY,
    message varchar(20)
);
insert into message_dim (message) values ('OK'), ('Out_of_Slot'), ('Unknown');

CREATE TABLE device_dim (
    id serial PRIMARY KEY,
    device_name varchar(20)
);
insert into device_dim (device_name) values ('Mobile'), ('Desktop'), ('Unknown');

CREATE TABLE browser_dim (
    id serial PRIMARY KEY,
    browser_name varchar(20)
);
insert into browser_dim (browser_name) values ('Chrome'), ('Firefox'), ('Microsoft Edge'), ('Unknown');

Viết code

Chúng ta sẽ gắn thêm thư viện vào đầu HTTP trong build.gradle:

Java
dependencies {
    ...
    compile group: 'io.vertx', name: 'vertx-jdbc-client', version: "${vertxVersion}"
    implementation group: 'io.agroal', name: 'agroal-pool', version: '1.0'
    implementation group: 'org.postgresql', name: 'postgresql', version: '42.5.1'
}

Chúng ta sẽ chỉnh sửa một chút code trong bài tập như sau:

Java
public class MainStarter {
  ...
  //Need to use jdbcPool, so we initialize variable here
  JDBCPool jdbcPool = null;
  public void setJdbcPool(JDBCPool jdbcPool) {
    this.jdbcPool = jdbcPool;
  }
  //We also need to format timestamp when inserting to database
  DateFormat sqlF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
  
  ...
  
  private void saveToPostgres(Date receiveTime, JsonObject raw) {
      jdbcPool
            .preparedQuery("INSERT INTO raw_data(event_time, device, browser, result, message, duration, account_id) " +
                  "VALUES (?, ?, ?, ?, ?, ?, ?)")
            .execute(Tuple.of(sqlF.format(receiveTime), raw.getString("device"), raw.getString("browser"),
                  raw.getString("result"), raw.getString("message"), raw.getInteger("duration"),
                  raw.getInteger("account_id")))
            .onSuccess(rows -> {
               Row lastInsertId = rows.property(JDBCPool.GENERATED_KEYS);
               System.out.println("New ID is " + lastInsertId.getInteger(0));
            })
            .onFailure(cause -> {
               cause.printStackTrace();
               System.out.println("Failure: " + cause.getMessage());
            });
   }

Chúng ta cũng sẽ thêm method saveToPostgres mới vào trong handleRequest để xử lý đẩy dữ liệu vào Postgres.

Java
public void handleRequest(RoutingContext context) {
  ...
  try {
    ...
    this.saveToPostgres(receiveTime, dataBody);
  } catch (Exception e) {
     e.printStackTrace();
  }
}

Chúng ta cũng sẽ cần thêm vào đoạn code kết nối vào Postgres nằm trong method main như sau:

Java
public static void main(String[] args) {
  ...
  //Create Postgres pooled client
  JDBCPool pool = JDBCPool.pool(
        vertx,
        // configure the connection
        new JDBCConnectOptions()
              .setJdbcUrl("jdbc:postgresql://localhost:5432/postgres")
              .setUser("postgres")
              .setPassword("password"),
        // configure the pool
        new PoolOptions()
              .setMaxSize(4)
              .setName("postgres-pool")
  );
  handlerObj.setJdbcPool(pool);
  ...
}

Chạy thử code, và chúng ta bắn thử một event thử nhé.

ShellScript
curl --location --request POST 'http://localhost:8080/accept_tracking' \
--header 'Content-Type: application/json' \
--data-raw '{
  "device": "mobile",
  "browser": "Chrome",
  "result": "success",
  "message": "OK",
  "duration": 3432,
  "accountId": null
}'

Nếu trong bảng raw_data, bạn nhận được dữ liệu như thế này thì bạn đã thành công. Chúc mừng bạn.

Data trong raw_data table

Takeaways

Ở phần này bạn đã học thêm cách đưa dữ liệu raw vào trong PostgreSQL (RDBMS). Đương nhiên bạn có thể dễ dàng thay đổi các loại database khác như MySQL, Oracle hay MSSQL chỉ với việc thay đổi JDBC Url (trong method setJDBCUrl) và gắn thư viện driver tương ứng trong build.gradle.

Ở phần sau, chúng ta sẽ học cách build dữ liệu từ raw data và dim table sang fact table để ứng dụng vào báo cáo thống kê, cũng như một số lưu ý khi bạn sử dụng trên production.

Toàn bộ của bài này, bạn có thể xem tại Github. Hẹn gặp các bạn ở bài sau nhé.

Bài viết liên quan

SQL trong Data Analysis: Procedure và Function – 2 công cụ không thể thiếu

Xin chào các bạn đã quay trở lại chuỗi bài SQL trong Data Analysis...

Tự học Data Analyst: Tổng hợp chuỗi bài SQL 101 trong Data Analysis

Trong bài viết này, chúng ta sẽ tổng hợp các bài viết thành một...

SQL trong Data Analysis: Hiểu rõ và ứng dụng đệ quy (Recursive trong PostgreSQL)

Trong thế giới của cơ sở dữ liệu quan hệ, các truy vấn đệ...

[Phân Tích Dữ Liệu Với Python] Tập 1: Làm Quen Với Pandas

Trong thời đại tiến bộ của khoa học dữ liệu, khả năng phân tích...
spot_img