fix: provisioning race condition and noisy ClickHouse logs
Defer provisionAsync() until after the transaction commits using TransactionSynchronization.afterCommit(). Previously the @Async thread raced the @Transactional commit — findById returned null because the tenant INSERT wasn't visible yet. Downgrade ClickHouse UNKNOWN_TABLE errors to DEBUG level in InfrastructureService. These are expected on fresh installs before any cameleer-server has created the tables. Make the onboarding slug field read-only (derived from org name). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -224,7 +224,11 @@ public class InfrastructureService {
|
||||
.put(table, cnt);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to query ClickHouse table '{}' for tenant stats: {}", table, e.getMessage(), e);
|
||||
if (e.getMessage() != null && e.getMessage().contains("UNKNOWN_TABLE")) {
|
||||
log.debug("ClickHouse table '{}' does not exist yet — skipping", table);
|
||||
} else {
|
||||
log.error("Failed to query ClickHouse table '{}' for tenant stats: {}", table, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -256,8 +260,12 @@ public class InfrastructureService {
|
||||
result.add(new ChTableStats(table, rs.getLong("cnt")));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to query ClickHouse table '{}' for tenant '{}': {}",
|
||||
table, tenantId, e.getMessage(), e);
|
||||
if (e.getMessage() != null && e.getMessage().contains("UNKNOWN_TABLE")) {
|
||||
log.debug("ClickHouse table '{}' does not exist yet — skipping", table);
|
||||
} else {
|
||||
log.error("Failed to query ClickHouse table '{}' for tenant '{}': {}",
|
||||
table, tenantId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
@@ -116,9 +118,19 @@ public class VendorTenantService {
|
||||
AuditAction.TENANT_CREATE, "provision:" + tenant.getSlug(),
|
||||
null, null, "SUCCESS", null);
|
||||
|
||||
// 4. Provision server asynchronously (Docker containers, health check, config push)
|
||||
// 4. Provision server asynchronously AFTER transaction commits
|
||||
// (the async thread needs the tenant row to be visible)
|
||||
if (tenantProvisioner.isAvailable()) {
|
||||
self.provisionAsync(tenant.getId(), tenant.getSlug(), tenant.getTier().name(), license.getToken(), actorId);
|
||||
UUID tenantId = tenant.getId();
|
||||
String slug = tenant.getSlug();
|
||||
String tierName = tenant.getTier().name();
|
||||
String token = license.getToken();
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
self.provisionAsync(tenantId, slug, tierName, token, actorId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return tenant;
|
||||
|
||||
Reference in New Issue
Block a user