fix(manticore): 同步命令读增量指针改用严格读取,引擎不可达时终止而非从头重灌

ManticoreKeyValue::get() 在引擎重启/不可达时返回默认值 0,导致增量同步从头重灌
并把指针写回低值,引发 AI 插件向量化风暴。新增 getStrict()/queryOneStrict(),
读取失败抛异常,五个同步命令据此终止本轮。
This commit is contained in:
kuaifan 2026-10-01 10:02:13 +00:00
parent 621017c0c6
commit 8bb169b4d0
7 changed files with 91 additions and 5 deletions

View File

@ -74,7 +74,13 @@ class SyncFileToManticore extends Command
do {
$round++;
$lastId = $isIncremental ? intval(ManticoreKeyValue::get($lastKey, 0)) : 0;
try {
// 严格读取:读失败(引擎重启/不可达)不能当成 0,否则会从头重灌并把指针写回低值
$lastId = $isIncremental ? intval(ManticoreKeyValue::getStrict($lastKey)) : 0;
} catch (\RuntimeException $e) {
$this->error("读取同步指针失败,本轮终止(避免从头重灌):" . $e->getMessage());
break;
}
if ($round === 1) {
if ($lastId > 0) {

View File

@ -81,7 +81,13 @@ class SyncMsgToManticore extends Command
// 持续处理循环(增量模式下)
do {
$round++;
$lastId = $isIncremental ? intval(ManticoreKeyValue::get($lastKey, 0)) : 0;
try {
// 严格读取:读失败(引擎重启/不可达)不能当成 0,否则会从头重灌并把指针写回低值
$lastId = $isIncremental ? intval(ManticoreKeyValue::getStrict($lastKey)) : 0;
} catch (\RuntimeException $e) {
$this->error("读取同步指针失败,本轮终止(避免从头重灌):" . $e->getMessage());
break;
}
if ($round === 1) {
if ($lastId > 0) {

View File

@ -68,7 +68,13 @@ class SyncProjectToManticore extends Command
do {
$round++;
$lastId = $isIncremental ? intval(ManticoreKeyValue::get($lastKey, 0)) : 0;
try {
// 严格读取:读失败(引擎重启/不可达)不能当成 0,否则会从头重灌并把指针写回低值
$lastId = $isIncremental ? intval(ManticoreKeyValue::getStrict($lastKey)) : 0;
} catch (\RuntimeException $e) {
$this->error("读取同步指针失败,本轮终止(避免从头重灌):" . $e->getMessage());
break;
}
if ($round === 1) {
if ($lastId > 0) {

View File

@ -68,7 +68,13 @@ class SyncTaskToManticore extends Command
do {
$round++;
$lastId = $isIncremental ? intval(ManticoreKeyValue::get($lastKey, 0)) : 0;
try {
// 严格读取:读失败(引擎重启/不可达)不能当成 0,否则会从头重灌并把指针写回低值
$lastId = $isIncremental ? intval(ManticoreKeyValue::getStrict($lastKey)) : 0;
} catch (\RuntimeException $e) {
$this->error("读取同步指针失败,本轮终止(避免从头重灌):" . $e->getMessage());
break;
}
if ($round === 1) {
if ($lastId > 0) {

View File

@ -68,7 +68,13 @@ class SyncUserToManticore extends Command
do {
$round++;
$lastId = $isIncremental ? intval(ManticoreKeyValue::get($lastKey, 0)) : 0;
try {
// 严格读取:读失败(引擎重启/不可达)不能当成 0,否则会从头重灌并把指针写回低值
$lastId = $isIncremental ? intval(ManticoreKeyValue::getStrict($lastKey)) : 0;
} catch (\RuntimeException $e) {
$this->error("读取同步指针失败,本轮终止(避免从头重灌):" . $e->getMessage());
break;
}
if ($round === 1) {
if ($lastId > 0) {

View File

@ -598,6 +598,37 @@ class ManticoreBase
);
}
/**
* 查询单行(严格版):连接/查询失败抛异常,而不是和「没有这一行」一样返回 null
*
* 用于「读不到就当默认值」会造成后果的场景,如同步命令读增量指针——
* 引擎不可达时若当成指针 0,会从头重灌全部数据。
*
* @param string $sql SQL语句
* @param array $params 参数
* @return array|null 单行结果;没有匹配行返回 null
* @throws \RuntimeException 连接或查询失败
*/
public function queryOneStrict(string $sql, array $params = []): ?array
{
$failed = new \stdClass();
$result = $this->runWithRetry(
function (PDO $pdo) use ($sql, $params) {
$stmt = $pdo->prepare($sql);
$this->bindParams($stmt, $params);
$stmt->execute();
$row = $stmt->fetch();
return $row ? $this->convertNumericTypesRow($row) : null;
},
$failed,
['sql' => $sql, 'params' => $params]
);
if ($result === $failed) {
throw new \RuntimeException('Manticore 不可达或查询失败');
}
return $result;
}
/**
* 转换结果集中的数值类型
* PDO 默认将 BIGINT 等数值类型返回为字符串,这里统一转换

View File

@ -34,6 +34,31 @@ class ManticoreKeyValue
return $result ? $result['v'] : $default;
}
/**
* 严格获取值:键不存在返回 null,引擎不可达/查询失败抛异常
*
* get() 对「键不存在」和「读取失败」都返回默认值,无法区分。同步命令用它读增量指针:
* 引擎重启期间读失败若当成 0,会从头重灌全部数据并把指针写回低值。
*
* @param string $key 键
* @return string|null 值;键不存在返回 null
* @throws \RuntimeException 引擎不可达或查询失败
*/
public static function getStrict(string $key): ?string
{
if (!Apps::isInstalled("search")) {
throw new \RuntimeException('Manticore Search 未安装');
}
$instance = new ManticoreBase();
$result = $instance->queryOneStrict(
"SELECT v FROM key_values WHERE k = ?",
[$key]
);
return $result ? (string) $result['v'] : null;
}
/**
* 设置值
*