同步目标与封存源常常不是同一个实例(kgseal vs kgmaint);用目标名去绑来源死信,
真实观测的 project 永远对不上,解除会被一律拒绝。新增 LIBRARY_SOURCE_INSTANCE,
留空回落到目标实例名,API 写记录与 worker 取指纹共用同一个来源实例名。
试过:真实 API GET/POST、go test ./internal/{httpapi,appconfig} 与 make verify 全绿。
This commit is contained in:
@@ -18,6 +18,9 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- 死信解除绑定 `LIBRARY_SOURCE_INSTANCE`(封存源),留空回落到 `LIBRARY_TARGET_INSTANCE`:
|
||||
同步目标与封存源常常不是同一个实例,用目标名去绑来源死信会让真实观测永远对不上
|
||||
(拒绝而不是误放行)。worker 取指纹、API 写记录用同一个来源实例名。
|
||||
- 结算/封存不再"死信存在即阻断",而是"每条实时死信都要被精确解除命中":
|
||||
`checkLibrarySourceSettlementReleased` / `ExportLibraryWithReleases` 对每条行
|
||||
重算指纹,未命中、行被改过、出现新行都继续阻断并点名。
|
||||
|
||||
@@ -203,6 +203,7 @@ func run(ctx context.Context, getenv func(string) string) error {
|
||||
VersionHistoryRepo: versionHistoryRepo,
|
||||
LibraryArchiveRoot: config.LibraryArchiveRoot,
|
||||
LibraryTargetInstance: config.LibraryTargetInstance,
|
||||
LibrarySourceInstance: config.SourceInstance(),
|
||||
LibraryAgentSocket: config.LibraryAgentSocket,
|
||||
LibraryCapacityBudgetBytes: config.LibraryCapacityBudgetBytes,
|
||||
LibraryProductionDatabaseURL: config.LibraryProductionDatabaseURL,
|
||||
|
||||
+1
-1
@@ -278,7 +278,7 @@ func workerHandlers(store *postgresstore.Store, config appconfig.Config) (map[st
|
||||
}
|
||||
// 平台审计过的精确死信解除指纹:读取失败必须阻断(fail closed),
|
||||
// 不能因为存储不可用就静默按"没有解除"或"全部解除"处理。
|
||||
released, releaseErr := store.DeadLetterReleaseFingerprints(ctx, config.LibraryTargetInstance)
|
||||
released, releaseErr := store.DeadLetterReleaseFingerprints(ctx, config.SourceInstance())
|
||||
if releaseErr != nil {
|
||||
_ = store.UpdateLibraryStage(ctx, libraryJobID, "export", "failed", nil, nil, nil, nil, releaseErr.Error(), time.Now().UTC())
|
||||
_ = store.AdvanceLibraryJob(ctx, libraryJobID, governance.LibraryJobFailed, "export", "dead letter release storage unavailable", "", "", "", time.Now().UTC())
|
||||
|
||||
@@ -96,7 +96,10 @@ type Config struct {
|
||||
LibraryRetentionDays int
|
||||
// 目标维护(实例级):同步/恢复期间关闭目标入口、停下写入者、排空在途请求。
|
||||
// 任一项留空即"未接线",生产写入会被拒绝——只锁治理库 scope 不算维护。
|
||||
LibraryTargetInstance string
|
||||
LibraryTargetInstance string
|
||||
// LibrarySourceInstance 是死信解除记录绑定的"来源实例"名字(封存源,不是同步目标)。
|
||||
// 留空时回落到 LibraryTargetInstance,保持单实例部署的旧行为。
|
||||
LibrarySourceInstance string
|
||||
LibraryTargetProject string
|
||||
LibraryTargetWriters []string
|
||||
LibraryTargetNonWriters map[string]string
|
||||
@@ -199,6 +202,7 @@ func Load(getenv func(string) string) (Config, error) {
|
||||
LibraryProductionS3AccessKey: strings.TrimSpace(getenv("LIBRARY_PRODUCTION_S3_ACCESS_KEY")),
|
||||
LibraryProductionS3SecretKey: strings.TrimSpace(getenv("LIBRARY_PRODUCTION_S3_SECRET_KEY")),
|
||||
LibraryTargetInstance: strings.TrimSpace(getenv("LIBRARY_TARGET_INSTANCE")),
|
||||
LibrarySourceInstance: strings.TrimSpace(getenv("LIBRARY_SOURCE_INSTANCE")),
|
||||
LibraryTargetProject: strings.TrimSpace(getenv("LIBRARY_TARGET_PROJECT")),
|
||||
LibraryTargetWriters: splitList(getenv("LIBRARY_TARGET_WRITERS")),
|
||||
LibraryTargetNonWriters: splitDeclarations(getenv("LIBRARY_TARGET_NON_WRITERS")),
|
||||
@@ -562,3 +566,12 @@ func positiveInt64OrDefault(value string, fallback int64) (int64, error) {
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// SourceInstance 是死信解除记录绑定的实例名:封存源与同步目标常常不是同一个实例,
|
||||
// 用目标名去绑来源死信会让真实观测永远对不上(拒绝而不是误放行)。
|
||||
func (config Config) SourceInstance() string {
|
||||
if config.LibrarySourceInstance != "" {
|
||||
return config.LibrarySourceInstance
|
||||
}
|
||||
return config.LibraryTargetInstance
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func deadLetterReleaseHandlers(w http.ResponseWriter, r *http.Request, config Co
|
||||
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "死信解除存储不可用"})
|
||||
return
|
||||
}
|
||||
instance := strings.TrimSpace(config.LibraryTargetInstance)
|
||||
instance := strings.TrimSpace(config.SourceInstance())
|
||||
if instance == "" {
|
||||
writeJSON(w, http.StatusServiceUnavailable, map[string]any{
|
||||
"code": "dead_letter_release_unwired", "reason": "未接线:没有配置来源实例身份,无法绑定解除记录",
|
||||
|
||||
@@ -148,6 +148,8 @@ type Config struct {
|
||||
// LibraryTargetInstance 是实例级维护的目标实例名;留空即"未接线",
|
||||
// 页面不能发起按预备份恢复,接口返回原因而不是假装能恢复。
|
||||
LibraryTargetInstance string
|
||||
// LibrarySourceInstance 是死信解除记录的来源实例名(封存源),留空回落到目标实例名。
|
||||
LibrarySourceInstance string
|
||||
// LibraryAgentSocket 是主机侧封存 agent 的 unix socket;留空即页面不能发起封存。
|
||||
LibraryAgentSocket string
|
||||
// LibraryCapacityBudgetBytes 是整库归档容量预算;0 = 未设置(容量门禁不放行)。
|
||||
@@ -2807,3 +2809,12 @@ func writeJSON(w http.ResponseWriter, status int, value any) {
|
||||
w.WriteHeader(status)
|
||||
_ = json.NewEncoder(w).Encode(value)
|
||||
}
|
||||
|
||||
// SourceInstance 是死信解除记录绑定的实例名:来源实例与同步目标常常不是同一个,
|
||||
// 用目标名去绑来源死信会让真实观测永远对不上。
|
||||
func (config Config) SourceInstance() string {
|
||||
if config.LibrarySourceInstance != "" {
|
||||
return config.LibrarySourceInstance
|
||||
}
|
||||
return config.LibraryTargetInstance
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user