Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize snapshots storage #234

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/api/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
bin
mkfcenv.tar.gz
.env
.env
.shared
4 changes: 2 additions & 2 deletions packages/nomad/loki.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ job "loki" {

resources {
memory_max = 2048
memory = 1024
cpu = 512
memory = 256
cpu = 256
}

template {
Expand Down
11 changes: 10 additions & 1 deletion packages/nomad/otel-collector.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,15 @@ processors:
- "nomad_client_unallocated_memory"
- "orchestrator.*"
- "api.*"
metricstransform:
transforms:
- include: "nomad_client_host_cpu_idle"
match_type: strict
action: update
operations:
- action: aggregate_labels
aggregation_type: sum
label_set: [instance, node_id, node_status, node_pool]
attributes/session-proxy:
actions:
- key: service.name
Expand Down Expand Up @@ -249,7 +258,7 @@ service:
receivers:
- prometheus
- otlp
processors: [filter, batch]
processors: [filter, batch, metricstransform]
exporters:
- prometheusremotewrite/grafana_cloud_metrics
# metrics/session-proxy:
Expand Down
10 changes: 9 additions & 1 deletion packages/nomad/proxies/client.conf
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ server {
location / {
if ($node_ip = "") {
# If you set any text, the header will be set to `application/octet-stream` and then browser won't be able to render the content
return 404;
return 404; # Invalid sandbox url
}


Expand All @@ -85,6 +85,14 @@ server {
}
}

# Mock for sandbox server when the sandbox is not running, 127.0.0.1 is returned by the DNS resolver
server {
listen 3003;

default_type text/plain;
return 404 'Sandbox is not running or not found.';
}

server {
listen 3001;
location /health {
Expand Down
9 changes: 1 addition & 8 deletions packages/orchestrator/cmd/inspect-header/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,7 @@ func main() {
fmt.Printf("=======\n")

for _, mapping := range h.Mapping {
rangeMessage := fmt.Sprintf("%d-%d", mapping.Offset/h.Metadata.BlockSize, (mapping.Offset+mapping.Length-1)/h.Metadata.BlockSize)

fmt.Printf(
"%-14s [%11d,%11d) = [%11d,%11d) in %s, %d B\n",
rangeMessage,
mapping.Offset, mapping.Offset+mapping.Length,
mapping.BuildStorageOffset, mapping.BuildStorageOffset+mapping.Length, mapping.BuildId.String(), mapping.Length,
)
fmt.Println(mapping.Format(h.Metadata.BlockSize))
}

fmt.Printf("\nMAPPING SUMMARY\n")
Expand Down
158 changes: 158 additions & 0 deletions packages/orchestrator/cmd/simulate-headers-merge/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package main

import (
"context"
"flag"
"fmt"
"log"
"os"

"github.com/e2b-dev/infra/packages/shared/pkg/storage"
"github.com/e2b-dev/infra/packages/shared/pkg/storage/gcs"
"github.com/e2b-dev/infra/packages/shared/pkg/storage/header"
"github.com/google/uuid"
)

func main() {
baseBuildId := flag.String("base", "", "base build id")
diffBuildId := flag.String("diff", "", "diff build id")
kind := flag.String("kind", "", "'memfile' or 'rootfs'")
visualize := flag.Bool("visualize", false, "visualize the headers")

flag.Parse()

baseTemplate := storage.NewTemplateFiles(
"",
*baseBuildId,
"",
"",
false,
)

diffTemplate := storage.NewTemplateFiles(
"",
*diffBuildId,
"",
"",
false,
)

var baseStoragePath string
var diffStoragePath string

if *kind == "memfile" {
baseStoragePath = baseTemplate.StorageMemfileHeaderPath()
diffStoragePath = diffTemplate.StorageMemfileHeaderPath()
} else if *kind == "rootfs" {
baseStoragePath = baseTemplate.StorageRootfsHeaderPath()
diffStoragePath = diffTemplate.StorageRootfsHeaderPath()
} else {
log.Fatalf("invalid kind: %s", *kind)
}

ctx := context.Background()

baseObj := gcs.NewObject(ctx, gcs.TemplateBucket, baseStoragePath)
diffObj := gcs.NewObject(ctx, gcs.TemplateBucket, diffStoragePath)

baseHeader, err := header.Deserialize(baseObj)
if err != nil {
log.Fatalf("failed to deserialize base header: %s", err)
}

diffHeader, err := header.Deserialize(diffObj)
if err != nil {
log.Fatalf("failed to deserialize diff header: %s", err)
}

fmt.Printf("\nBASE METADATA\n")
fmt.Printf("Storage path %s/%s\n", gcs.TemplateBucket.BucketName(), baseStoragePath)
fmt.Printf("========\n")

for _, mapping := range baseHeader.Mapping {
fmt.Println(mapping.Format(baseHeader.Metadata.BlockSize))
}

if *visualize {
bottomLayers := header.Layers(baseHeader.Mapping)
delete(*bottomLayers, baseHeader.Metadata.BaseBuildId)

fmt.Println("")
fmt.Println(
header.Visualize(
baseHeader.Mapping,
baseHeader.Metadata.Size,
baseHeader.Metadata.BlockSize,
128,
bottomLayers,
&map[uuid.UUID]struct{}{
baseHeader.Metadata.BuildId: {},
},
),
)
}

if err := header.ValidateMappings(baseHeader.Mapping, baseHeader.Metadata.Size, baseHeader.Metadata.BlockSize); err != nil {
log.Fatalf("failed to validate base header: %s", err)
}

fmt.Printf("\nDIFF METADATA\n")
fmt.Printf("Storage path %s/%s\n", gcs.TemplateBucket.BucketName(), diffStoragePath)
fmt.Printf("========\n")

onlyDiffMappings := make([]*header.BuildMap, 0)

for _, mapping := range diffHeader.Mapping {
if mapping.BuildId == diffHeader.Metadata.BuildId {
onlyDiffMappings = append(onlyDiffMappings, mapping)
}
}

for _, mapping := range onlyDiffMappings {
fmt.Println(mapping.Format(baseHeader.Metadata.BlockSize))
}

if *visualize {
fmt.Println("")
fmt.Println(
header.Visualize(
onlyDiffMappings,
baseHeader.Metadata.Size,
baseHeader.Metadata.BlockSize,
128,
nil,
header.Layers(onlyDiffMappings),
),
)
}

mergedHeader := header.MergeMappings(baseHeader.Mapping, onlyDiffMappings)

fmt.Printf("\n\nMERGED METADATA\n")
fmt.Printf("========\n")

for _, mapping := range mergedHeader {
fmt.Println(mapping.Format(baseHeader.Metadata.BlockSize))
}

if *visualize {
bottomLayers := header.Layers(baseHeader.Mapping)
delete(*bottomLayers, baseHeader.Metadata.BaseBuildId)

fmt.Println("")
fmt.Println(
header.Visualize(
mergedHeader,
baseHeader.Metadata.Size,
baseHeader.Metadata.BlockSize,
128,
bottomLayers,
header.Layers(onlyDiffMappings),
),
)
}

if err := header.ValidateMappings(mergedHeader, baseHeader.Metadata.Size, baseHeader.Metadata.BlockSize); err != nil {
fmt.Fprintf(os.Stderr, "\n\n[VALIDATION ERROR]: failed to validate merged header: %s", err)
}
}
2 changes: 1 addition & 1 deletion packages/orchestrator/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ require (
github.com/hashicorp/consul/api v1.30.0
github.com/jellydator/ttlcache/v3 v3.3.0
github.com/loopholelabs/userfaultfd-go v0.1.2
github.com/miekg/dns v1.1.62
github.com/pojntfx/go-nbd v0.3.2
github.com/shirou/gopsutil/v4 v4.24.10
github.com/vishvananda/netlink v1.3.0
Expand Down Expand Up @@ -93,6 +92,7 @@ require (
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect
github.com/oklog/ulid v1.3.1 // indirect
Expand Down
1 change: 1 addition & 0 deletions packages/orchestrator/internal/sandbox/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func (b *File) Slice(off, length int64) ([]byte, error) {
return nil, fmt.Errorf("failed to get mapping: %w", err)
}

// Pass empty huge page when the build id is nil.
if *buildID == uuid.Nil {
return header.EmptyHugePage, nil
}
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestrator/internal/sandbox/build/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/e2b-dev/infra/packages/shared/pkg/storage/gcs"
)

const buildExpiration = time.Hour * 25
const buildExpiration = time.Hour * 48

const cachePath = "/orchestrator/build"

Expand Down
36 changes: 29 additions & 7 deletions packages/orchestrator/internal/sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ func NewSandbox(
config.KernelVersion,
config.FirecrackerVersion,
config.HugePages,
isSnapshot,
)
if err != nil {
return nil, cleanup, fmt.Errorf("failed to get template snapshot data: %w", err)
Expand Down Expand Up @@ -393,7 +392,13 @@ func (s *Sandbox) Snapshot(
return nil, fmt.Errorf("failed to create memfile diff file: %w", err)
}

err = header.CreateDiff(sourceFile, s.files.MemfilePageSize(), memfileDirtyPages, memfileDiffFile)
memfileDirtyPages, emptyDirtyPages, err := header.CreateDiff(
sourceFile,
s.files.MemfilePageSize(),
memfileDirtyPages,
originalMemfile,
memfileDiffFile,
)
if err != nil {
return nil, fmt.Errorf("failed to create memfile diff: %w", err)
}
Expand All @@ -402,15 +407,32 @@ func (s *Sandbox) Snapshot(

releaseLock()

memfileMapping := header.CreateMapping(
memfileMetadata,
var memfileMappings []*header.BuildMap

memfileEmptyMapping := header.CreateMapping(
&uuid.Nil,
emptyDirtyPages,
memfileMetadata.BlockSize,
)

if memfileEmptyMapping != nil {
memfileMappings = header.MergeMappings(
originalMemfile.Header().Mapping,
memfileEmptyMapping,
)

memfileMappings = header.NormalizeMappings(memfileMappings)
}

memfileDirtyMappings := header.CreateMapping(
&buildId,
memfileDirtyPages,
memfileMetadata.BlockSize,
)

memfileMappings := header.MergeMappings(
memfileMappings = header.MergeMappings(
originalMemfile.Header().Mapping,
memfileMapping,
memfileDirtyMappings,
)

snapfile, err := template.NewLocalFile(snapshotTemplateFiles.CacheSnapfilePath())
Expand Down Expand Up @@ -469,9 +491,9 @@ func (s *Sandbox) Snapshot(
}

rootfsMapping := header.CreateMapping(
rootfsMetadata,
&buildId,
rootfsDirtyBlocks,
rootfsMetadata.BlockSize,
)

rootfsMappings := header.MergeMappings(
Expand Down
5 changes: 1 addition & 4 deletions packages/orchestrator/internal/sandbox/template/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

// How long to keep the template in the cache since the last access.
// Should be longer than the maximum possible sandbox lifetime.
const templateExpiration = time.Hour * 25
const templateExpiration = time.Hour * 72

type Cache struct {
cache *ttlcache.Cache[string, Template]
Expand Down Expand Up @@ -62,15 +62,13 @@ func (c *Cache) GetTemplate(
kernelVersion,
firecrackerVersion string,
hugePages bool,
isSnapshot bool,
) (Template, error) {
storageTemplate, err := newTemplateFromStorage(
templateId,
buildId,
kernelVersion,
firecrackerVersion,
hugePages,
isSnapshot,
nil,
nil,
c.bucket,
Expand Down Expand Up @@ -125,7 +123,6 @@ func (c *Cache) AddSnapshot(
kernelVersion,
firecrackerVersion,
hugePages,
true,
memfileHeader,
rootfsHeader,
c.bucket,
Expand Down
Loading