เครื่องมืออ่าน CSV (DuckDB Engine)
ภาพรวม
ผลลัพธ์ของ audience ในระบบนี้ถูกเก็บเป็น ไฟล์ CSV บน MinIO/S3 ไม่ใช่แถวในตารางฐานข้อมูล เพื่อรองรับสมาชิกจำนวนมาก เมื่อต้องอ่านข้อมูลกลับ เช่น แบ่งหน้าดูรายชื่อสมาชิก นับจำนวนสมาชิก หรือดึง LINE user id ไปส่งข้อความ ระบบจะใช้ engine ตัวนี้เป็นตัวอ่าน
NestJS เดิมใช้ DuckDB แบบ in-memory ร่วมกับ httpfs เพื่ออ่าน CSV จาก S3 โดยตรง ส่วนฝั่ง Go เขียนใหม่เป็น pure Go โดยไม่ใช้ CGO คือ stream ไฟล์จาก object storage แล้ว parse CSV เอง (รองรับ quoted field, newline ภายในค่า และ BOM) จากนั้นเก็บเป็นโครงสร้างตารางใน memory พร้อม cache อายุ 30 วินาทีต่อหนึ่ง path โดยยังคง public surface และรูปแบบผลลัพธ์ให้เหมือน DuckDB เดิมทุกประการ
มี endpoint สำหรับทดสอบหนึ่งตัวคือ GET /api/duckdb ในโมดูล duckdbctl
ใช้ debug ว่าอ่าน CSV จาก path ที่ระบุได้หรือไม่
Business Flow
- โมดูล Audience Management และ Audience Filter สร้าง audience แล้วเขียนผลลัพธ์เป็น CSV ลง object storage
- เมื่อ cms-web เรียก
GET /api/audiences/:id/memberservice จะเรียกLoadCsvFromMinio(path)หากยังไม่มีในแคช แล้วจึงเรียกGetPaginatedLineUserIds - cache มีอายุ 30 วินาที การขอหน้าถัด ๆ ไปภายในช่วงนี้จึงไม่ต้องดาวน์โหลดไฟล์ใหม่
GetTotalLineUserCountใช้ตอบจำนวนสมาชิกทั้งหมด- เมื่อเลิกใช้งานจะเรียก
DestroyTempTable,CleanupExpiredTablesหรือCleanupAllTables - rich menu ที่ผูกกับ audience ใช้ path เดียวกันนี้เพื่อ resolve รายชื่อ LINE user
ผ่าน
richmenu/create_update.goที่ฟังก์ชันprocessRichMenuAudience GET /api/duckdb?path=...&page=&limit=เป็น endpoint สำหรับ debug ที่ยิงตรงเข้าฟังก์ชันTest
พฤติกรรมที่จงใจรักษาไว้ให้เหมือน DuckDB เดิม
- ค่าว่างใน CSV ถือเป็น NULL ดังนั้นแถวที่ช่อง
"Line UserID"ว่างจะถูกกรองออกด้วยเงื่อนไขIS NOT NULL GetPaginatedLineUserIdsและTestอ่าน คอลัมน์แรกของแถว ตามพฤติกรรมObject.values(r)[0]ของโค้ด TypeScript ไม่ใช่คอลัมน์ที่ชื่อ"Line UserID"_ensureTableExistsซึ่งเป็นเส้นทางของ endpoint ทดสอบ ไม่ ใส่ null filter- ค่าที่คืนกลับเป็น string ทั้งหมด (DuckDB เดิมจะ infer ชนิดตัวเลขให้) ซึ่งบันทึกไว้เป็น deviation ที่ยอมรับ
ไฟล์และฟังก์ชันหลัก
| ไฟล์ | บทบาท |
|---|---|
internal/duckdb/duckdb.go | engine ตัวจริง เข้าถึงผ่าน Deps.CSVEngine |
internal/modules/duckdbctl/controller.go | RegisterRoutes ลงทะเบียน authed.GET("/duckdb", h.test) |
internal/modules/duckdbctl/dto.go | query params โดย path บังคับ ส่วน page และ limit เป็น optional |
| Method | Route | Handler | Guard |
|---|---|---|---|
| GET | /api/duckdb | h.test | group authed (global JWT) ไม่มี policy check |
Public API ของ engine: LoadCsvFromMinio, GetPaginatedLineUserIds, GetTotalLineUserCount,
DestroyTempTable, ListTables, CleanupTableCache, CleanupExpiredTables, CleanupAllTables,
GetCacheStats, Test
จุดเชื่อมต่อกับ Service อื่น
- Permission — ต้องมี token ที่ผ่าน global
JwtAuthซึ่งบังคับว่าต้องมีlineOaIdแต่ไม่มี policy check - Storage — อ่านไฟล์จาก MinIO/S3 ผ่าน
internal/storage - ผู้บริโภค — Audience Management, Audience Filter และ Rich Menu
- ข้อควรระวัง — cache เป็น in-memory ต่อ process หากรันหลาย replica แต่ละ replica จะมี cache แยกกัน