GoogleCloudPlatform / GoogleCloudPlatform/cloud-spanner-emulator
Spanner Emulator: UNIQUE constraint violation detection takes ~7 seconds
- Dominant language
- C++
- Stars
- 334
- Forks
- 77
- Avg merge
- 8m
- Merged PRs (30d)
- 2
Description
When inserting a duplicate value that violates a UNIQUE INDEX constraint, the Spanner Emulator takes approximately 7 seconds to detect and return the error. This delay is significantly longer than expected and may impact development and testing workflows.
### Environment
- **Spanner Emulator**: Running on localhost:9010
- **@google-cloud/spanner**: ^7.0.0
### Steps to Reproduce
1. Create a table with a UNIQUE INDEX on a column (e.g., `email`)
2. Insert a record successfully
3. Attempt to insert another record with the same value for the unique column
4. Observe the delay before the error is returned
### Expected Behavior
The UNIQUE constraint violation should be detected and an error should be returned immediately (within milliseconds).
### Actual Behavior
The error is returned after approximately 7 seconds, which suggests the emulator may be performing unnecessary operations or timeouts before detecting the constraint violation.
### Code to Reproduce
```typescript
import { Spanner } from "@google-cloud/spanner";
import { randomUUID } from "crypto";
const ALREADY_EXISTS = 6;
const projectId = "dev-project";
const instanceId = "dev-instance";
const databaseId = "dev-database";
process.env.SPANNER_EMULATOR_HOST = "localhost:9010";
function log(...args: any[]): void {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}]`, ...args);
}
const spanner = new Spanner({
projectId: projectId,
});
export const db = spanner.instance(instanceId).database(databaseId);
interface User {
id: string;
name: string;
email: string;
}
async function createUser(user: User): Promise {
try {
return await db.runTransactionAsync(async (tx) => {
const [rows] = await tx.run({
sql: `INSERT users (id, name, email)
VALUES (@id, @name, @email)
THEN RETURN *`,
params: {
id: user.id,
name: user.name,
email: user.email
},
});
await tx.commit();
return rows[0].toJSON() as User;
});
} catch (error) {
if (error && typeof error === "object" && "code" in error) {
const grpcCode = (error as { code: number }).code;
if (grpcCode === ALREADY_EXISTS) {
throw new Error(`User with this email already exists: ${user.email}`);
}
}
throw new Error(`Failed to create user: ${error}`);
}
}
async function main() {
const testEmail = "test@example.com";
try {
log("Attempting to create first user...");
await createUser({
id: randomUUID(),
name: "Test User 1",
email: testEmail,
});
log("✓ Successfully created first user");
log("\nAttempting to create second user (same email)...");
await createUser({
id: randomUUID(),
name: "Test User 2",
email: testEmail, // same email
});
log("✗ No error occurred (unexpected behavior)");
} catch (error: any) {
log("Error message:", error.message);
if (error && typeof error === "object" && "code" in error) {
log("Error code:", (error as { code: number }).code);
if ((error as { code: number }).code === ALREADY_EXISTS) {
log("→ UNIQUE INDEX violation: User with this email already exists");
}
}
if (error.details) {
log("Error details:", JSON.stringify(error.details, null, 2));
}
}
}
main()
```
### Database Schema
```sql
CREATE TABLE users (
id STRING(36) NOT NULL,
name STRING(100) NOT NULL,
email STRING(255) NOT NULL
) PRIMARY KEY(id);
CREATE UNIQUE INDEX idx_users_email ON users(email);
```
Contributor guide
Assessment
This issue has not been assessed yet.