Skip to content

Commit 5bca75a

Browse files
author
Timothy Dodd
committed
chore: update Angular and related dependencies to version 21.x
- Upgraded Angular packages to version 21.0.x for improved performance and features. - Updated other dependencies including @auth0/auth0-spa-js and @microsoft/signalr. - Adjusted devDependencies for Angular ESLint and TypeScript to align with the new Angular version. - Updated prettier and jasmine-core to their latest versions. feat: add composite index migration for Log table - Created a new SQL migration script to add a composite index on (Deployment, Pod, TimeStamp) for better query performance on /api/log/counts and /api/log/times endpoints. - Added README documentation for the migration process, expected time, and rollback instructions.
1 parent a90c71c commit 5bca75a

11 files changed

Lines changed: 2212 additions & 1617 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
-- Migration: Add composite index for better query performance
2+
-- Run this during a maintenance window as it may take time on large tables
3+
--
4+
-- This index significantly improves performance for:
5+
-- - /api/log/counts endpoint (GetDeploymentCounts)
6+
-- - /api/log/times endpoint (GetLatestEntryTimes)
7+
--
8+
-- Estimated time: Depends on table size
9+
-- - 1M rows: ~30 seconds
10+
-- - 10M rows: ~5 minutes
11+
-- - 100M rows: ~30-60 minutes
12+
--
13+
-- Progress monitoring:
14+
-- You can check progress in another session with:
15+
-- SHOW PROCESSLIST;
16+
17+
-- Check if index already exists before creating
18+
SELECT
19+
CASE
20+
WHEN COUNT(*) > 0 THEN 'Index already exists, skipping creation'
21+
ELSE 'Index does not exist, creating...'
22+
END AS status
23+
FROM information_schema.statistics
24+
WHERE table_schema = DATABASE()
25+
AND table_name = 'Log'
26+
AND index_name = 'Deployment_Pod_TimeStamp_idx';
27+
28+
-- Create the composite index
29+
-- Note: Remove the comment below to execute
30+
-- CREATE INDEX Deployment_Pod_TimeStamp_idx ON `Log` (Deployment, Pod, TimeStamp);
31+
32+
-- Verify index was created
33+
SELECT
34+
index_name,
35+
column_name,
36+
seq_in_index,
37+
cardinality,
38+
index_type
39+
FROM information_schema.statistics
40+
WHERE table_schema = DATABASE()
41+
AND table_name = 'Log'
42+
AND index_name = 'Deployment_Pod_TimeStamp_idx'
43+
ORDER BY seq_in_index;

database-migrations/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Database Migrations
2+
3+
This directory contains SQL migration scripts for the LogMk database.
4+
5+
## Migration 001: Add Composite Indexes
6+
7+
**File:** `001-add-composite-indexes.sql`
8+
9+
### Problem
10+
The `/api/log/counts` and `/api/log/times` endpoints perform `GROUP BY Deployment, Pod` queries on the entire Log table. Without proper indexing, these queries can timeout on large datasets (>1M rows).
11+
12+
### Solution
13+
Create a composite index on `(Deployment, Pod, TimeStamp)` columns.
14+
15+
### When to Run
16+
- If you see timeout errors in the LogMkAgent logs when calling `/api/log/counts` or `/api/log/times`
17+
- If the API logs show: `PERFORMANCE WARNING: Missing composite index 'Deployment_Pod_TimeStamp_idx'`
18+
- During a scheduled maintenance window (recommended for production)
19+
20+
### How to Run
21+
22+
1. **Connect to your MySQL database:**
23+
```bash
24+
mysql -h <host> -u <user> -p <database>
25+
```
26+
27+
2. **Check if index exists:**
28+
```sql
29+
SELECT COUNT(*)
30+
FROM information_schema.statistics
31+
WHERE table_schema = DATABASE()
32+
AND table_name = 'Log'
33+
AND index_name = 'Deployment_Pod_TimeStamp_idx';
34+
```
35+
If the count is 0, the index doesn't exist and should be created.
36+
37+
3. **Create the index:**
38+
```sql
39+
CREATE INDEX Deployment_Pod_TimeStamp_idx ON `Log` (Deployment, Pod, TimeStamp);
40+
```
41+
42+
4. **Monitor progress (in another MySQL session):**
43+
```sql
44+
SHOW PROCESSLIST;
45+
```
46+
47+
### Expected Time
48+
- **1M rows:** ~30 seconds
49+
- **10M rows:** ~5 minutes
50+
- **100M rows:** ~30-60 minutes
51+
52+
### Impact
53+
- **During creation:** High CPU and I/O usage, queries to the Log table may be slower
54+
- **After creation:** 10-100x faster queries for counts and times endpoints
55+
- **Disk space:** Approximately 10-20% of the Log table size
56+
57+
### Rollback
58+
If you need to remove the index:
59+
```sql
60+
DROP INDEX Deployment_Pod_TimeStamp_idx ON `Log`;
61+
```
62+
63+
### Alternative: Online Index Creation (MySQL 5.6+)
64+
For minimal downtime on large tables:
65+
```sql
66+
CREATE INDEX Deployment_Pod_TimeStamp_idx ON `Log` (Deployment, Pod, TimeStamp) ALGORITHM=INPLACE, LOCK=NONE;
67+
```
68+
69+
This allows concurrent reads/writes during index creation but may take longer.
70+
71+
## Future Migrations
72+
73+
Add new migration files with incremental numbering:
74+
- `002-description.sql`
75+
- `003-description.sql`
76+
- etc.

src/LogMkAgent/LogMkAgent.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
</PropertyGroup>
1414

1515
<ItemGroup>
16-
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.11" />
17-
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.11" />
18-
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.11" />
19-
<PackageReference Include="System.Text.Json" Version="9.0.11" />
16+
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
17+
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
18+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
19+
<PackageReference Include="System.Text.Json" Version="10.0.0" />
2020
</ItemGroup>
2121

2222
<ItemGroup>

src/LogMkApi/Controllers/LogController.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,17 @@ public async Task<ActionResult<LogResponse>> Create(
228228
.ToDictionary(g => g.Key, g => g.Count());
229229

230230
_logger.LogWarning("Batch {BatchId}: {ErrorCount} validation errors, {SkippedCount} logs skipped. " +
231-
"Error breakdown: {ErrorSummary}",
232-
batchId, errors.Count, skippedCount,
231+
"Error breakdown: {ErrorSummary}",
232+
batchId, errors.Count, skippedCount,
233233
string.Join(", ", errorSummary.Select(kvp => $"{kvp.Key}: {kvp.Value}")));
234-
234+
235+
// Log individual rejected lines for debugging (limit to first 5)
236+
foreach (var error in errors.Take(5))
237+
{
238+
_logger.LogDebug("Batch {BatchId}: Rejected log at index {Index}. Errors: [{Errors}]. Details: {LogLine}",
239+
batchId, error.Index, string.Join(", ", error.Errors), error.LogLine);
240+
}
241+
235242
_metrics.IncrementErrors("validation", skippedCount);
236243
}
237244

src/LogMkApi/Data/DatabaseInitializer.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,23 @@ FROM information_schema.statistics
6161

6262
if (!indexExists)
6363
{
64-
_logger.LogInformation("Creating missing composite index Deployment_Pod_TimeStamp_idx on Log table");
64+
_logger.LogWarning(
65+
"PERFORMANCE WARNING: Missing composite index 'Deployment_Pod_TimeStamp_idx' on Log table. " +
66+
"This will cause slow queries for /api/log/counts and /api/log/times endpoints. " +
67+
"To create the index manually, run: CREATE INDEX Deployment_Pod_TimeStamp_idx ON `Log` (Deployment, Pod, TimeStamp);");
6568

66-
// Create the index
67-
var createIndexSql = @"
68-
CREATE INDEX Deployment_Pod_TimeStamp_idx
69-
ON `Log` (Deployment, Pod, TimeStamp)";
70-
71-
db.Execute(createIndexSql);
72-
_logger.LogInformation("Successfully created composite index Deployment_Pod_TimeStamp_idx");
69+
// Note: We don't create the index automatically because it can take a very long time
70+
// on large tables and cause startup timeout. It should be created manually during
71+
// a maintenance window with: CREATE INDEX Deployment_Pod_TimeStamp_idx ON `Log` (Deployment, Pod, TimeStamp);
72+
}
73+
else
74+
{
75+
_logger.LogInformation("Composite index Deployment_Pod_TimeStamp_idx exists on Log table");
7376
}
7477
}
7578
catch (Exception ex)
7679
{
77-
_logger.LogWarning(ex, "Failed to create missing indexes, they may already exist or require manual creation");
80+
_logger.LogWarning(ex, "Failed to check indexes");
7881
}
7982
}
8083
}

src/LogMkApi/Data/LogRepo.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,11 @@ public async Task<IEnumerable<LatestDeploymentEntry>> GetLatestEntryTimes()
161161
{
162162
using (var db = _dbFactory.CreateConnection())
163163
{
164-
// Use the composite index (Deployment, Pod, TimeStamp) for optimal performance
165-
// The index is created by DatabaseInitializer.EnsureLogIndexes()
164+
// This query will be slow on large tables without the composite index (Deployment, Pod, TimeStamp)
165+
// See DatabaseInitializer.EnsureLogIndexes() for instructions on creating the index
166166
var query = @"
167167
SELECT Deployment, Pod, MAX(TimeStamp) AS TimeStamp
168-
FROM Log USE INDEX (Deployment_Pod_TimeStamp_idx)
168+
FROM Log
169169
GROUP BY Deployment, Pod
170170
";
171171
return await db.QueryAsync<LatestDeploymentEntry>(query);
@@ -176,11 +176,11 @@ public async Task<IEnumerable<DeploymentCount>> GetDeploymentCounts()
176176
{
177177
using (var db = _dbFactory.CreateConnection())
178178
{
179-
// Use the composite index (Deployment, Pod, TimeStamp) for optimal performance
180-
// The index is created by DatabaseInitializer.EnsureLogIndexes()
179+
// This query will be slow on large tables without the composite index (Deployment, Pod, TimeStamp)
180+
// See DatabaseInitializer.EnsureLogIndexes() for instructions on creating the index
181181
var query = @"
182182
SELECT Deployment, Pod, COUNT(*) AS Count
183-
FROM Log USE INDEX (Deployment_Pod_TimeStamp_idx)
183+
FROM Log
184184
GROUP BY Deployment, Pod
185185
";
186186
return await db.QueryAsync<DeploymentCount>(query);

src/LogMkApi/LogMkApi.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
<ItemGroup>
1616

1717
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.11" />
18-
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.11" />
18+
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.0" />
1919
<PackageReference Include="MySql.Data" Version="9.5.0" />
20-
<PackageReference Include="System.Text.Json" Version="9.0.11" />
20+
<PackageReference Include="System.Text.Json" Version="10.0.0" />
2121
<PackageReference Include="Microsoft.AspNetCore.SpaProxy">
22-
<Version>9.0.11</Version>
22+
<Version>10.0.0</Version>
2323
</PackageReference>
2424
</ItemGroup>
2525
<ItemGroup>

src/LogMkCommon/LogParser.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public static class LogParser
4747
@"(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})" // 2024-01-15 12:34:56
4848
};
4949

50+
// First try standard patterns
5051
foreach (var pattern in patterns)
5152
{
5253
var match = Regex.Match(line, pattern);
@@ -59,6 +60,22 @@ public static class LogParser
5960
}
6061
}
6162

63+
// Try to handle malformed time-only formats like "21: 28: 16]" (spaces around colons)
64+
var malformedTimePattern = @"(\d{1,2}):\s*(\d{1,2}):\s*(\d{1,2})\]";
65+
var malformedMatch = Regex.Match(line, malformedTimePattern);
66+
if (malformedMatch.Success)
67+
{
68+
if (int.TryParse(malformedMatch.Groups[1].Value, out var hours) &&
69+
int.TryParse(malformedMatch.Groups[2].Value, out var minutes) &&
70+
int.TryParse(malformedMatch.Groups[3].Value, out var seconds) &&
71+
hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59 && seconds >= 0 && seconds <= 59)
72+
{
73+
// Use today's date with the parsed time
74+
var today = DateTimeOffset.UtcNow.Date;
75+
return new DateTimeOffset(today.Year, today.Month, today.Day, hours, minutes, seconds, TimeSpan.Zero);
76+
}
77+
}
78+
6279
return null;
6380
}
6481

0 commit comments

Comments
 (0)