กระรอกดินต้นฉบับกำลังรวมกุญแจรูปทรงเดียวกันจากหลายถาดเข้าสู่ช่องเก็บกลางเพียงช่องเดียว

Go unique.Handle: ทำคีย์ซ้ำให้เหลือตัวแทนเดียวก่อนเก็บในแคช

พี่โชว์Engineering

ระบบที่รับชื่อ tenant, tag หรือชนิด event ซ้ำ ๆ มักเก็บ string เดิมหลายชุดและเทียบทีละไบต์ทุกครั้งครับ unique.Make เปลี่ยนค่าที่เปรียบเทียบได้ให้เป็น unique.Handle[T]: ค่าที่เท่ากันจะได้ handle ที่เท่ากัน จึงใช้ handle เป็นคีย์ภายในได้โดยไม่ต้องสร้างตาราง intern เอง

ให้นึกถึงห้องฝากกุญแจ ลูกค้านำกุญแจหน้าตาเหมือนกันเข้ามาหลายดอก แต่พนักงานออกบัตรหมายถึง “รูปแบบกุญแจนี้” เพียงใบเดียว การเทียบบัตรจึงไม่ต้องไล่ดูร่องกุญแจทุกครั้ง

package main

import (
	"fmt"
	"strings"
	"unique"
)

func main() {
	a := unique.Make("tenant-blue")
	b := unique.Make(strings.Clone("tenant-blue"))
	c := unique.Make("tenant-green")
	if a != b || a == c {
		panic("canonical identity is wrong")
	}
	if a.Value() != "tenant-blue" {
		panic("value round trip failed")
	}
	fmt.Println("unique.Handle checks passed")
}

ใช้ตรงไหนแล้วคุ้ม

เหมาะกับค่าที่ซ้ำสูงและมีอายุยาว เช่น tag ใน index หรือ symbol ใน parser เพราะ handle เป็นชนิด comparable และนำไปเป็น map key ได้ ส่วน request ID ที่แทบไม่ซ้ำจะไม่ได้ประโยชน์จากการรวมค่า แถมยังเพิ่มงาน canonicalization

เอกสารระบุว่า handle ที่ยังมีชีวิตจะทำให้ค่าต้นฉบับไม่ถูกเก็บกวาด ดังนั้นอย่าทำ handle จากข้อมูลผู้ใช้ที่มี cardinality ไม่จำกัดแล้วเก็บตลอด process หากขอบเขตมีแค่ request เดียว map ธรรมดาที่ถูกทิ้งพร้อม request อาจชัดกว่า และอย่าใช้ pointer จาก Value() เป็น API identity; contract ที่ควรพึ่งคือ handle ของค่าที่เท่ากันเทียบเท่ากัน

Recap

  • ใช้ unique.Make เมื่อค่าซ้ำสูงและต้องเทียบบ่อย
  • เก็บ Handle ภายใน boundary แทนเผยเป็นข้อมูลธุรกิจ
  • จำกัด cardinality และอายุ handle
  • benchmark กับ workload จริงก่อนอ้างว่าประหยัด memory