Skip to content

Commit c82ee97

Browse files
committed
fix(new-compiler): optimization fixes
1 parent 1bfb5ea commit c82ee97

File tree

2 files changed

+41
-70
lines changed

2 files changed

+41
-70
lines changed

packages/new-compiler/src/metadata/manager.test.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,60 +108,57 @@ describe("metadata", () => {
108108
expect((await loadMetadata(testDbPath)).stats!.totalEntries).toBe(3);
109109

110110
// Update existing entry (count should not increase)
111-
const result = await saveMetadata(testDbPath, [
111+
await saveMetadata(testDbPath, [
112112
createTestEntry({ hash: "entry-1", sourceText: "v2" }),
113113
]);
114-
expect(result.stats!.totalEntries).toBe(3);
115-
expect(result.entries["entry-1"].sourceText).toBe("v2");
114+
const updated = await loadMetadata(testDbPath);
115+
expect(updated.stats!.totalEntries).toBe(3);
116+
expect(updated.entries["entry-1"].sourceText).toBe("v2");
116117

117118
// Empty array should not change anything
118119
await saveMetadata(testDbPath, []);
119120
expect((await loadMetadata(testDbPath)).stats!.totalEntries).toBe(3);
120121
});
121122

122123
it("should handle large batch of entries", async () => {
123-
const entries = Array.from({ length: 1000 }, (_, i) =>
124+
const entries = Array.from({ length: 100 }, (_, i) =>
124125
createTestEntry({ hash: `batch-${i}` }),
125126
);
126127

127-
const result = await saveMetadata(testDbPath, entries);
128-
expect(result.stats!.totalEntries).toBe(1000);
128+
await saveMetadata(testDbPath, entries);
129+
expect((await loadMetadata(testDbPath)).stats!.totalEntries).toBe(100);
129130
});
130131

131132
it("should maintain data integrity after many operations", async () => {
132133
// Many saves with overlapping keys
133-
for (let i = 0; i < 50; i++) {
134+
for (let i = 0; i < 10; i++) {
134135
await saveMetadata(testDbPath, [
135136
createTestEntry({
136-
hash: `persistent-${i % 10}`,
137+
hash: `persistent-${i % 5}`,
137138
sourceText: `v${i}`,
138139
}),
139140
createTestEntry({ hash: `unique-${i}` }),
140141
]);
141142
}
142143

143144
const final = await loadMetadata(testDbPath);
144-
// 10 persistent + 50 unique = 60
145-
expect(final.stats!.totalEntries).toBe(60);
146-
147-
// Verify save result matches load result
148-
const saveResult = await saveMetadata(testDbPath, []);
149-
expect(saveResult.stats!.totalEntries).toBe(final.stats!.totalEntries);
145+
// 5 persistent + 10 unique = 15
146+
expect(final.stats!.totalEntries).toBe(15);
150147
});
151148
});
152149

153150
describe("concurrent access (single process)", () => {
154151
it("should handle concurrent operations from multiple calls", async () => {
155152
// LMDB handles concurrent writes via OS-level locking
156-
const promises = Array.from({ length: 20 }, async (_, i) => {
153+
const promises = Array.from({ length: 10 }, async (_, i) => {
157154
await saveMetadata(testDbPath, [
158155
createTestEntry({ hash: `concurrent-${i}` }),
159156
]);
160157
});
161158
await Promise.all(promises);
162159

163160
// Verify all entries are present
164-
expect((await loadMetadata(testDbPath)).stats!.totalEntries).toBe(20);
161+
expect((await loadMetadata(testDbPath)).stats!.totalEntries).toBe(10);
165162
});
166163
});
167164

packages/new-compiler/src/metadata/manager.ts

Lines changed: 28 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,48 @@ import type { MetadataSchema, PathConfig, TranslationEntry } from "../types";
55
import { getLingoDir } from "../utils/path-helpers";
66
import { logger } from "../utils/logger";
77

8-
// Special key for storing stats
98
const STATS_KEY = "__stats__";
10-
11-
// Metadata directory names for each environment
129
const METADATA_DIR_DEV = "metadata-dev";
1310
const METADATA_DIR_BUILD = "metadata-build";
1411

1512
/**
16-
* Opens an LMDB database connection at the given path.
13+
* Opens a short-lived LMDB connection.
14+
*
15+
* Short-lived over singleton: bundlers (Webpack/Next.js) spawn isolated workers
16+
* that can't share a single connection. LMDB's MVCC handles concurrent access.
1717
*/
18-
function openDatabase(dbPath: string): RootDatabase {
18+
function openDatabaseConnection(dbPath: string): RootDatabase {
1919
fs.mkdirSync(dbPath, { recursive: true });
2020

21-
// Build mode: disable fsync - metadata is deleted immediately after build, so durability is not needed.
22-
// Dev mode: keep sync enabled for consistency during long-running sessions.
2321
const isBuildMode = dbPath.endsWith(METADATA_DIR_BUILD);
2422

2523
try {
2624
return open({
2725
path: dbPath,
2826
compression: true,
27+
// Build: skip fsync (data is ephemeral). Dev: sync for durability.
2928
noSync: isBuildMode,
3029
});
3130
} catch (error) {
3231
const message = error instanceof Error ? error.message : String(error);
33-
throw new Error(
34-
`Failed to open LMDB metadata database at ${dbPath}. Error: ${message}`,
35-
);
32+
throw new Error(`Failed to open LMDB at ${dbPath}: ${message}`);
3633
}
3734
}
3835

3936
/**
40-
* Safely close database connection.
37+
* Releases file handles to allow directory cleanup (avoids EBUSY/EPERM on Windows).
4138
*/
42-
async function closeDatabase(db: RootDatabase, dbPath: string): Promise<void> {
39+
async function closeDatabaseConnection(
40+
db: RootDatabase,
41+
dbPath: string,
42+
): Promise<void> {
4343
try {
4444
await db.close();
4545
} catch (e) {
4646
logger.debug(`Error closing database at ${dbPath}: ${e}`);
4747
}
4848
}
4949

50-
/**
51-
* Read all entries from an open database.
52-
* Internal helper - does not manage connection lifecycle.
53-
*/
5450
function readEntriesFromDb(db: RootDatabase): MetadataSchema {
5551
const entries: Record<string, TranslationEntry> = {};
5652

@@ -84,62 +80,46 @@ export function createEmptyMetadata(): MetadataSchema {
8480
};
8581
}
8682

87-
/**
88-
* Load metadata from LMDB database.
89-
*/
9083
export async function loadMetadata(dbPath: string): Promise<MetadataSchema> {
91-
const db = openDatabase(dbPath);
84+
const db = openDatabaseConnection(dbPath);
9285
try {
9386
return readEntriesFromDb(db);
9487
} finally {
95-
await closeDatabase(db, dbPath);
88+
await closeDatabaseConnection(db, dbPath);
9689
}
9790
}
9891

9992
/**
100-
* Save translation entries to the metadata database.
93+
* Persists translation entries to LMDB.
10194
*
102-
* LMDB handles concurrency via MVCC, so multiple processes can write safely.
103-
*
104-
* @param dbPath - Path to the LMDB database directory
105-
* @param entries - Translation entries to add/update
106-
* @returns The updated metadata schema
95+
* Uses transactionSync to batch all writes into a single commit.
96+
* Async transactions are slow in Vite (~80-100ms) due to setImmediate scheduling.
10797
*/
10898
export async function saveMetadata(
10999
dbPath: string,
110100
entries: TranslationEntry[],
111-
): Promise<MetadataSchema> {
112-
const db = openDatabase(dbPath);
101+
): Promise<void> {
102+
const db = openDatabaseConnection(dbPath);
103+
113104
try {
114-
await db.transaction(() => {
105+
db.transactionSync(() => {
115106
for (const entry of entries) {
116-
db.put(entry.hash, entry);
107+
db.putSync(entry.hash, entry);
117108
}
118109

119-
// Count entries explicitly (excluding stats key) for clarity
120-
let entryCount = 0;
121-
for (const { key } of db.getRange()) {
122-
if (key !== STATS_KEY) {
123-
entryCount++;
124-
}
125-
}
126-
127-
const stats = {
110+
const totalKeys = db.getKeysCount();
111+
const entryCount =
112+
db.get(STATS_KEY) !== undefined ? totalKeys - 1 : totalKeys;
113+
db.putSync(STATS_KEY, {
128114
totalEntries: entryCount,
129115
lastUpdated: new Date().toISOString(),
130-
};
131-
db.put(STATS_KEY, stats);
116+
});
132117
});
133-
134-
return readEntriesFromDb(db);
135118
} finally {
136-
await closeDatabase(db, dbPath);
119+
await closeDatabaseConnection(db, dbPath);
137120
}
138121
}
139122

140-
/**
141-
* Clean up the metadata database directory.
142-
*/
143123
export function cleanupExistingMetadata(metadataDbPath: string): void {
144124
logger.debug(`Cleaning up metadata database: ${metadataDbPath}`);
145125

@@ -164,12 +144,6 @@ export function cleanupExistingMetadata(metadataDbPath: string): void {
164144
}
165145
}
166146

167-
/**
168-
* Get the absolute path to the metadata database directory
169-
*
170-
* @param config - Config with sourceRoot, lingoDir, and environment
171-
* @returns Absolute path to metadata database directory
172-
*/
173147
export function getMetadataPath(config: PathConfig): string {
174148
const dirname =
175149
config.environment === "development"

0 commit comments

Comments
 (0)