uffdpager

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 27 Imported by: 0

README

UFFD Snapshot Pager

The UFFD pager lets Firecracker restore snapshot memory lazily. A restored VM gets a per-session UFFD socket, and page faults are served from the snapshot memory file by a local pager process.

The pager is local to the host. It is not backed by Redis or another external cache because page faults are latency-sensitive and the kernel-facing UFFD socket is local. The process keeps one shared in-memory page cache, bounded by hypervisor.firecracker_uffd_cache_max_bytes. Cache entries are keyed by a snapshot cache key plus page offset, so restore sessions with the same propagated snapshot cache key can reuse hot pages without starting one pager per snapshot.

UFFD is opt-in through hypervisor.firecracker_snapshot_memory_backend=uffd. The default backend remains file. Enabling UFFD does not change already running VMs; it only changes future Firecracker snapshot restores. If a restore uses UFFD, the VM is pinned to the pager session created for that restore until the instance stops, is deleted, or otherwise closes the session.

The pager version is declared in lib/uffdpager/VERSION. Installed Linux hosts run versioned systemd units named hypeman-uffd@<version>.service. Hypeman connects to the pager version from VERSION, so regular Hypeman releases do not start a new pager unless the UFFD pager version changes. Older pager versions are drained: they reject new sessions but continue serving existing sessions until those sessions close. Systemd runs the pager through the dedicated hypeman-uffd-pager binary rather than an alternate API-server mode. UFFD requires that systemd unit template to be installed; Hypeman does not launch an unmanaged pager subprocess if the template is missing.

Control HTTP API

Hypeman talks to the pager over Unix HTTP at <data_dir>/uffd/<version>/control.sock:

  • GET /health
  • GET /stats
  • POST /sessions
  • POST /sessions/{id}/close
  • POST /sessions/{id}/complete
  • POST /drain

POST /sessions/{id}/complete populates every outstanding page of a session from the backing file and then unregisters userfaultfd, so the restored VM stops depending on this pager and keeps running on resident memory. The VM is never paused and its network is untouched. Completion reads the whole remaining memory image, so it is paced by the caller; the request is bounded by its context rather than a fixed timeout. Unregister happens only after a full populate, because once a range is unregistered the kernel zero-fills any still-absent page.

Firecracker does not use this control socket directly. Each restore session gets its own Unix socket that receives Firecracker's UFFD file descriptor and memory regions.

The instance health check currently verifies that the pager version is healthy; it does not prove that a particular UFFD session still exists after an unplanned pager restart. If a pager disappears while serving active UFFD VMs, those VMs should be treated as unhealthy and recycled.

Documentation

Index

Constants

View Source
const (
	BackendFile = "file"
	BackendUFFD = "uffd"
)

Variables

View Source
var ErrSessionNotFound = errors.New("uffd pager session not found")

ErrSessionNotFound reports that a pager no longer has the requested session, so a completion request had nothing to act on.

Functions

func Main

func Main([]string) error

func Version

func Version() string

Types

type CreateSessionRequest

type CreateSessionRequest struct {
	SessionID         string `json:"session_id,omitempty"`
	InstanceID        string `json:"instance_id"`
	BackingMemoryPath string `json:"backing_memory_path"`
	CacheKey          string `json:"cache_key"`
}

CreateSessionRequest describes one Firecracker UFFD restore session.

type CreateSessionResponse

type CreateSessionResponse struct {
	SessionID      string `json:"session_id"`
	UFFDSocketPath string `json:"uffd_socket_path"`
	PagerVersion   string `json:"pager_version"`
}

CreateSessionResponse returns the per-session socket Firecracker should use as mem_backend.backend_path.

type HealthResponse

type HealthResponse struct {
	Version        string `json:"version"`
	Draining       bool   `json:"draining"`
	ActiveSessions int    `json:"active_sessions"`
}

type PageCache

type PageCache struct {
	// contains filtered or unexported fields
}

func NewPageCache

func NewPageCache(maxBytes int64) *PageCache

func (*PageCache) Add

func (c *PageCache) Add(cacheKey string, offset int64, data []byte)

func (*PageCache) Borrow

func (c *PageCache) Borrow(cacheKey string, offset int64, size int) ([]byte, bool)

Borrow returns the immutable cached page without copying. It marks the page referenced so the next CLOCK eviction sweep spares it.

func (*PageCache) Get

func (c *PageCache) Get(cacheKey string, offset int64, size int) ([]byte, bool)

func (*PageCache) SnapshotStats

func (c *PageCache) SnapshotStats() (bytes, maxBytes int64, items int, hits, misses int64)

func (*PageCache) SnapshotTimingStats

func (c *PageCache) SnapshotTimingStats() (shards int, lookupNanos, lookupMaxNanos, addNanos, addMaxNanos int64)

type Stats

type Stats struct {
	Version        string `json:"version"`
	Draining       bool   `json:"draining"`
	ActiveSessions int    `json:"active_sessions"`

	CacheBytes          int64 `json:"cache_bytes"`
	CacheMax            int64 `json:"cache_max"`
	CacheItems          int   `json:"cache_items"`
	CacheHits           int64 `json:"cache_hits"`
	CacheMisses         int64 `json:"cache_misses"`
	CacheShards         int   `json:"cache_shards"`
	CacheLookupNanos    int64 `json:"cache_lookup_nanos"`
	CacheLookupMaxNanos int64 `json:"cache_lookup_max_nanos"`
	CacheAddNanos       int64 `json:"cache_add_nanos"`
	CacheAddMaxNanos    int64 `json:"cache_add_max_nanos"`

	Faults           int64 `json:"faults"`
	BackingBytesRead int64 `json:"backing_bytes_read"`
	Copies           int64 `json:"copies"`
	CopyErrors       int64 `json:"copy_errors"`

	ActiveFaults        int64 `json:"active_faults"`
	MaxConcurrentFaults int64 `json:"max_concurrent_faults"`
	FaultNanos          int64 `json:"fault_nanos"`
	FaultMaxNanos       int64 `json:"fault_max_nanos"`
	ReadPageNanos       int64 `json:"read_page_nanos"`
	ReadPageMaxNanos    int64 `json:"read_page_max_nanos"`
	BackingReadNanos    int64 `json:"backing_read_nanos"`
	BackingReadMaxNanos int64 `json:"backing_read_max_nanos"`
	CopyNanos           int64 `json:"copy_nanos"`
	CopyMaxNanos        int64 `json:"copy_max_nanos"`
}

type Supervisor

type Supervisor struct{}

func NewSupervisor

func NewSupervisor(context.Context, string, int64) (*Supervisor, error)

func (*Supervisor) CloseSession

func (s *Supervisor) CloseSession(context.Context, string) error

func (*Supervisor) CloseSessionVersion

func (s *Supervisor) CloseSessionVersion(context.Context, string, string) error

func (*Supervisor) CompleteSessionVersion added in v0.2.0

func (s *Supervisor) CompleteSessionVersion(context.Context, string, string) error

func (*Supervisor) CreateSession

func (*Supervisor) HealthVersion

func (s *Supervisor) HealthVersion(context.Context, string) (*HealthResponse, error)

func (*Supervisor) Stats

func (s *Supervisor) Stats(context.Context) (*Stats, error)

func (*Supervisor) VersionKey

func (s *Supervisor) VersionKey() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL