Documentation
¶
Overview ¶
Package tools provides high-level meta-only rewrites of Tarantool journal files. The headline operation is RewriteMeta, which parses the meta header, hands a clone to a caller-supplied transform, writes the new header, and then byte-copies the rest of the source (tx blocks + EOF marker) verbatim to the destination. CRCs are preserved because the on-disk bytes are unchanged.
Index ¶
- Variables
- func RecoverInstanceUUIDInPlace(path string) (bool, error)
- func RemapVClock(remap map[uint32]uint32) func(*format.Meta) *format.Meta
- func ReplaceInstanceUUID(newID uuid.UUID) func(*format.Meta) *format.Meta
- func ReplaceInstanceUUIDInPlace(path string, newID uuid.UUID) error
- func RewriteMeta(srcPath, dstPath string, fn func(*format.Meta) *format.Meta) error
- func RewriteMetaFields(srcPath, dstPath string, opts ...Option) error
- type Option
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrUUIDLineNotFound is returned when the header has no Instance/Server // line to overwrite. ErrUUIDLineNotFound = errors.New("tools.ReplaceInstanceUUIDInPlace: no Instance/Server line in header") // ErrUUIDWidthMismatch is returned when the on-disk UUID value is not the // same byte width as the canonical replacement, so an in-place overwrite // would shift following bytes. Use RewriteMetaFields (on-copy) instead. ErrUUIDWidthMismatch = errors.New("tools.ReplaceInstanceUUIDInPlace: on-disk UUID width differs from replacement; use RewriteMetaFields") // ErrCorruptUUIDBackup is returned by RecoverInstanceUUIDInPlace when the // undo-log sidecar exists but is too short or self-inconsistent to apply. ErrCorruptUUIDBackup = errors.New("tools.RecoverInstanceUUIDInPlace: corrupt undo-log sidecar") )
Sentinel errors returned by ReplaceInstanceUUIDInPlace.
Functions ¶
func RecoverInstanceUUIDInPlace ¶
RecoverInstanceUUIDInPlace completes the crash-recovery half of ReplaceInstanceUUIDInPlace. If path has an undo-log sidecar (path+".uuidbak"), a prior in-place overwrite was interrupted: this restores the original bytes recorded in the sidecar to their offset in path (so a torn UUID becomes the consistent pre-write value again) and then removes the sidecar. It reports whether a recovery was performed.
With no sidecar present it is a no-op returning (false, nil), so it is safe to call unconditionally before opening a file. A sidecar that is too short or describes an out-of-range span is rejected with ErrCorruptUUIDBackup and left in place for inspection.
func RemapVClock ¶
RemapVClock returns a transform fn that renumbers replica ids in both VClock and PrevVClock according to remap: a key oldID present in remap becomes remap[oldID]; a key not present in remap is left at its current id. Per-row ReplicaID values inside the tx blocks are NOT rewritten by this transform — RewriteMeta copies tx bytes verbatim. Callers who need a consistent rewrite (so Tarantool's filename-signature check stays valid) must also remap row ReplicaIDs by other means; otherwise the resulting file's vclock signature will not match the sum of the rows' LSNs that load into Tarantool.
func ReplaceInstanceUUID ¶
ReplaceInstanceUUID returns a transform fn that sets m.InstanceUUID to newID and leaves every other field untouched. Suitable as the fn argument to RewriteMeta.
func ReplaceInstanceUUIDInPlace ¶
ReplaceInstanceUUIDInPlace overwrites the instance UUID in path's meta header directly, without copying the file. This is sound only because a canonical UUID is always exactly 36 bytes, so the replacement occupies the same span and no following byte moves — tx blocks, their CRCs, and the EOF marker are left untouched. Use it to re-stamp the instance UUID of a large file without rewriting every byte; for any other header change, or when the on-disk UUID is not 36 bytes, use RewriteMetaFields.
Crash safety. The 36-byte WriteAt is not itself atomic: a power loss or kernel crash while the span straddles a sector boundary can leave a torn, unparseable UUID. To make this recoverable without giving up the no-copy property, the original bytes and their offset are first written to an undo-log sidecar (path+".uuidbak"), fsync'd, then the in-place write is performed and fsync'd, and only then is the sidecar removed. If the process dies in that window the sidecar survives; the next call to ReplaceInstanceUUIDInPlace heals it automatically (it runs recovery on entry), or the caller can run RecoverInstanceUUIDInPlace explicitly. The net guarantee is old-or-new, never a torn header.
The file is opened read-write. It returns ErrUUIDLineNotFound if the header has no Instance/Server line, ErrUUIDWidthMismatch if the on-disk value is not 36 bytes, and leaves the file unmodified in both cases. When the on-disk UUID already equals newID the call is a no-op. Legacy 0.12 headers are accepted (the version is not touched).
Example ¶
ExampleReplaceInstanceUUIDInPlace re-stamps the instance UUID directly in a file, without copying it — sound because a canonical UUID is always 36 bytes, so nothing downstream shifts.
package main
import (
"bytes"
"fmt"
"os"
"path/filepath"
"github.com/google/uuid"
"github.com/tarantool/go-iproto"
"github.com/vmihailenco/msgpack/v5"
"github.com/tarantool/go-xlog/format"
"github.com/tarantool/go-xlog/reader"
"github.com/tarantool/go-xlog/tools"
"github.com/tarantool/go-xlog/writer"
)
// writeSampleXlog writes a minimal one-row xlog to path, for the on-disk
// tools examples.
func writeSampleXlog(path string) {
meta := &format.Meta{
Filetype: format.FiletypeXLOG,
Version: "example",
InstanceUUID: uuid.MustParse("11111111-2222-3333-4444-555555555555"),
}
var buf bytes.Buffer
w, err := writer.NewWriter(&buf, meta)
if err != nil {
panic(err)
}
body, err := msgpack.Marshal([]int{1})
if err != nil {
panic(err)
}
if err := w.WriteTx([]format.XRow{{Type: iproto.IPROTO_INSERT, LSN: 1, BodyRaw: body}}); err != nil {
panic(err)
}
if err := w.Close(); err != nil {
panic(err)
}
if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
panic(err)
}
}
func main() {
dir, err := os.MkdirTemp("", "go-xlog-example")
if err != nil {
panic(err)
}
defer func() { _ = os.RemoveAll(dir) }()
path := filepath.Join(dir, "x.xlog")
writeSampleXlog(path)
newID := uuid.MustParse("99999999-aaaa-bbbb-cccc-dddddddddddd")
if err := tools.ReplaceInstanceUUIDInPlace(path, newID); err != nil {
panic(err)
}
m, err := reader.ReadHeader(path)
if err != nil {
panic(err)
}
fmt.Printf("uuid=%s\n", m.InstanceUUID)
}
Output: uuid=99999999-aaaa-bbbb-cccc-dddddddddddd
func RewriteMeta ¶
RewriteMeta reads srcPath, parses its meta header, hands a deep clone of that meta to fn (so the caller's transform cannot mutate state shared with the on-disk parse), encodes the returned meta into a fresh `<dstPath>.inprogress` file, byte-copies every byte of srcPath from the position right after the source's meta blank-line terminator through to the end of the file (including the 4-byte EOF marker), syncs, and atomically renames `.inprogress` → dstPath.
Tx blocks are NOT re-encoded — their CRCs and zstd-compressed payloads are preserved exactly. The caller-supplied transform is therefore expected to limit its changes to header fields; any rewrite that would invalidate the rows (e.g. a VClock signature that disagrees with the per-row LSN sums) will be rejected by Tarantool at load time.
Failure modes:
- srcPath cannot be opened or its meta is malformed: error returned; no `.inprogress` file is created.
- The destination `.inprogress` already exists (O_EXCL): error returned; nothing is overwritten.
- Any failure after the destination is created: the partial `.inprogress` file is removed before returning.
On success, dstPath exists and any pre-existing file at dstPath is replaced atomically via os.Rename.
func RewriteMetaFields ¶
RewriteMetaFields copies srcPath to dstPath, overwriting the header fields named by opts and preserving every tx block and the EOF marker verbatim. It is a thin convenience over RewriteMeta: listed fields change, everything else (PrevVClock, Extras, and any field without an Option) is kept. With no opts it is a meta-preserving copy.
See RewriteMeta for the failure modes and the verbatim-tx-bytes contract.
Example ¶
ExampleRewriteMetaFields copies an xlog while overwriting selected header fields (here the instance UUID and version). The tx blocks are copied verbatim — only the header changes.
package main
import (
"bytes"
"fmt"
"os"
"path/filepath"
"github.com/google/uuid"
"github.com/tarantool/go-iproto"
"github.com/vmihailenco/msgpack/v5"
"github.com/tarantool/go-xlog/format"
"github.com/tarantool/go-xlog/reader"
"github.com/tarantool/go-xlog/tools"
"github.com/tarantool/go-xlog/writer"
)
// writeSampleXlog writes a minimal one-row xlog to path, for the on-disk
// tools examples.
func writeSampleXlog(path string) {
meta := &format.Meta{
Filetype: format.FiletypeXLOG,
Version: "example",
InstanceUUID: uuid.MustParse("11111111-2222-3333-4444-555555555555"),
}
var buf bytes.Buffer
w, err := writer.NewWriter(&buf, meta)
if err != nil {
panic(err)
}
body, err := msgpack.Marshal([]int{1})
if err != nil {
panic(err)
}
if err := w.WriteTx([]format.XRow{{Type: iproto.IPROTO_INSERT, LSN: 1, BodyRaw: body}}); err != nil {
panic(err)
}
if err := w.Close(); err != nil {
panic(err)
}
if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
panic(err)
}
}
func main() {
dir, err := os.MkdirTemp("", "go-xlog-example")
if err != nil {
panic(err)
}
defer func() { _ = os.RemoveAll(dir) }()
src := filepath.Join(dir, "src.xlog")
dst := filepath.Join(dir, "dst.xlog")
writeSampleXlog(src)
newID := uuid.MustParse("99999999-aaaa-bbbb-cccc-dddddddddddd")
if err := tools.RewriteMetaFields(src, dst,
tools.WithInstanceUUID(newID),
tools.WithVersion("3.8.0"),
); err != nil {
panic(err)
}
m, err := reader.ReadHeader(dst)
if err != nil {
panic(err)
}
fmt.Printf("uuid=%s version=%s\n", m.InstanceUUID, m.Version)
}
Output: uuid=99999999-aaaa-bbbb-cccc-dddddddddddd version=3.8.0
Types ¶
type Option ¶
Option overrides a single meta header field in an on-copy rewrite performed by RewriteMetaFields. Options follow the functional-options pattern (cf. Writer.Option): each mutates the cloned meta in place, and any field without a corresponding Option is left exactly as the source had it.
func WithFormatVer ¶
WithFormatVer sets the format-version line. It must be "0.13" or the legacy "0.12" — EncodeMeta rejects anything else. Changing the version does NOT re-encode tx rows (they are copied verbatim), so only retarget the version when the existing row encoding is already compatible.
func WithInstanceUUID ¶
WithInstanceUUID sets the instance UUID.
func WithVClock ¶
WithVClock sets the VClock line. A copy of vc is stored, so later mutation of vc by the caller does not affect the rewrite. As with RemapVClock this touches only the header: a vclock that disagrees with the per-row LSN sums will fail Tarantool's signature check at load time.
func WithVersion ¶
WithVersion sets the free-form Version string (Tarantool's PACKAGE_VERSION).