perf(panel): kill per-request full-database clones (item 22, backend)
Data.Clone() was a full JSON marshal/unmarshal round trip (with a per-record map dance for unknown fields): 6.3ms and ~13.8k allocs per call on sandbox hardware, and every admin GET paid it at least twice (RequireAdmin's user lookup + the handler). Clone is now a reflective deep copy of the plain data graph — byte-identical output, proven against the JSON clone on the real seed database by TestCloneEquivalence — at 0.37ms / 538 allocs. CurrentAdmin resolves the session user under a read-only store View and no longer clones at all, so an admin GET takes exactly one snapshot. Sandbox benchmarks (11 servers / 26 customers / 25 nodes): overview 17.2ms -> 0.38ms, servers 13.3 -> 0.85ms, customers 20.5 -> 1.54ms, nodes 15.8 -> 3.45ms, jobs 13.0 -> 0.39ms per request.
This commit is contained in:
+78
-1
@@ -254,8 +254,85 @@ func (d Data) MarshalJSON() ([]byte, error) {
|
||||
return mergeMarshal(plain(d), d.Extra)
|
||||
}
|
||||
|
||||
// Clone is structuredClone(this.data).
|
||||
// Clone is structuredClone(this.data). Since round-3 item 22 it is a
|
||||
// reflective deep copy: the old JSON marshal/unmarshal round trip cost
|
||||
// a full serialize + parse of the whole database (with a per-record
|
||||
// map dance for unknown fields) on EVERY store read — twice per admin
|
||||
// request — and dominated panel latency on the router's CPU. The data
|
||||
// graph is plain exported structs/slices/maps/strings/numbers, which
|
||||
// reflection copies exactly; TestCloneEquivalence proves byte-identical
|
||||
// output against cloneViaJSON on the real seed database.
|
||||
func (d *Data) Clone() *Data {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
return cloneReflectValue(reflect.ValueOf(d)).Interface().(*Data)
|
||||
}
|
||||
|
||||
// cloneReflectValue deep-copies v. Structs are shallow-copied first (so
|
||||
// any unexported field keeps its value) and every exported field is
|
||||
// then replaced by its deep copy; []byte (incl. json.RawMessage) is
|
||||
// copied verbatim; scalars pass through.
|
||||
func cloneReflectValue(v reflect.Value) reflect.Value {
|
||||
if !v.IsValid() {
|
||||
return v
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Pointer:
|
||||
if v.IsNil() {
|
||||
return v
|
||||
}
|
||||
out := reflect.New(v.Type().Elem())
|
||||
out.Elem().Set(cloneReflectValue(v.Elem()))
|
||||
return out
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
return v
|
||||
}
|
||||
return reflect.ValueOf(cloneReflectValue(v.Elem()).Interface())
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return v
|
||||
}
|
||||
if v.Type().Elem().Kind() == reflect.Uint8 {
|
||||
out := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
|
||||
reflect.Copy(out, v)
|
||||
return out
|
||||
}
|
||||
out := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
out.Index(i).Set(cloneReflectValue(v.Index(i)))
|
||||
}
|
||||
return out
|
||||
case reflect.Map:
|
||||
if v.IsNil() {
|
||||
return v
|
||||
}
|
||||
out := reflect.MakeMapWithSize(v.Type(), v.Len())
|
||||
iter := v.MapRange()
|
||||
for iter.Next() {
|
||||
out.SetMapIndex(cloneReflectValue(iter.Key()), cloneReflectValue(iter.Value()))
|
||||
}
|
||||
return out
|
||||
case reflect.Struct:
|
||||
out := reflect.New(v.Type()).Elem()
|
||||
out.Set(v)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if v.Type().Field(i).PkgPath != "" {
|
||||
continue // unexported: kept from the shallow copy
|
||||
}
|
||||
out.Field(i).Set(cloneReflectValue(v.Field(i)))
|
||||
}
|
||||
return out
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// cloneViaJSON is the original Clone implementation: a full JSON
|
||||
// marshal/unmarshal round trip. Kept for the equivalence test and the
|
||||
// item-22 benchmarks that quantify what the reflective Clone replaced.
|
||||
func (d *Data) cloneViaJSON() *Data {
|
||||
raw, err := marshalJSON(d)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
Reference in New Issue
Block a user