perf(go): deduplicate unchanged mileage samples

This commit is contained in:
lingniu
2026-07-03 08:39:48 +08:00
parent c1640b4332
commit 8f5afd20a6
2 changed files with 80 additions and 3 deletions

View File

@@ -4,8 +4,10 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope" "lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
@@ -16,8 +18,10 @@ type Execer interface {
} }
type Writer struct { type Writer struct {
exec Execer exec Execer
loc *time.Location loc *time.Location
mu sync.Mutex
lastTotalMileage map[string]float64
} }
type MetricSample struct { type MetricSample struct {
@@ -34,7 +38,7 @@ func NewWriter(exec Execer, loc *time.Location) *Writer {
if loc == nil { if loc == nil {
loc = time.FixedZone("Asia/Shanghai", 8*3600) loc = time.FixedZone("Asia/Shanghai", 8*3600)
} }
return &Writer{exec: exec, loc: loc} return &Writer{exec: exec, loc: loc, lastTotalMileage: map[string]float64{}}
} }
func (w *Writer) EnsureSchema(ctx context.Context) error { func (w *Writer) EnsureSchema(ctx context.Context) error {
@@ -50,6 +54,9 @@ func (w *Writer) Append(ctx context.Context, env envelope.FrameEnvelope) error {
return err return err
} }
for _, sample := range samples { for _, sample := range samples {
if w.seenSameMileage(sample) {
continue
}
if _, err := w.exec.ExecContext(ctx, upsertDailyMileageSQL, if _, err := w.exec.ExecContext(ctx, upsertDailyMileageSQL,
sample.VIN, sample.VIN,
sample.StatDate, sample.StatDate,
@@ -62,6 +69,23 @@ func (w *Writer) Append(ctx context.Context, env envelope.FrameEnvelope) error {
return nil return nil
} }
func (w *Writer) seenSameMileage(sample MetricSample) bool {
prefix := fmt.Sprintf("%s|%s|", sample.VIN, sample.Protocol)
key := prefix + sample.StatDate
w.mu.Lock()
defer w.mu.Unlock()
if last, ok := w.lastTotalMileage[key]; ok && last == sample.TotalMileageKM {
return true
}
for existing := range w.lastTotalMileage {
if strings.HasPrefix(existing, prefix) && existing != key {
delete(w.lastTotalMileage, existing)
}
}
w.lastTotalMileage[key] = sample.TotalMileageKM
return false
}
func SamplesFromEnvelope(env envelope.FrameEnvelope, loc *time.Location) ([]MetricSample, error) { func SamplesFromEnvelope(env envelope.FrameEnvelope, loc *time.Location) ([]MetricSample, error) {
vin := strings.TrimSpace(env.VIN) vin := strings.TrimSpace(env.VIN)
if vin == "" { if vin == "" {

View File

@@ -149,6 +149,59 @@ func TestWriterEnsuresSchemaAndUpsertsDailyMileage(t *testing.T) {
} }
} }
func TestWriterSkipsConsecutiveDuplicateMileageSamples(t *testing.T) {
exec := &recordingExec{}
writer := NewWriter(exec, time.FixedZone("Asia/Shanghai", 8*3600))
event := envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
VIN: "LNBVIN00000000001",
EventTimeMS: time.Date(2026, 7, 1, 9, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)).UnixMilli(),
Fields: map[string]any{
envelope.FieldTotalMileageKM: 10241.2,
},
}
if err := writer.Append(context.Background(), event); err != nil {
t.Fatalf("first Append() error = %v", err)
}
event.ReceivedAtMS += 1000
if err := writer.Append(context.Background(), event); err != nil {
t.Fatalf("duplicate Append() error = %v", err)
}
if len(exec.calls) != 1 {
t.Fatalf("exec calls = %d, want 1", len(exec.calls))
}
}
func TestWriterKeepsOnlyLatestDateInMileageCache(t *testing.T) {
exec := &recordingExec{}
writer := NewWriter(exec, time.FixedZone("Asia/Shanghai", 8*3600))
event := envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
VIN: "LNBVIN00000000001",
Fields: map[string]any{
envelope.FieldTotalMileageKM: 10241.2,
},
}
event.EventTimeMS = time.Date(2026, 7, 1, 23, 59, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)).UnixMilli()
if err := writer.Append(context.Background(), event); err != nil {
t.Fatalf("first Append() error = %v", err)
}
event.EventTimeMS = time.Date(2026, 7, 2, 0, 1, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)).UnixMilli()
if err := writer.Append(context.Background(), event); err != nil {
t.Fatalf("next-day Append() error = %v", err)
}
if len(exec.calls) != 2 {
t.Fatalf("exec calls = %d, want 2", len(exec.calls))
}
if len(writer.lastTotalMileage) != 1 {
t.Fatalf("cache entries = %d, want 1", len(writer.lastTotalMileage))
}
}
type execCall struct { type execCall struct {
query string query string
args []any args []any