Skip to content

Commit ce4a6e9

Browse files
authored
Event broadcast based cache invalidation (#3001)
* Skill * otlp and benchmark fixes * Benchmark tracing improvements * Tracing fix * Tracing fix * Rust SDK performance fix 1 * Rust SDK performance fix 2 * Rust SDK performance fix 3 * Rust SDK performance fix 4 * Rust SDK performance fix 5 * Rust SDK performance fix 6 * More spans * Typescript SDK improvements #1 * Typescript SDK improvements #2 * Typescript SDK improvements #3 * Typescript SDK improvements #4 * Invocation API improvements * Typescript SDK improvements #5 * Remove an unnecessary clone * Typescript SDK improvements #6 * Rust SDK clippy & format * Returning environment_id in registered agent lookup * TS SDK lint fixes * Benchmark components recompiled and name fix * Fix * Initial version of deploy event broadcast * Generalized event broadcast * More caching * Missing file * Clippy and format * Updated config * Deparated domain change events * Cleanup 1 * Cleanup 2 * Security scheme change broadcast * Fixes and cleanup * Format * Fix * Fix db index creation * Change index name, and add db-migration-skill * Skill update
1 parent 5198321 commit ce4a6e9

File tree

54 files changed

+4494
-167
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+4494
-167
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
name: db-migration-scripts
3+
description: Writing database migration SQL scripts. Use when creating or modifying migration files under db/migration/ directories, adding tables, indexes, or columns.
4+
---
5+
6+
# Database Migration Scripts
7+
8+
## Dual-Database Support
9+
10+
Every migration must be written for **both** PostgreSQL and SQLite. Migration files live in parallel directories:
11+
- `db/migration/postgres/NNN_description.sql`
12+
- `db/migration/sqlite/NNN_description.sql`
13+
14+
Both files must have the same numbered prefix and name. Use the appropriate SQL dialect for each (e.g., `BIGSERIAL` vs `INTEGER PRIMARY KEY AUTOINCREMENT`, `UUID` vs `TEXT`, `TIMESTAMPTZ` vs `TEXT`).
15+
16+
## File Naming
17+
18+
Migration files are numbered sequentially with zero-padded three-digit prefixes:
19+
```
20+
001_init.sql
21+
002_code_first_routes.sql
22+
003_wasi_config.sql
23+
```
24+
25+
Check existing files to determine the next number.
26+
27+
## Index Naming Convention
28+
29+
Index names follow the format: `<table>_<column(s)>_<idx|uk>`
30+
31+
- `_idx` for regular indexes
32+
- `_uk` for unique indexes
33+
34+
Examples:
35+
```sql
36+
CREATE INDEX accounts_deleted_at_idx ON accounts (deleted_at);
37+
CREATE UNIQUE INDEX accounts_email_uk ON accounts (email) WHERE deleted_at IS NULL;
38+
CREATE UNIQUE INDEX plugins_name_version_uk ON plugins (account_id, name, version);
39+
```
40+
41+
## Primary Keys
42+
43+
- **Do not create indexes on primary key columns.** Both PostgreSQL and SQLite automatically create an index for primary key columns.
44+
- Name primary key constraints as `<table>_pk`:
45+
```sql
46+
CONSTRAINT accounts_pk PRIMARY KEY (account_id)
47+
```
48+
49+
## Column Types
50+
51+
Use the same column types in PostgreSQL and SQLite whenever possible, to make it easier to write queries that work on both databases. Only use database-specific types (e.g., `BIGSERIAL` vs `INTEGER PRIMARY KEY AUTOINCREMENT`, `UUID` vs `TEXT`, `TIMESTAMPTZ` vs `TEXT`, `TEXT[]` vs `TEXT`) when there is no common alternative.
52+
53+
## Table Style
54+
55+
- Use uppercase SQL keywords (`CREATE TABLE`, `NOT NULL`, `PRIMARY KEY`)
56+
- Column definitions are indented and aligned
57+
- Primary key constraints are defined inline or as named table constraints

Cargo.lock

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

golem-api-grpc/proto/golem/registry/v1/registry_service.proto

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ service RegistryService {
5252
rpc GetActiveRoutesForDomain (GetActiveRoutesForDomainRequest) returns (GetActiveRoutesForDomainResponse);
5353
rpc GetActiveMcpForDomain (GetActiveMcpForDomainRequest) returns (GetActiveMcpForDomainResponse);
5454
rpc GetCurrentEnvironmentState (GetCurrentEnvironmentStateRequest) returns (GetCurrentEnvironmentStateResponse);
55+
56+
// registry invalidation stream — multiplexed event types
57+
rpc SubscribeRegistryInvalidations(SubscribeRegistryInvalidationsRequest)
58+
returns (stream RegistryInvalidationEvent);
5559
}
5660

5761
message AuthenticateTokenRequest {
@@ -309,6 +313,7 @@ message ResolveAgentTypeByNamesResponse {
309313
message ResolveAgentTypeByNamesSuccessResponse {
310314
RegisteredAgentType agent_type = 1;
311315
golem.common.EnvironmentId environment_id = 2;
316+
uint64 deployment_revision = 3;
312317
}
313318

314319
message GetActiveRoutesForDomainRequest {
@@ -355,3 +360,57 @@ message GetCurrentEnvironmentStateResponse {
355360
message GetCurrentEnvironmentStateSuccessResponse {
356361
EnvironmentState environment_state = 1;
357362
}
363+
364+
// ── Multiplexed registry invalidation stream ──
365+
366+
message SubscribeRegistryInvalidationsRequest {
367+
// The event_id of the last event the client successfully processed.
368+
// If omitted, the server starts streaming from live events only (no replay).
369+
optional uint64 last_seen_event_id = 1;
370+
}
371+
372+
// Streamed to worker-service subscribers when any registry state changes.
373+
// Each event carries exactly one payload variant.
374+
message RegistryInvalidationEvent {
375+
uint64 event_id = 1;
376+
oneof payload {
377+
CursorExpiredEvent cursor_expired = 2;
378+
DeploymentChangedEvent deployment_changed = 3;
379+
AccountTokensInvalidatedEvent account_tokens_invalidated = 4;
380+
EnvironmentPermissionsChangedEvent permissions_changed = 5;
381+
DomainRegistrationChangedEvent domain_registration_changed = 6;
382+
SecuritySchemeChangedEvent security_scheme_changed = 7;
383+
}
384+
}
385+
386+
// Sent when the client's cursor is older than the server's retention window.
387+
// The client must flush all caches and use this event's event_id as its new cursor.
388+
message CursorExpiredEvent {}
389+
390+
// Sent when the current deployment for an environment changes.
391+
message DeploymentChangedEvent {
392+
golem.common.EnvironmentId environment_id = 1;
393+
uint64 deployment_revision = 2;
394+
}
395+
396+
// Sent when domain registrations change (create/delete).
397+
message DomainRegistrationChangedEvent {
398+
golem.common.EnvironmentId environment_id = 1;
399+
repeated string domains = 2;
400+
}
401+
402+
// Sent when an account's tokens are invalidated (token deleted or account soft-deleted).
403+
message AccountTokensInvalidatedEvent {
404+
golem.common.AccountId account_id = 1;
405+
}
406+
407+
// Sent when environment sharing permissions change (create/update/delete share).
408+
message EnvironmentPermissionsChangedEvent {
409+
golem.common.EnvironmentId environment_id = 1;
410+
golem.common.AccountId grantee_account_id = 2;
411+
}
412+
413+
// Sent when a security scheme is created, updated, or deleted.
414+
message SecuritySchemeChangedEvent {
415+
golem.common.EnvironmentId environment_id = 1;
416+
}

golem-common/src/base_model/agent.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
use crate::base_model::account::AccountId;
1616
use crate::base_model::component::{ComponentId, ComponentRevision};
17+
use crate::base_model::deployment::DeploymentRevision;
1718
use crate::base_model::environment::EnvironmentId;
1819
use crate::base_model::AgentId;
1920
use crate::model::Empty;
@@ -110,9 +111,59 @@ impl From<DeployedRegisteredAgentType> for RegisteredAgentType {
110111

111112
/// Result of resolving an agent type by names, bundling the agent type
112113
/// with the environment it belongs to.
114+
#[derive(Clone)]
113115
pub struct ResolvedAgentType {
114116
pub registered_agent_type: RegisteredAgentType,
115117
pub environment_id: EnvironmentId,
118+
pub deployment_revision: DeploymentRevision,
119+
}
120+
121+
/// Event received from the registry service when any registry state changes.
122+
#[derive(Debug, Clone)]
123+
pub enum RegistryInvalidationEvent {
124+
/// The client's cursor is older than the server's retention window.
125+
CursorExpired { event_id: u64 },
126+
/// A deployment changed for an environment.
127+
DeploymentChanged {
128+
event_id: u64,
129+
environment_id: EnvironmentId,
130+
deployment_revision: u64,
131+
},
132+
/// Domain registrations changed (created/deleted).
133+
DomainRegistrationChanged {
134+
event_id: u64,
135+
environment_id: EnvironmentId,
136+
domains: Vec<String>,
137+
},
138+
/// An account's tokens were invalidated.
139+
AccountTokensInvalidated {
140+
event_id: u64,
141+
account_id: AccountId,
142+
},
143+
/// Environment sharing permissions changed.
144+
EnvironmentPermissionsChanged {
145+
event_id: u64,
146+
environment_id: EnvironmentId,
147+
grantee_account_id: AccountId,
148+
},
149+
/// A security scheme was created, updated, or deleted.
150+
SecuritySchemeChanged {
151+
event_id: u64,
152+
environment_id: EnvironmentId,
153+
},
154+
}
155+
156+
impl RegistryInvalidationEvent {
157+
pub fn event_id(&self) -> u64 {
158+
match self {
159+
Self::CursorExpired { event_id } => *event_id,
160+
Self::DeploymentChanged { event_id, .. } => *event_id,
161+
Self::DomainRegistrationChanged { event_id, .. } => *event_id,
162+
Self::AccountTokensInvalidated { event_id, .. } => *event_id,
163+
Self::EnvironmentPermissionsChanged { event_id, .. } => *event_id,
164+
Self::SecuritySchemeChanged { event_id, .. } => *event_id,
165+
}
166+
}
116167
}
117168

118169
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, IntoValue, FromValue)]

golem-debugging-service/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ pub async fn run_debug_worker_executor<T: Bootstrap<DebugContext> + ?Sized>(
323323
let lazy_worker_activator = Arc::new(LazyWorkerActivator::new());
324324
let shutdown = golem_worker_executor::services::shutdown::Shutdown::new();
325325

326-
let (worker_executor_impl, epoch_thread, epoch_stop) =
326+
let (worker_executor_impl, epoch_thread, epoch_stop, _registry_service) =
327327
create_worker_executor_impl::<DebugContext, T>(
328328
golem_config.clone(),
329329
bootstrap,

golem-registry-service/config/registry-service.sample.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ GOLEM__DB__TYPE="Sqlite"
2121
GOLEM__DB__CONFIG__DATABASE="golem_service.db"
2222
GOLEM__DB__CONFIG__FOREIGN_KEYS=false
2323
GOLEM__DB__CONFIG__MAX_CONNECTIONS=10
24+
GOLEM__DEPLOYMENT_EVENTS__CLEANUP_INTERVAL="1h"
25+
GOLEM__DEPLOYMENT_EVENTS__RETENTION="1day"
2426
GOLEM__DOMAIN_PROVISIONER__TYPE="NoOp"
2527
GOLEM__GRPC__PORT=9090
2628
GOLEM__GRPC__TLS__TYPE="Disabled"

golem-registry-service/config/registry-service.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ database = "golem_service.db"
3838
foreign_keys = false
3939
max_connections = 10
4040

41+
[deployment_events]
42+
cleanup_interval = "1h"
43+
retention = "1day"
44+
4145
[domain_provisioner]
4246
type = "NoOp"
4347

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CREATE TABLE registry_change_events (
2+
event_id BIGSERIAL PRIMARY KEY,
3+
event_type SMALLINT NOT NULL DEFAULT 0,
4+
environment_id UUID,
5+
deployment_revision_id BIGINT,
6+
account_id UUID,
7+
grantee_account_id UUID,
8+
domains TEXT[],
9+
changed_at TIMESTAMPTZ NOT NULL DEFAULT now()
10+
);
11+
12+
CREATE INDEX registry_change_events_changed_at_idx ON registry_change_events (changed_at);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CREATE TABLE registry_change_events (
2+
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
3+
event_type INTEGER NOT NULL DEFAULT 0,
4+
environment_id TEXT,
5+
deployment_revision_id INTEGER,
6+
account_id TEXT,
7+
grantee_account_id TEXT,
8+
domains TEXT,
9+
changed_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
10+
);
11+
12+
CREATE INDEX registry_change_events_changed_at_idx ON registry_change_events (changed_at);

0 commit comments

Comments
 (0)