IceLog is a Go-based gRPC metadata control plane for table catalogs, schema evolution, partition tracking, snapshots, and transactions.
- Table lifecycle operations: create, rename, drop, list, and get metadata.
- Snapshot lifecycle: commit, fetch, and list snapshots.
- Partition metadata management.
- Transaction primitives: begin, commit, and abort.
- gRPC reflection enabled for fast API discovery with
grpcurl.
cmd/server/ # Server entrypoint
internal/server/ # gRPC service implementation
internal/catalog/ # Catalog, schema, and partition management
internal/transaction/ # MVCC and transaction management
internal/db/ # Postgres client and models
internal/cache/ # In-memory LRU cache
proto/ # Protobuf definitions
gen/metadata/ # Generated gRPC/protobuf code
scripts/grpc/ # Example grpcurl payloads and helper scripts
- Go 1.23+
- Docker Desktop (for local PostgreSQL)
protoc(only needed if you regenerate protobuf code)grpcurl
docker compose up --buildThis starts PostgreSQL, waits for it to become healthy, builds the Go gRPC server image, and starts the metadata server on:
127.0.0.1:50051
On Windows with Docker Desktop and WSL enabled, run the same command from this repository directory in PowerShell or a WSL shell.
docker compose up -d postgresPowerShell:
$env:PG_CONN_STRING="host=127.0.0.1 port=5432 dbname=metadata user=metadata_user password=metadata_pass"
go run ./cmd/serverAlternative using Makefile:
make runServer default gRPC endpoint:
127.0.0.1:50051
Unit tests do not require Docker or PostgreSQL:
make test-unitIntegration tests use the PostgreSQL service from Docker Compose:
make test-integrationRun both:
make testIf Make is not installed on Windows, the equivalent commands are:
go test -v ./cmd/... ./gen/... ./internal/...
docker compose up -d --wait postgres
go test -v ./testsRuntime configuration is loaded from environment variables in internal/config/config.go.
At minimum, set:
PG_CONN_STRING
Common runtime values shown on startup include:
- gRPC address
- Postgres pool size
- cache capacity
- transaction timeout
IceLog does not store table data itself. Instead, it stores metadata about tables, partitions, immutable snapshots, and transaction state, then serves that metadata over gRPC to clients such as query engines, ETL jobs, or admin tooling.
A typical read flow looks like this:
- A client requests metadata for a table or asks for visible partitions.
- IceLog resolves the requested snapshot, or falls back to the table's current snapshot.
- The service checks the in-memory LRU cache for partition metadata.
- On a cache miss, it loads the visible partitions from PostgreSQL.
- IceLog returns the exact file paths and metadata needed to read the table at that point in time.
A typical write flow looks like this:
- A client commits a new snapshot with added and/or deleted partitions.
- IceLog validates that the parent snapshot still matches the table's current snapshot.
- The service writes a new immutable snapshot record and associated partition changes.
- The table's current snapshot pointer is updated transactionally.
- Cached partition metadata for that table is invalidated.
This architecture gives the system:
- immutable snapshot history
- point-in-time table views
- optimistic concurrency on snapshot commits
- fast repeated partition lookups via caching
IceLog is organized into a few core layers:
internal/server/
The gRPC server exposes the MetadataService defined in proto/metadata_service.proto. It handles request validation, response shaping, and delegation to the catalog, partition, snapshot, and transaction layers.
internal/catalog/
This layer owns:
- table creation, rename, deletion, and listing
- schema retrieval and schema evolution
- partition metadata lookup
- snapshot commit orchestration
internal/transaction/
This layer tracks:
- active transactions
- pinned read snapshots
- transaction expiration and cleanup
- parent snapshot validation during snapshot commits
internal/db/
PostgreSQL is the durable metadata store for:
- tables
- schema history
- snapshots
- partitions
- transaction rows
internal/cache/, internal/lock/
IceLog uses:
- an in-memory LRU cache for hot partition metadata
- per-table read/write locks for concurrency control
- cache invalidation on metadata mutation
For GetTableMetadata or GetPartitions:
- Acquire a shared lock for the table.
- Resolve the requested snapshot or current snapshot.
- Check the in-memory cache for table:snapshot.
- On cache miss, query PostgreSQL for visible partitions.
- Return partition metadata and aggregate stats.
For CommitSnapshot:
- Acquire an exclusive lock for the table.
- Read the current table snapshot.
- Validate the provided parent snapshot.
- Insert a new snapshot row.
- Insert new partitions and/or mark deleted ones.
- Update the table's current snapshot pointer.
- Invalidate cache entries for that table.
IceLog uses per-table locking plus optimistic snapshot validation.
- Reads take a shared lock and can proceed concurrently.
- Metadata mutations take an exclusive lock per table.
- Snapshot commits succeed only if the provided parent snapshot is still current.
This avoids conflicting table mutations while still allowing concurrent reads.
The PostgreSQL schema stores:
tables: table definitions, schema, properties, current snapshot pointerschema_history: historical schema versionssnapshots: immutable snapshot recordspartitions: partition/file metadata with visibility over snapshotstransactions: transaction lifecycle and read snapshot tracking
AlterTabledoes not yet support partition spec evolution.- Transaction lifecycle is table-aware at begin time, but transaction semantics are still lightweight compared to a full distributed transaction protocol.
ListTablesuses per-table count queries for visible partitions, which is correct but not yet batch-optimized.- Integration tests currently cover the main happy paths, but not every error case.
List available services:
grpcurl -plaintext 127.0.0.1:50051 listDescribe the metadata service:
grpcurl -plaintext 127.0.0.1:50051 describe metadata.MetadataServiceList methods:
grpcurl -plaintext 127.0.0.1:50051 list metadata.MetadataServiceThe repository already includes JSON request payloads in scripts/grpc/.
Use the helper script:
scripts\grpc\windows\call.cmd CreateTable scripts\grpc\create_table.jsonCreate table:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/CreateTable < scripts/grpc/create_table.jsonGet table metadata:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/GetTableMetadata < scripts/grpc/get_table.jsonAlter table schema:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/AlterTable < scripts/grpc/alter_schema.jsonRename table:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/AlterTable < scripts/grpc/rename_table.jsonDrop table:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/DropTable < scripts/grpc/drop_table.jsonList tables:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/ListTables < scripts/grpc/list_tables.jsonCommit snapshot:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/CommitSnapshot < scripts/grpc/commit_snapshot.jsonGet partitions:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/GetPartitions < scripts/grpc/get_partitions.jsonGet partition stats:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/GetPartitionStats < scripts/grpc/get_partition_stats.jsonGet snapshot:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/GetSnapshot < scripts/grpc/get_snapshot.jsonList snapshots:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/ListSnapshots < scripts/grpc/list_snapshots.jsonBegin transaction:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/BeginTransaction < scripts/grpc/begin_txn.jsonCommit transaction:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/CommitTransaction < scripts/grpc/commit_txn.jsonAbort transaction:
grpcurl -plaintext -d @ 127.0.0.1:50051 metadata.MetadataService/AbortTransaction < scripts/grpc/abort_txn.jsonMore payloads are available in scripts/grpc/ for:
- ad-hoc variations of these requests
GitHub Actions runs unit tests, integration tests with PostgreSQL, and a Docker image build on pushes and pull requests.
Generate protobuf code:
make protoRun the server:
make runTidy dependencies:
make tidy