lungo

repository·master·Indexed 19 days ago

https://github.com/256dpi/lungo

A MongoDB-compatible, embeddable database and toolkit for Go. It supports in-memory workloads for testing or single-file persistent storage via FileStore. The toolkit includes mongokit for CRUD operations and indexing, bsonkit for BSON manipulation, and dbkit for database utilities. It features multi-document transactions using copy-on-write, an oplog for change streams, and a GridFS implementation with a unique tracking mode for resumable uploads.

Tokens
18.5K
Snippets
91
Records
117
Agent score
67%

What's inside lungo

  1. Overview of lungo architecture and packages

    master

    Lungo is a MongoDB-compatible embeddable database for Go. It is composed of several specialized packages:

    • lungo: The main package implementing the embeddable database and the mongo compatible driver. It manages access to collections via an engine and transaction types.
    • mongokit: Provides MongoDB data handling algorithms, including querying, updating, sorting, and a B-tree based index for documents. It provides the core mongokit.Collection CRUD interface.
    • bsonkit: Extends the official bson package with utilities to inspect, compare, convert, transform, clone, and manipulate BSON data in memory.
    • dbkit: Provides database-centric utilities, such as atomic file writes.

    Most users should interact with the generic driver interface provided by the lungo package to achieve drop-in compatibility with applications written for official MongoDB deployments.

  2. How sessions and multi-document transactions work

    master

    Lungo supports multi-document transactions using a copy-on-write mechanism:

    1. Every transaction creates a copy of the catalog and clones namespaces before applying changes.
    2. Once the new catalog is written to disk, the transaction is considered successful and the catalog is replaced.
    3. Concurrency: Read-only transactions run in parallel as snapshots. Write transactions run sequentially using pessimistic concurrency control to prevent abortions due to conflicts.
  3. How Oplog and Change Streams work

    master
    Lungo implements an oplog similar to MongoDB. Every CRUD change is logged to the local.oplog collection in the same format used by MongoDB change streams. This allows developers to use change streams in the same way they would with a MongoDB replica set.
  4. Manage data operations with Transaction

    master

    A Transaction buffers multiple changes to a Catalog. It provides a thread-safe way to perform CRUD operations, index management, and namespace management. Operations within a transaction are applied to a cloned version of the catalog, and the transaction is marked as dirty if changes occur. Use NewTransaction(catalog *Catalog) to initialize a new transaction.

    import "github.com/256dpi/lungo"
    
    tx := lungo.NewTransaction(myCatalog)
    // Perform operations...
    if tx.Dirty() {
        // Apply changes to the main catalog
    }
  5. Manage MongoDB sessions and transactions

    master

    Lungo provides two ways to manage sessions:

    1. Manual Session Management: Use StartSession to get an ISession. You can then manually call CommitTransaction, AbortTransaction, or EndSession.
    2. Session Callback: Use UseSession or UseSessionWithOptions to execute a function within a session context. This is useful for ensuring sessions are properly managed.
    3. Transactions: Use WithTransaction on an ISession to execute a function within a managed transaction. If the function returns an error, the transaction is aborted; otherwise, it is committed.
    // Example: Using a transaction
    session, _ := client.StartSession()
    result, err := session.WithTransaction(ctx, func(sc ISessionContext) (interface{}, error) {
        // Perform database operations using sc
        return nil, nil
    })
  6. How multikey indexing works in bsonkit

    master

    Lungo's Index implements multikey indexing to mirror MongoDB's behavior. When a document contains an array at an indexed path, the index expands that document into multiple entries—one for each element in the array.

    If multiple columns in a compound index point to arrays, the index generates the Cartesian product of all array elements to ensure all combinations are indexed.

    Example: If an index is on tags and categories, and a document has tags: ["a", "b"] and categories: ["x", "y"], the index will store entries for (a, x), (a, y), (b, x), and (b, y).

  7. Implement storage adapters using the Store interface

    master

    The Store interface defines the contract for storage adapters in Lungo. Any type implementing this interface can be used to persist and retrieve the Catalog. It requires two methods:

    • Load() (*Catalog, error): Retrieves the current catalog.
    • Store(*Catalog) error: Persists the provided catalog.

    Lungo provides two built-in implementations: MemoryStore for in-memory operations and FileStore for disk-based persistence.

  8. Configure storage with MemoryStore and FileStore

    master

    The lungo.Store interface allows for different storage backends:

    • MemoryStore: Keeps all data in memory (volatile).
    • FileStore: Writes all data atomically to a single BSON file (persistent).

    Developers can implement custom adapters by satisfying the lungo.Store interface.

  9. Cursor implementation details and compatibility

    master

    The Cursor struct implements the ICursor interface, ensuring compatibility with MongoDB-style driver patterns in Lungo.

    Note the following behaviors:

    • Thread Safety: The Cursor uses a sync.Mutex to ensure that methods like Next, Decode, and All are safe for concurrent use.
    • Closing Behavior: Once All or Close is called, the cursor is marked as closed. Subsequent calls to Next or All will return errors or false.
    • No-op Methods: For compatibility with the ICursor interface, the following methods are implemented as no-ops:
      • SetBatchSize(int32)
      • SetComment(interface{})
      • SetMaxTime(time.Duration)
      • ID() int64 (always returns 0)
      • Err() error (always returns nil)
      • TryNext(ctx context.Context) (alias for Next)
  10. Clone an index for safe mutations

    master

    The Index type is not thread-safe and does not support automatic rollbacks on errors. To safely perform mutations, it is recommended to use the Clone() method to create a copy of the index before making changes.

    newIndex := index.Clone()
    // Perform mutations on newIndex safely
  11. How tracking mode works in GridFS

    master

    By calling EnableTracking(), the bucket enters a non-standard mode where in-progress uploads and deletions are recorded in a markers collection.

    Key behaviors:

    • Uploads: Instead of creating a file immediately, Close() on an UploadStream transitions a marker to BucketMarkerStateUploaded. The file is only officially created in the files collection when ClaimUpload(ctx, id) is called. This allows for safe multi-document transactions.
    • Deletions: Delete(ctx, id) in tracked mode inserts a BucketMarkerStateDeleted marker instead of immediate deletion. The actual removal of files and chunks happens during a Cleanup cycle.
    • Resumability: Tracked uploads can be suspended via Suspend() and later resumed using Resume() on a new UploadStream with the same ID.
    bucket := lungo.NewBucket(db)
    bucket.EnableTracking()
    
    // 1. Open upload
    stream, _ := bucket.OpenUploadStream("my-file.txt")
    stream.Write(data)
    stream.Close() // Marker is now 'uploaded'
    
    // 2. Claim the upload (e.g., inside a transaction)
    err := bucket.ClaimUpload(ctx, fileID)