Skip to content

Commit 6347405

Browse files
authored
Merge branch 'master' into resumable-ingestion
2 parents 3f58f48 + b0b0ded commit 6347405

10 files changed

Lines changed: 230 additions & 92 deletions

File tree

indexer/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ Data monitored:
150150
### Cache hit/miss tracking
151151

152152
`CacheService` tracks per bucket:
153-
- `raffles`, `users`, `others`
153+
- `raffles`, `users`, `stats`, `others`
154154
- `hits`, `misses`, `requests`
155155
- `hit rate` (percent) via `getAllCacheHitRates()`
156156

@@ -222,6 +222,13 @@ src/
222222
├── ingestor/
223223
│ ├── cursor-manager.service.ts
224224
│ └── ingestor.module.ts
225+
├── api/
226+
│ ├── api.module.ts # Internal HTTP API
227+
│ └── controllers/
228+
│ ├── raffles.controller.ts
229+
│ ├── users.controller.ts
230+
│ ├── leaderboard.controller.ts
231+
│ └── stats.controller.ts
225232
├── processors/
226233
│ ├── processors.module.ts
227234
│ ├── raffle.processor.ts

indexer/docker-compose.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version: '3.8'
2+
3+
services:
4+
postgres:
5+
image: postgres:16-alpine
6+
environment:
7+
- POSTGRES_PASSWORD=postgres
8+
- POSTGRES_DB=tikka_indexer
9+
ports:
10+
- "5432:5432"
11+
volumes:
12+
- pgdata:/var/lib/postgresql/data
13+
14+
redis:
15+
image: redis:7-alpine
16+
command: redis-server /usr/local/etc/redis/redis.conf
17+
ports:
18+
- "6379:6379"
19+
volumes:
20+
- ./redis.conf:/usr/local/etc/redis/redis.conf
21+
22+
volumes:
23+
pgdata:

indexer/package-lock.json

Lines changed: 35 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

indexer/redis.conf

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,21 @@
1-
# Production-ready Redis configuration for Tikka indexer cache
1+
# Redis configuration for Tikka Indexer Cache
22

3-
# Max memory available to Redis (reasonable default for 8GB instance)
4-
# Adjust this based on server RAM and memory reserved for system/other services.
3+
# Memory limit
54
maxmemory 4gb
65

76
# Eviction policy
8-
# Chosen allkeys-lru to evict least-recently-used keys across all keys,
9-
# ensuring frequently accessed data stays in cache and less-used keys are evicted first.
10-
# This is safer in mixed TTL and non-TTL cache usage patterns.
7+
# allkeys-lru: Evict any key using LRU when maxmemory is reached.
118
maxmemory-policy allkeys-lru
129

13-
# Optional: ensure a hard limit to avoid OOM kills by the OS.
14-
# Redis itself will enforce maxmemory + eviction policy.
15-
# maxmemory-samples controls the precision of LRU approximation.
10+
# LRU samples
11+
# 5 is a good balance between precision and CPU usage.
1612
maxmemory-samples 5
1713

18-
# Additional hardening
19-
# Avoid persistence costs on caching nodes; adjust if persistence is required.
20-
save ""
21-
appendonly no
14+
# Persistence (optional for cache, but good for quick recovery)
15+
save 900 1
16+
save 300 10
17+
save 60 10000
2218

23-
# Clients and buffer limits
24-
client-output-buffer-limit normal 0 0 0
25-
client-output-buffer-limit slave 256mb 64mb 60
26-
client-output-buffer-limit pubsub 32mb 8mb 60
27-
28-
# Always log warnings and errors
29-
loglevel notice
19+
# Networking
20+
bind 0.0.0.0
21+
port 6379
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Controller, Get, Query } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
import { Repository } from 'typeorm';
4+
import { UserEntity } from '../../database/entities/user.entity';
5+
import { CacheService } from '../../cache/cache.service';
6+
7+
@Controller('leaderboard')
8+
export class LeaderboardController {
9+
constructor(
10+
@InjectRepository(UserEntity)
11+
private readonly userRepo: Repository<UserEntity>,
12+
private readonly cacheService: CacheService,
13+
) {}
14+
15+
@Get()
16+
async getLeaderboard(
17+
@Query('by') by: 'wins' | 'volume' | 'tickets' = 'wins',
18+
@Query('limit') limit: number = 50,
19+
) {
20+
const cacheKey = `leaderboard:${by}:${limit}`;
21+
// Use generic leaderboard key for invalidation if needed, or specific one
22+
// Requirements said "leaderboard" key.
23+
24+
return this.cacheService.wrap('leaderboard', 60, async () => {
25+
const query = this.userRepo.createQueryBuilder('user');
26+
27+
if (by === 'wins') {
28+
query.orderBy('user.totalRafflesWon', 'DESC');
29+
} else if (by === 'volume') {
30+
query.orderBy('user.totalPrizeXlm', 'DESC');
31+
} else if (by === 'tickets') {
32+
query.orderBy('user.totalTicketsBought', 'DESC');
33+
}
34+
35+
query.take(limit);
36+
37+
const entries = await query.getMany();
38+
return { entries };
39+
});
40+
}
41+
}

indexer/src/cache/cache.service.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,25 @@ describe('CacheService', () => {
7070
lb = await service.getLeaderboard();
7171
expect(lb).toBeNull();
7272
});
73+
74+
it('should handle user profile', async () => {
75+
const address = 'GUSER';
76+
await service.setUserProfile(address, { wins: 2 });
77+
let profile = await service.getUserProfile(address);
78+
expect(profile.wins).toBe(2);
79+
80+
await service.invalidateUserProfile(address);
81+
profile = await service.getUserProfile(address);
82+
expect(profile).toBeNull();
83+
});
84+
85+
it('should handle platform stats', async () => {
86+
await service.setPlatformStats({ totalRaffles: 100 });
87+
let stats = await service.getPlatformStats();
88+
expect(stats.totalRaffles).toBe(100);
89+
90+
await service.invalidatePlatformStats();
91+
stats = await service.getPlatformStats();
92+
expect(stats).toBeNull();
93+
});
7394
});

0 commit comments

Comments
 (0)