Architecture
AstralAnalytics is fan-in: many event producers, one event sink. The shape is deliberately small — a single packet type, a single exchange, a single table.
Modules
Module | Build artefact | Role |
|---|---|---|
|
| Shared packet + service implementation. Imported by |
|
| Bukkit plugin. Emits events. |
|
| Velocity proxy plugin. Emits events. |
| MageHic plugin | Single subscriber. Persists to Postgres. |
The packet
There is one packet in the entire network:
Registered at opcode 0x00 by AnalyticsPacketRegistry. Opcodes are scoped to the registry, so AstralAnalytics doesn't collide with AstralCore's own packet space.
The exchange
Every producer registers the same exchange and publishes LogEventPacket instances. The producer-side registration uses an empty packet listener — producers don't consume their own events. Only the master attaches a non-trivial handler.
Producer flow
The constructor of AnalyticsServiceImpl takes the server name and group, so the resulting events inherit the right tags without the caller having to think about it. On Paper they come from AstralPaperAPI.serverInformation(). On Velocity they're hard-coded to "proxy".
If the publish fails, the exception is logged at WARN and swallowed — analytics never block gameplay.
Master consumer flow
GeoIP enrichment is best-effort — it has a 5-second connect timeout, a 5-second read timeout, and returns "UNKNOWN" on any error. The lookup is asynchronous; the eventual insert chains off the GeoIP future via thenCompose.
The repository uses a PGobject of type jsonb so the payload is stored natively (and indexable through the GIN index).
MageHic-local shortcut
When the producer is the master (or another MageHic plugin running in the same JVM), there is no point publishing to RabbitMQ just to read it back. The master exposes MHAnalyticsService implements AnalyticsService that calls EventService.logEvent directly:
The server_name and server_group columns end up as the literal string "magehic" so MageHic-side events are easy to filter or exclude in Metabase queries.
What is not in this design
No retries. If RabbitMQ is down at publish time, the event is dropped (and logged).
No back-pressure. Producers fire-and-forget.
No client-side batching. Each event is its own packet.
No schema for
payload— it is aJSONBblob keyed only bytype. Conventions live in Events, not in code.
These trade-offs are explicit: telemetry that interferes with gameplay is worse than telemetry that occasionally drops a sample.