perf(lastleg): limit transport reconciliation facts and trace sync phases
This commit is contained in:
+48
-4
@@ -2,6 +2,8 @@ package com.baiyue.lastleg.appointmentwork;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
@@ -12,6 +14,7 @@ import static com.baiyue.lastleg.appointmentwork.AppointmentWorkPolicy.*;
|
||||
|
||||
@Service
|
||||
public class AppointmentWorkSyncService {
|
||||
private static final Logger LOG=LoggerFactory.getLogger(AppointmentWorkSyncService.class);
|
||||
private static final Actor SYSTEM=new Actor("appointment-work-sync","服务端工作范围同步",Set.of(LIST,ORDER_READ));
|
||||
private final AppointmentWorkStore store;
|
||||
private final AppointmentWorkFactGateway gateway;
|
||||
@@ -20,12 +23,17 @@ public class AppointmentWorkSyncService {
|
||||
@Transactional
|
||||
public SyncResult synchronize(SyncRequest request) {
|
||||
if(request==null || request.size()<1 || request.size()>100) throw bad("同步批次须为1至100柜");
|
||||
var timing=new SyncTiming();boolean success=false;
|
||||
try {
|
||||
var guard=store.lock();
|
||||
if(request.afterContainerBookingId()!=null && !Objects.equals(guard.getScanCursor(),request.afterContainerBookingId())) throw conflict("来源同步游标已变化,请从首批重新核对");
|
||||
timing.next(SyncPhase.DISCOVER);
|
||||
var page=gateway.discover(request.afterContainerBookingId(),request.size());
|
||||
timing.pageContainers=page.containers().size();timing.complete=page.complete();
|
||||
if(request.afterContainerBookingId()==null) { guard.setScanScopeVersion(page.scopeVersion()); guard.setScanRequirementVersion(guard.getRequirementVersion()); }
|
||||
else if(!Objects.equals(guard.getScanScopeVersion(),page.scopeVersion())) throw conflict("来源范围扫描中变化,请从首批重试");
|
||||
else if(guard.getScanRequirementVersion()!=guard.getRequirementVersion()) throw conflict("预约需求扫描中变化,请从首批重试");
|
||||
timing.next(SyncPhase.GENERATE);
|
||||
// 保留全部历史与稳定键顺序;逐柜更新立即进入本次工作集,不跨事务缓存。
|
||||
var known=workingSet();
|
||||
int created=0; var ids=new HashSet<String>();
|
||||
@@ -51,18 +59,54 @@ public class AppointmentWorkSyncService {
|
||||
}
|
||||
}
|
||||
}
|
||||
// An existing row is never deleted because an order left the active source page.
|
||||
// Empty return and other unfinished work continue to read their original authority.
|
||||
// 原来源离开当前页也保留历史事项,未完成工作仍在第二轮按原业务完整核对。
|
||||
timing.next(SyncPhase.TRANSPORT_FACTS);
|
||||
var all=new ArrayList<>(store.all());
|
||||
var facts=gateway.facts(all.stream().filter(s->s.getType()!=Type.ROUTE && s.getTripKey()==null && !"BATCH_REPLACED".equals(s.getRetirementReason())).toList());
|
||||
// 首轮仅归并运输关系;保留已关联车次的退役成员,才能发现解绑并恢复原运输事项。
|
||||
var transportItems=all.stream().filter(s->s.getType()==Type.TRANSPORT && s.getTripKey()==null && !"BATCH_REPLACED".equals(s.getRetirementReason())).toList();
|
||||
timing.transportFactsCount=transportItems.size();
|
||||
var facts=gateway.facts(transportItems);
|
||||
timing.next(SyncPhase.RECONCILE);
|
||||
reconcileTransport(all,facts);
|
||||
timing.next(SyncPhase.ACTIVE_FACTS);
|
||||
all=new ArrayList<>(store.all());
|
||||
facts=gateway.facts(all.stream().filter(s->!s.isRetired()).toList());
|
||||
var activeItems=all.stream().filter(s->!s.isRetired()).toList();
|
||||
timing.activeFactsCount=activeItems.size();
|
||||
facts=gateway.facts(activeItems);
|
||||
timing.next(SyncPhase.OBSERVE_CHECKPOINT);
|
||||
observe(all,facts,SYSTEM);
|
||||
guard.setScanCursor(page.complete()?null:page.nextCursor());
|
||||
if(page.complete()) { guard.setInitialized(true); guard.setScopeVersion(page.scopeVersion()); guard.setSynchronizedRequirementVersion(guard.getRequirementVersion()); guard.setCompletedAt(LocalDateTime.now(ZoneOffset.UTC)); }
|
||||
store.checkpoint(guard);
|
||||
success=true;
|
||||
return new SyncResult(page.containers().size(),created,page.nextCursor(),page.complete());
|
||||
} finally { timing.finish(success); }
|
||||
}
|
||||
/** 固定阶段名用于慢同步定位,不包含来源、游标或异常业务内容。 */
|
||||
private enum SyncPhase { GUARD, DISCOVER, GENERATE, TRANSPORT_FACTS, RECONCILE, ACTIVE_FACTS, OBSERVE_CHECKPOINT }
|
||||
/** 仅统计本次方法执行,不含事务代理提交,不能当作精确锁持有时间。 */
|
||||
private static final class SyncTiming {
|
||||
private final long started=System.nanoTime();
|
||||
private final long[] elapsed=new long[SyncPhase.values().length];
|
||||
private long phaseStarted=started;
|
||||
private SyncPhase phase=SyncPhase.GUARD;
|
||||
private int pageContainers=-1,transportFactsCount=-1,activeFactsCount=-1;
|
||||
private boolean complete;
|
||||
/** 完成前一阶段后开始下一阶段;事实阶段包含紧邻的数据库回读。 */
|
||||
private void next(SyncPhase next) {
|
||||
long now=System.nanoTime();elapsed[phase.ordinal()]+=now-phaseStarted;phaseStarted=now;phase=next;
|
||||
}
|
||||
/** 成功和失败共用一条慢调用摘要,未到达的计数保持为负一。 */
|
||||
private void finish(boolean success) {
|
||||
long now=System.nanoTime();elapsed[phase.ordinal()]+=now-phaseStarted;
|
||||
long totalMs=java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(now-started);
|
||||
if(totalMs<250) return;
|
||||
LOG.warn("Appointment work sync slow: totalMs={}, guardMs={}, discoverMs={}, generateMs={}, transportFactsMs={}, reconcileMs={}, activeFactsMs={}, observeCheckpointMs={}, pageContainers={}, transportFactsCount={}, activeFactsCount={}, complete={}, failedStage={}",
|
||||
totalMs,millis(SyncPhase.GUARD),millis(SyncPhase.DISCOVER),millis(SyncPhase.GENERATE),millis(SyncPhase.TRANSPORT_FACTS),millis(SyncPhase.RECONCILE),millis(SyncPhase.ACTIVE_FACTS),millis(SyncPhase.OBSERVE_CHECKPOINT),
|
||||
pageContainers,transportFactsCount,activeFactsCount,complete,success?"NONE":phase.name());
|
||||
}
|
||||
/** 阶段仅输出整数毫秒,不携带业务标识。 */
|
||||
private long millis(SyncPhase value) { return java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(elapsed[value.ordinal()]); }
|
||||
}
|
||||
static void validateSource(ContainerSource source) {
|
||||
if(source==null || source.containerBookingId()==null || source.containerBookingId().isBlank() || source.departedAt()==null && source.departedLocalAt()==null && source.sailedEvidenceLocalAt()==null || source.sourceVersion()==null || source.establishedFlow()==null || source.requirements()==null)
|
||||
|
||||
Reference in New Issue
Block a user