WeaveDocs
Forum

Write Tuple

Append a tuple to the Forum and learn the durability and indexing guarantees.

What this page covers

The write operation: how a tuple becomes durable, the sequence number it receives, and what the in-memory index promises.

Construction

Note

A Forum wraps a single Strand. Multiple Forum instances can share the same Strand if you want a single audit log, but each instance manages its own live_tuples index — and indices may briefly diverge until they re-converge on the next Strand sync.

A Forum wraps a single Strand. Multiple Forums can share the same Strand if you want a single audit log, but each Forum instance manages its own live_tuples index.

use std::sync::Arc;
use tokio::sync::RwLock;
use strand::{Strand, StrandConfig};
use forum::Forum;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dir = tempfile::tempdir()?;
    let cfg = StrandConfig::default().with_storage(dir.path());
    let strand = Arc::new(RwLock::new(Strand::new(cfg).await?));

    let forum = Forum::new(strand);

    // Write a 7-byte tuple. The Forum returns its entry id (also the strand
    // sequence number assigned to this write).
    let id = forum.write(b"hello".to_vec()).await?;
    println!("wrote tuple at entry id {id}");

    Ok(())
}

Step-by-step

  1. The Strand write lock is taken.
  2. The current strand.len() is read; that value becomes the EntryId for this tuple.
  3. A TupleEntry::Write { id, tuple } is serialized with serde_json::to_vec.
  4. The bytes are appended to the Strand. On return, the new block is durable on disk.
  5. The live_tuples write lock is taken; the tuple is inserted under its id.
  6. Both locks are released.

The two locks are taken in sequence (Strand first, then index). They are never held simultaneously across await boundaries.

Durability guarantees

PropertyGuarantee
Survival after write returnsThe Strand block is written through to the configured storage backend
Order among writesStrictly increasing EntryId
Visibility to concurrent takesImmediate, once the index write lock is released
Visibility to peersEventual, after Strand replication

Errors

VariantCauseRecovery
ForumError::Strand(String)Strand append failed (disk, quorum)Retry the write; the operation is idempotent only if you reuse the same payload — the new attempt will receive a new EntryId
ForumError::Serialization(serde_json::Error)TupleEntry failed to encodeShould not happen for byte vectors; check for changes to the enum definition

Practical patterns

Job queue producer

use forum::Forum;
use serde::Serialize;

#[derive(Serialize)]
struct Job {
    kind: String,
    payload: serde_json::Value,
}

async fn enqueue(forum: &Forum, job: &Job) -> Result<(), Box<dyn std::error::Error>> {
    let bytes = serde_json::to_vec(job)?;
    forum.write(bytes).await?;
    Ok(())
}

Idempotent producer (deduplicate before write)

use forum::{Forum, Pattern};

async fn enqueue_once(forum: &Forum, payload: &[u8]) -> Result<bool, Box<dyn std::error::Error>> {
    if forum.read(&Pattern(payload.to_vec())).await?.is_some() {
        return Ok(false); // already present, skip
    }
    forum.write(payload.to_vec()).await?;
    Ok(true)
}

read does not consume; this is the right primitive for "is this already queued?" checks. Be aware of the race: another writer may insert between the read and the write. If you need strict deduplication, key tuples with a content-addressed prefix and have the consumer drop duplicates.

Limits

  • The in-memory live_tuples map holds every live (not-yet-taken) tuple. Memory grows linearly with queue depth. Take consumed tuples aggressively if your workload allows it.
  • Tuples are arbitrary byte vectors; the Forum does not impose a maximum size, but Strand block-size limits apply (see Strand's storage page).