memdb.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Package memdb provides an in-memory database that supports transactions
  2. // and MVCC.
  3. package memdb
  4. import (
  5. "sync"
  6. "sync/atomic"
  7. "unsafe"
  8. "github.com/hashicorp/go-immutable-radix"
  9. )
  10. // MemDB is an in-memory database providing Atomicity, Consistency, and
  11. // Isolation from ACID. MemDB doesn't provide Durability since it is an
  12. // in-memory database.
  13. //
  14. // MemDB provides a table abstraction to store objects (rows) with multiple
  15. // indexes based on inserted values. The database makes use of immutable radix
  16. // trees to provide transactions and MVCC.
  17. //
  18. // Objects inserted into MemDB are not copied. It is **extremely important**
  19. // that objects are not modified in-place after they are inserted since they
  20. // are stored directly in MemDB. It remains unsafe to modify inserted objects
  21. // even after they've been deleted from MemDB since there may still be older
  22. // snapshots of the DB being read from other goroutines.
  23. type MemDB struct {
  24. schema *DBSchema
  25. root unsafe.Pointer // *iradix.Tree underneath
  26. primary bool
  27. // There can only be a single writer at once
  28. writer sync.Mutex
  29. }
  30. // NewMemDB creates a new MemDB with the given schema.
  31. func NewMemDB(schema *DBSchema) (*MemDB, error) {
  32. // Validate the schema
  33. if err := schema.Validate(); err != nil {
  34. return nil, err
  35. }
  36. // Create the MemDB
  37. db := &MemDB{
  38. schema: schema,
  39. root: unsafe.Pointer(iradix.New()),
  40. primary: true,
  41. }
  42. if err := db.initialize(); err != nil {
  43. return nil, err
  44. }
  45. return db, nil
  46. }
  47. // getRoot is used to do an atomic load of the root pointer
  48. func (db *MemDB) getRoot() *iradix.Tree {
  49. root := (*iradix.Tree)(atomic.LoadPointer(&db.root))
  50. return root
  51. }
  52. // Txn is used to start a new transaction in either read or write mode.
  53. // There can only be a single concurrent writer, but any number of readers.
  54. func (db *MemDB) Txn(write bool) *Txn {
  55. if write {
  56. db.writer.Lock()
  57. }
  58. txn := &Txn{
  59. db: db,
  60. write: write,
  61. rootTxn: db.getRoot().Txn(),
  62. }
  63. return txn
  64. }
  65. // Snapshot is used to capture a point-in-time snapshot of the database that
  66. // will not be affected by any write operations to the existing DB.
  67. //
  68. // If MemDB is storing reference-based values (pointers, maps, slices, etc.),
  69. // the Snapshot will not deep copy those values. Therefore, it is still unsafe
  70. // to modify any inserted values in either DB.
  71. func (db *MemDB) Snapshot() *MemDB {
  72. clone := &MemDB{
  73. schema: db.schema,
  74. root: unsafe.Pointer(db.getRoot()),
  75. primary: false,
  76. }
  77. return clone
  78. }
  79. // initialize is used to setup the DB for use after creation. This should
  80. // be called only once after allocating a MemDB.
  81. func (db *MemDB) initialize() error {
  82. root := db.getRoot()
  83. for tName, tableSchema := range db.schema.Tables {
  84. for iName := range tableSchema.Indexes {
  85. index := iradix.New()
  86. path := indexPath(tName, iName)
  87. root, _, _ = root.Insert(path, index)
  88. }
  89. }
  90. db.root = unsafe.Pointer(root)
  91. return nil
  92. }
  93. // indexPath returns the path from the root to the given table index
  94. func indexPath(table, index string) []byte {
  95. return []byte(table + "." + index)
  96. }