⚡ Dependency caching for millisecond startup times
| ← Back to README | Security Policy → |
Fast Start enables near-instant service startup by caching Python virtual environments based on dependency hash. When the same dependencies are used again, the cached venv is symlinked instead of recreated.
| Scenario | Without Cache | With Cache |
|---|---|---|
| First run (2 deps) | ~5-10s | ~5-10s |
| Second run (same deps) | ~5-10s | ~50-100ms |
| First run (6 deps) | ~15-30s | ~15-30s |
| Second run (same deps) | ~15-30s | ~100-200ms |
from pactown import ServiceRunner
runner = ServiceRunner(
sandbox_root="/tmp/sandboxes",
enable_fast_start=True, # Enabled by default
)
# First run - creates and caches venv
result = await runner.fast_run(
service_id="my-api",
content=markdown_content,
port=8001,
)
print(f"Started in {result.message}") # "Started in 150ms"
# Second run - uses cached venv
result = await runner.fast_run(
service_id="my-api-2",
content=markdown_content, # Same deps
port=8002,
)
print(f"Started in {result.message}") # "Started in 50ms (cached)"
/tmp/pactown-sandboxes/
├── .cache/
│ └── venvs/
│ ├── venv_a1b2c3d4/ # fastapi + uvicorn
│ │ ├── bin/python
│ │ ├── lib/
│ │ └── .deps # ["fastapi", "uvicorn"]
│ └── venv_e5f6g7h8/ # flask + gunicorn
│ └── ...
├── service_123/
│ ├── main.py
│ └── .venv -> ../.cache/venvs/venv_a1b2c3d4 # SYMLINK!
└── service_456/
├── app.py
└── .venv -> ../.cache/venvs/venv_a1b2c3d4 # Same deps = same cache
deps = ["fastapi", "uvicorn"]
sorted_deps = sorted(deps) # ["fastapi", "uvicorn"]
hash = sha256("\n".join(sorted_deps)) # "a1b2c3d4..."
# Same deps in different order = same hash
deps2 = ["uvicorn", "fastapi"]
hash2 = sha256("\n".join(sorted(deps2))) # "a1b2c3d4..." (same!)
async def fast_run(
service_id: str,
content: str,
port: int,
env: Optional[Dict[str, str]] = None,
user_id: Optional[str] = None,
user_profile: Optional[Dict[str, Any]] = None,
skip_health_check: bool = False,
on_log: Optional[Callable[[str], None]] = None,
) -> RunResult
Parameters:
| Parameter | Type | Description |
|---|---|---|
service_id |
str | Unique service identifier |
content |
str | Markdown content with markpact blocks |
port |
int | Port to run on |
env |
dict | Additional environment variables |
user_id |
str | User ID for security policy |
user_profile |
dict | User limits (see Security Policy) |
skip_health_check |
bool | Return immediately without waiting |
on_log |
callable | Log callback function |
Returns: RunResult with timing in message field.
from pactown import DependencyCache
cache = DependencyCache(
cache_root=Path("/tmp/cache"),
max_cache_size=20, # Max cached venvs
max_age_hours=24, # Expire after 24h
)
# Check for cached venv
cached = cache.get_cached_venv(["fastapi", "uvicorn"])
if cached:
print(f"Cache hit: {cached.venv_path}")
else:
# Create and cache
cached = cache.create_and_cache(["fastapi", "uvicorn"])
from pactown import FastServiceStarter
starter = FastServiceStarter(
sandbox_root=Path("/tmp/sandboxes"),
enable_caching=True,
enable_pool=True, # Pre-warmed sandboxes (optional)
)
result = await starter.fast_create_sandbox(
service_name="my-service",
content=markdown,
on_log=print,
)
print(f"Ready in {result.startup_time_ms}ms")
print(f"Cache hit: {result.cache_hit}")
runner = ServiceRunner()
stats = runner.get_cache_stats()
print(f"Cache entries: {stats['cache_entries']}")
print(f"Total size: {stats['total_size_mb']:.1f} MB")
print(f"Hit rate: {stats['hit_rate']:.1%}")
Old cache entries are automatically cleaned up based on:
Manual cleanup:
cache._cleanup_old()
POST /runner/run
Content-Type: application/json
{
"project_id": 123,
"readme_content": "...",
"port": 10001,
"fast_mode": true,
"skip_health_check": false
}
Response:
{
"success": true,
"port": 10001,
"message": "Running on port 10001 (150ms)",
"logs": [
"⚡ Fast start mode enabled",
"⚡ Cache hit! Reusing venv (a1b2c3d4)",
"⚡ Sandbox ready in 45ms (cached)",
"Starting: uvicorn main:app...",
"✓ Running in 150ms"
]
}
GET /runner/cache/stats
Response:
{
"caching_enabled": true,
"cache_entries": 5,
"total_size_mb": 234.5,
"oldest_entry_hours": 12.3
}
Same deps = same cache. Avoid unnecessary variations:
# Good - will share cache
```python markpact:deps
fastapi
uvicorn
```python markpact:deps fastapi uvicorn requests # Only add if needed!
# Return immediately, check health later
result = await runner.fast_run(
service_id="background-worker",
content=markdown,
port=8001,
skip_health_check=True, # Returns in ~50ms
)
# Check health separately if needed
await asyncio.sleep(2)
status = runner.get_status("background-worker")
from pactown import FastServiceStarter
starter = FastServiceStarter(sandbox_root=Path("/tmp"))
# Pre-warm during app startup
common_stacks = [
["fastapi", "uvicorn"],
["flask", "gunicorn"],
["django", "gunicorn"],
]
for deps in common_stacks:
starter.dep_cache.create_and_cache(deps)
First run always installs dependencies. This is expected.
Ensure sandbox_root and cache are on the same filesystem.