perf(go): deduplicate unchanged mileage samples
This commit is contained in:
@@ -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 == "" {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user