Files
music_library/backlog/docs/nerves-deployment/doc-10 - Deepseek-v4-Pro-xhigh-analysis.md
T
2026-05-05 10:45:25 +01:00

224 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: doc-10
title: "Deepseek v4 Pro, xhigh analysis"
type: other
created_date: "2026-05-04 15:11"
---
# Nerves Deployment Feasibility Analysis for Music Library
> Research report — do not implement. May 2026.
## Overview
This report assesses the viability of deploying the Music Library application (a
Phoenix LiveView app backed by SQLite + Litestream) as a Nerves firmware image.
Three areas were identified as potential challenges:
1. **Libraries with native extensions (NIFs)** — do they offer precompiled ARM
binaries or can they be cross-compiled?
2. **SQLite extensions**`vec0` (sqlite-vec) and `unicode` — already support
Linux/ARM on paper, but are they tested on Nerves targets?
3. **Data replication** — the production instance uses Litestream for backup. How
would a Nerves device replicate data, and what happens with write conflicts?
Each area is analysed below, followed by a summary of risks and recommended next
steps.
---
## 1. Native-extensions audit
Every dependency that ships a NIF or binary artefact was reviewed for ARM /
Nerves compatibility. Devonly tools (esbuild, tailwind, credo, etc.) are
excluded — they never reach the firmware.
| Dependency | Purpose | NIF type | Precompiled ARM? | Nerves feasibility |
| ----------------------------------- | ------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **exqlite**`ecto_sqlite3` | SQLite driver | C NIF (`cc_precompiler`) | ✅ `aarch64-linux-gnu/musl` (no `armv7-*`) | **🟢 Good** — `force_build: true` compiles from source inside the Nerves toolchain. Explicitly mentioned in Exqlites README as an embedded use-case. |
| **vix** → libvips | Image processing (covers, thumbnails) | C NIF (`elixir_make`) | ❌ No ARM binaries advertised | **🔴 High risk** — libvips is ~30 MB with transitive deps (libjpeg, libpng, libwebp, etc.). Each needs Buildroot integration or crosscompilation. Biggest blocker for a lean firmware. |
| **mdex** → comrak + ammonia | Markdown → HTML | Rust NIF (RustlerPrecompiled) | ⚠️ Standard Rustler targets (`aarch64-unknown-linux-gnu`, possibly `arm-*`) | **🟡 Needs crosscompilation** — RustlerPrecompiled targets use different triples than Nerves (`aarch64-nerves-linux-gnu`). Precompiled `.so` likely wont load. Crosscompile from source with a Nervesaware Rust toolchain. |
| **lumis** → treesitter | Syntax highlighting (in mdex) | Rust NIF (RustlerPrecompiled) | Same as mdex | **🟡 Same situation** — part of the mdex stack. Treesitter grammars add perlanguage compilation but the NIF itself is manageable. |
| **typst** | PDF generation (tracklists) | Rust NIF (RustlerPrecompiled) | Same as mdex | **🔴 Heavy** — a full typesetting engine. Large binary, plus the same targettriple mismatch. Likely overkill for embedded PDFs. |
| **dominant_colors** → kmeans_colors | Colour extraction from covers | Rust NIF (`rustler_precompiled ~> 0.7`) | Same as mdex | **🟢 Small** — trivial NIF. Crosscompilation is straightforward. Low risk. |
| **esbuild** | JS bundling | Go binary | — | **N/A** — dev only, never deployed. |
| **tailwind** | CSS generation | Go binary | — | **N/A** — dev only, never deployed. |
### Targetarchitecture specifics
| Target | CPU | Nerves toolchain triple | Precompiledbinary match? |
| --------- | ------------------------- | ------------------------------ | --------------------------------------------------------------------------------- |
| RPi 4 / 5 | CortexA72 / A76 (64bit) | `aarch64-nerves-linux-gnu` | ⚠️ `aarch64-linux-gnu` _may_ be ABIcompatible (exqlite). Rust NIFs need rebuild. |
| RPi 3 | CortexA53 (32bit) | `armv7-nerves-linux-gnueabihf` | ❌ No precompiled binaries match. Everything must be compiled from source. |
**Recommendation**: Target RPi 4 or 5 (64bit) first — the precompiled ecosystem is
far better and the 32bit path adds unnecessary friction.
---
## 2. SQLite extensions
The application loads two runtime extensions via Exqlites `load_extensions`
config:
| Extension | Source | ARM status | Notes |
| ----------- | ---------------------------------------------------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **vec0** | [sqlite-vec](https://github.com/asg017/sqlite-vec) | ✅ **Good** | “Written in pure C, no dependencies, runs anywhere SQLite runs (… Raspberry Pis, etc.)” — explicitly tested on ARM. Compilable for any Nerves target. |
| **unicode** | likely [sqlean](https://github.com/nalgeon/sqlean) (`text` module or bundle) | ✅ **ARM64 available** | Sqlean ships `sqlean-linux-arm64.zip`. For ARMv7, compile from C source. |
### Loading mechanism
The project already uses an architectureaware directory layout:
```elixir
config :myapp, Myapp.Repo,
load_extensions: ["./priv/sqlite/#{arch_dir}/vector0"]
```
This pattern extends naturally to Nerves — compile the `.so` files with the Nerves
crosscompiler, place them in the appropriate arch subdirectory, and Exqlite will
load them at connection time.
**Unknown**: Whether the `unicode` extension is built from the exact same sqlean
release as production, or compiled independently. Sync the build flags and version
to avoid subtle behavioural differences.
---
## 3. Data replication & conflict resolution
This is the **hardest problem** and the most architecturally open of the three.
### What Litestream does (and doesnt)
| Capability | Litestream | Needed for device ↔ cloud sync |
| ----------------------------------- | ---------- | ------------------------------ |
| Unidirectional backup (app → S3) | ✅ | ❌ |
| Disaster recovery (restore from S3) | ✅ | ❌ |
| Bidirectional sync | ❌ | ✅ |
| Conflict detection / resolution | ❌ | ✅ |
| Multiwriter support | ❌ | ✅ |
Litestream is purely an asynchronous backup tool. It continuously streams WAL pages
to S3. It has **no mechanism** for a second instance to push writes back, no merge
strategy, and no conflict detection. Adding a Nerves device that writes to the same
database file would corrupt the Litestream generation (or create a parallel one that
cannot be merged).
### What LiteFS does (and doesnt)
[LiteFS](https://github.com/superfly/litefs) (same creators, different project)
provides multinode SQLite replication with a primary/replica model:
- ✅ Automatic failover via distributed lease (Consul)
- ✅ LTXbased transactionlog replication
- ✅ Builtin HTTP proxy for writeforwarding to the primary
- ❌ Requires FUSE (needs a Buildroot kernel module)
- ❌ Requires Consul or static leasing
-**Replicas are readonly** — only one writer at a time
- ❌ Designed for sameregion clusters, not device ↔ cloud
- ❌ No offlinefirst or intermittentconnectivity support
**LiteFS does not fit** the devicesync use case. If the Nerves device goes offline,
accumulates writes, then reconnects, LiteFS has no way to merge those writes onto
the primary.
### Realistic approaches
#### A. Readonly Nerves device (simplest, safest)
The Nerves device is a readonly replica. All writes happen on the production
server. The device periodically pulls a fresh database snapshot.
- **Sync**: `GET /api/v1/backup` (already exists), Litestream restore, or HTTP
rangerequest to fetch a compressed DB.
- **Pros**: Zero conflicts, simple to implement, leverages existing backup API.
- **Cons**: No local writes — cant add records, update notes, or scrobble from
the device.
#### B. Separate write domains (pragmatic)
Never write the same record type from both sides:
- **Production server**: records, embeddings, notes, record sets, artist info.
- **Nerves device**: scrobbles, listening stats, playback logs.
- Each instance has its own SQLite database. They sync via API calls (e.g., device
pushes scrobbles to production, production pushes new records to device).
- **Pros**: No databaselevel conflicts. Clear ownership boundaries.
- **Cons**: Applevel routing logic. Two separate databases on the device.
#### C. CRDTbacked data model (most robust)
Model the subset of data writable on both sides using Conflictfree Replicated Data
Types (CRDTs).
- **Libraries**: [Automerge](https://automerge.org/), [Yjs](https://yjs.dev/),
custom CRDT stores.
- **Pros**: True offlinefirst, automatic conflict resolution, no central
authority needed.
- **Cons**: Requires wrapping your data in CRDT types. No mature Elixir CRDT ↔
SQLite bridge. Significant architecture change.
#### D. Event sourcing with log shipping
Write all mutations as an appendonly event log. Ship the log between instances.
Replay to reconstruct state.
- **Pros**: Natural fit with SQLite (append to a `mutations` table). Full audit
trail.
- **Cons**: Requires eventschema design, log compaction, eventual consistency
handling.
#### E. Applicationlevel sync protocol
Define a custom sync protocol with:
- Vector clocks or timestamps for causality tracking.
- Lastwritewins (LWW) or manual conflict resolution.
- Periodic pull/push of changed records since the last sync point.
- **Pros**: Domainspecific, can optimise for exact data shapes.
- **Cons**: Most implementation effort. Easy to get wrong.
### Litestreams role on the device
Even with a sync strategy in place, Litestream could still run on the Nerves device
for **local disaster recovery** — streaming the devices own WAL to a separate S3
path. This is independent from the production Litestream instance.
---
## Summary of risks
| Risk | Severity | Mitigation |
| -------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------- |
| **vix / libvips on Nerves** | 🔴 High | Replace image processing with a lighter alternative, or offload to the production server via API. |
| **Datasync strategy** | 🔴 High | No offtheshelf solution. Start with readonly replica (option A), iterate toward separate write domains (option B). |
| **typst on embedded** | 🟡 Medium | Switch to serverside PDF generation; client downloads the result. |
| **Rust NIF crosscompilation** | 🟡 Medium | Set up Nerves Rust crosscompilation. Works but adds build complexity. Each library needs individual testing. |
| **ARMv7 (RPi 3) vs AArch64 (RPi 4/5)** | 🟡 Medium | RPi 4/5 (64bit) has much better precompiledbinary support. RPi 3 requires everything compiled from source. |
| **Buildroot package deps (libvips)** | 🟡 Medium | Each transitive C library needs a Buildroot package definition or a custom crosscompile step. |
| **Firmware size** | 🟡 Medium | libvips + typst + sqlite extensions → image could exceed 200 MB. Typical Nerves firmware is 2080 MB. |
| **sqlitevec on ARM** | 🟢 Low | Explicitly supported. Pure C, no deps. Compiles trivially with the Nerves toolchain. |
| **exqlite on Nerves** | 🟢 Low | Designed for embedded use. Source compilation wellsupported and documented. |
| **dominant_colors** | 🟢 Low | Tiny Rust NIF. Straightforward crosscompilation. |
---
## Recommended next steps (if proceeding)
1. **Target RPi 4 or 5 (64bit)** — significantly better precompiled binary
ecosystem and larger RAM for Phoenix + Oban.
2. **Start with a readonly firmware** — pull database snapshots from the
production server via the existing `/api/v1/backup` (or `/health`) endpoint.
Dont write locally until a sync strategy is decided.
3. **Audit which features to keep ondevice** — consider dropping image
processing (vix) and PDF generation (typst) from the embedded build, or
replacing them with lighter alternatives / serverside APIs.
4. **Benchmark firmware size incrementally** — start with bare Phoenix + SQLite,
add extensions, measure. Set a hard size budget (e.g., 120 MB).
5. **Design the sync protocol as a separate project** — this is the most complex
piece. Prototype with a readonly client first, then evaluate CRDTs, event
sourcing, or applicationlevel sync based on actual write patterns.