Skip to content

GitLab

  • Menu
Projects Groups Snippets
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in
  • H happy-qa
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 11
    • Issues 11
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 0
    • Merge requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Monitor
    • Monitor
    • Metrics
    • Incidents
  • Packages & Registries
    • Packages & Registries
    • Package Registry
    • Infrastructure Registry
  • Analytics
    • Analytics
    • CI/CD
    • Repository
    • Value stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • Pornpavit Tantilavasute
  • happy-qa
  • Issues
  • #3

Closed
Open
Created Sep 23, 2026 by Pornpavit Tantilavasute@joe_qaMaintainer0 of 3 tasks completed0/3 tasks

[QA Brain MVP] T3: Atomic Transactional Idempotency Engine for Brain Mutations

สรุปงาน (Summary)

พัฒนา Transactional Idempotency Engine สำหรับคำสั่งสร้างและแก้ไขใน QA Brain (happyqa_brain_record_gotcha, POST /api/v1/workspaces/:wsId/brain/create และคำสั่งที่เกี่ยวข้อง) เพื่อรองรับการทำงานของ AI Agent หลายตัวพร้อมกัน ป้องกันการบันทึกข้อมูลซ้ำซ้อนจาก Network Retries และรับประกันการ Replay ข้อมูลที่ปลอดภัยต่อสิทธิ์การเข้าถึง โดยกำหนดอัลกอริทึมจัดการ Race Condition อย่างเป็นรูปธรรมเพื่อป้องกัน Unique Constraint Error หลุดไปหาผู้เรียก

อ้างอิงข้อกำหนดสถาปัตยกรรม: Canonical RFC v4.1 (Section 2.7, 3.2, 5.4)
ลำดับการทำงาน (Dependency): ขึ้นต่อกับ T1 (Data Model) และ T2 (Sanitizer)


ขอบเขตและงานที่ต้องทำ (Scope of Work)

1. โครงสร้างและการจัดการ Idempotency Record

  • ใช้ตาราง QaBrainIdempotencyKey ซึ่งมี Unique Key: [workspaceId, principalType, principalId, operation, key]
  • กำหนดอายุ Key (TTL) เป็น 24 ชั่วโมง นับจากเวลาที่สร้าง (expiresAt = now() + 24 hours)

2. ล็อคอัลกอริทึมจัดการ Concurrent Idempotency (Atomic Resolution Algorithm)

เพื่อแก้ปัญหา Race Condition ระหว่างคำขอที่ยิงเข้ามาพร้อมกัน (เช่น 10 คำขอพร้อมกันด้วย Key เดียวกัน) ห้ามใช้วิธี "ค้นหาก่อน แล้วสร้างทีหลัง" (Check-then-act) แต่ให้ใช้กระบวนการระดับฐานข้อมูลที่ป้องกัน Race Condition 100%:

                          ┌───────────────────────────┐
                          │   คำขอใหม่พร้อม IdemKey   │
                          └─────────────┬─────────────┘
                                        │
             ┌──────────────────────────▼──────────────────────────┐
             │ 1. Transactional Lock                               │
             │    - ใช้ pg_advisory_xact_lock ด้วย Hash ของ Key     │
             │      หรือ INSERT ... ON CONFLICT DO NOTHING         │
             │      ตามด้วย SELECT ... FOR UPDATE                  │
             └──────────────────────────┬──────────────────────────┘
                                        │
                     ┌──────────────────┴──────────────────┐
                     ▼                                     ▼
             [พบแถว Idempotency เดิม]              [ไม่พบแถว / ผู้ชนะแรก]
                     │                                     │
      ┌──────────────┴──────────────┐                      │
      │ 2. ตรวจสอบสถานะแถวเดิม       │                      │
      │ - แถวกำลังประมวลผล (In-flight)│                      │
      │   (Lock จะบล็อกรอจนกว่า       │                      │
      │    Tx แรก commit)           │                      │
      │ - เมื่อ Tx แรก commit แล้ว:   │                      │
      │   Reload ข้อมูลแถวล่าสุด     │                      │
      └──────────────┬──────────────┘                      │
                     │                                     │
      ┌──────────────┴──────────────┐                      │
      │ 3. ตรวจความถูกต้องของคำขอ    │                      │
      │ - Policy Ver ตรงกันไหม      │                      │
      │ - Fingerprint ตรงกันไหม     │                      │
      │   (ไม่ตรง -> 409 CONFLICT)   │                      │
      │ - Re-check สิทธิ์ผู้เรียก    │                      │
      │   (ไม่มีสิทธิ์ -> 403 FORBIDDEN)│                  │
      └──────────────┬──────────────┘                      │
                     │                                     │
                     ▼                                     ▼
        ┌─────────────────────────┐           ┌─────────────────────────┐
        │ 4. ส่งผล Replay แคชเดิม │           │ 5. ประมวลผลสร้าง Gotcha │
        │    (replayed: true)     │           │    - บันทึกแถว Gotcha   │
        │                         │           │    - เขียน responseSnap │
        └─────────────────────────┘           │    - Commit Transaction │
                                              │    (replayed: false)    │
                                              └─────────────────────────┘

รายละเอียดขั้นตอนในโค้ด:

  1. Advisory Transactional Lock:
    • คำนวณ 64-bit lock key จาก brain_idem:${workspaceId}:${principalType}:${principalId}:${operation}:${key}
    • รัน SELECT pg_advisory_xact_lock(:lockKey) ภายใน Prisma Interactive Transaction (tx.$executeRaw) เพื่อให้ทุกคำขอที่ใช้ Key เดียวกันเข้าคิวเรียงหนึ่งอย่างสมบูรณ์
  2. ค้นหาแถวที่มีอยู่แล้ว:
    • รัน tx.qaBrainIdempotencyKey.findUnique(...)
  3. กรณีพบแถวเดิม (Replay Resolution):
    • ตรวจสอบ sanitizerPolicyVer: ถ้านโยบายเปลี่ยน ตอบ 409
    • ตรวจสอบ requestFingerprint: เทียบกับ SHA-256 ของ Canonical Sanitized Payload ปัจจุบัน หากไม่ตรงกัน $\rightarrow$ ตอบกลับ HTTP 409 IDEMPOTENCY_CONFLICT
    • Replay Authorization Re-check: ตรวจสอบสิทธิ์ปัจจุบันของผู้เรียกกับ Project ID ของ Gotcha นั้น หากสิทธิ์ถูกเพิกถอนไปแล้ว ตอบกลับ HTTP 403 FORBIDDEN ทันที (ห้ามส่งข้อมูลจากแคชกลับ)
    • หากผ่านทุกเงื่อนไข $\rightarrow$ ส่งคืน responseSnapshot เดิมพร้อมระบุ "replayed": true
  4. กรณีไม่พบแถว (First Winner):
    • สร้าง Gotcha ในฐานข้อมูล (สถานะ DRAFT)
    • บันทึก Audit Log ผ่าน appendAudit
    • บันทึกแถว QaBrainIdempotencyKey พร้อมแนบ responseSnapshot และ expiresAt = now() + 24 hours
    • Commit Transaction $\rightarrow$ ส่งผลลัพธ์กลับด้วย "replayed": false

3. ระบบจัดการและล้าง Key ที่หมดอายุ (Key Expiration Pruning)

  • สร้างฟังก์ชัน Pruning หรือ scheduled task สำหรับลบแถวที่ expiresAt < now() ออกจากตารางเพื่อไม่ให้ฐานข้อมูลบวม

เกณฑ์การตรวจรับ (Acceptance Criteria)

  • Concurrent Execution Test: ยิงคำขอ record_gotcha พร้อมกัน 10 คำขอด้วย Idempotency Key เดียวกันแบบขนานกัน (Parallel Requests) ต้องสร้าง Gotcha ในฐานข้อมูลเพียง 1 แถวเท่านั้น และอีก 9 คำขอต้องได้รับผล Replay เดิม ("replayed": true) โดยไม่มีข้อผิดพลาด Unique Constraint Violation หลุดออกมาเด็ดขาด
  • Conflict Detection Test: ส่งคำขอเดิมซ้ำด้วย Key เดิมแต่เปลี่ยนเนื้อหา Payload ระบบต้องตอบกลับ IDEMPOTENCY_CONFLICT (HTTP 409)
  • Authorization Revocation Immunity Test: เมื่อผู้ใช้บันทึก Gotcha สำเร็จ แล้วถูกเพิกถอนสิทธิ์ออกจากโปรเจกต์ การส่งคำขอเดิมซ้ำด้วย Idempotency Key เดิมต้องได้ผลลัพธ์ FORBIDDEN ไม่ส่งข้อมูลเดิมกลับไป
Edited Sep 24, 2026 by Pornpavit Tantilavasute
Assignee
Assign to
Time tracking