// NewIterator creates a new Iterator for the given source and metric. func (db *DB) NewIterator(source, metric string) (*Iterator, error) { var p partition.Partition = nil i := db.partitionList.NewIterator() for i.Next() { val, _ := i.Value() val.Hold() if val.HasMetric(source, metric) { if p != nil { p.Release() } p = val } else { val.Release() } } if p == nil { return nil, errors.New("catena: couldn't find metric for iterator") } partitionIter, err := p.NewIterator(source, metric) p.Release() if err != nil { return nil, err } return &Iterator{ source: source, metric: metric, db: db, curPartition: p, Iterator: partitionIter, }, nil }
// Reset moves i to the first available timestamp. func (i *Iterator) Reset() error { i.Iterator.Close() var p partition.Partition partitionListIter := i.db.partitionList.NewIterator() for partitionListIter.Next() { val, _ := partitionListIter.Value() val.Hold() if val.HasMetric(i.source, i.metric) { if p != nil { p.Release() } p = val } else { val.Release() } } if p == nil { return errors.New("catena: couldn't find metric for iterator") } defer p.Release() i.curPartition = p partitionIter, err := p.NewIterator(i.source, i.metric) if err != nil { return err } i.Iterator = partitionIter return nil }
// InsertRows inserts the given rows into the database. func (db *DB) InsertRows(rows []Row) error { keyToRows := map[int][]Row{} for _, row := range rows { key := int(row.Timestamp / db.partitionSize) keyToRows[key] = append(keyToRows[key], row) } keys := []int{} for key := range keyToRows { keys = append(keys, key) } sort.Ints(keys) for _, key := range keys { rowsForKey := keyToRows[key] minTimestampInRows := int64(0) maxTimestampInRows := int64(0) for i, row := range rowsForKey { if i == 1 { minTimestampInRows = row.Timestamp maxTimestampInRows = row.Timestamp } if row.Timestamp > maxTimestampInRows { maxTimestampInRows = row.Timestamp } if row.Timestamp < minTimestampInRows { minTimestampInRows = row.Timestamp } } var p partition.Partition FIND_PARTITION: i := db.partitionList.NewIterator() for i.Next() { val, _ := i.Value() val.Hold() if val.MinTimestamp()/db.partitionSize == int64(key) { p = val goto VALID_PARTITION } if val.MinTimestamp()/db.partitionSize < int64(key) && val.MaxTimestamp()/db.partitionSize >= int64(key) { p = val goto VALID_PARTITION } val.Release() } db.partitionCreateLock.Lock() if p == nil { if int64(key) < atomic.LoadInt64(&db.minTimestamp)/db.partitionSize { db.partitionCreateLock.Unlock() return errors.New("catena: row(s) being inserted are too old") } if db.partitionList.Size() == 0 || int64(key) > atomic.LoadInt64(&db.maxTimestamp)/db.partitionSize { // Need to make a new partition newPartitionID := atomic.LoadInt64(&db.lastPartitionID) + 1 w, err := wal.NewFileWAL(filepath.Join(db.baseDir, fmt.Sprintf("%d.wal", newPartitionID))) if err != nil { // Couldn't create the WAL. Maybe another writer has created // the WAL file. Retry. db.partitionCreateLock.Unlock() goto FIND_PARTITION } p = memory.NewMemoryPartition(w) db.partitionList.Insert(p) p.Hold() if db.partitionList.Size() == 1 { atomic.SwapInt64(&db.minTimestamp, minTimestampInRows) atomic.SwapInt64(&db.maxTimestamp, maxTimestampInRows) } if !atomic.CompareAndSwapInt64(&db.lastPartitionID, newPartitionID-1, newPartitionID) { p.Release() p.Destroy() db.partitionCreateLock.Unlock() goto FIND_PARTITION } } if p == nil { db.partitionCreateLock.Unlock() goto FIND_PARTITION } } db.partitionCreateLock.Unlock() VALID_PARTITION: if p.ReadOnly() { p.Release() return errors.New("catena: insert into read-only partition") } err := p.InsertRows(*(*[]partition.Row)(unsafe.Pointer(&rows))) if err != nil { p.Release() return err } p.Release() for min := atomic.LoadInt64(&db.minTimestamp); min > minTimestampInRows; min = atomic.LoadInt64(&db.minTimestamp) { if atomic.CompareAndSwapInt64(&db.minTimestamp, min, minTimestampInRows) { break } } for max := atomic.LoadInt64(&db.maxTimestamp); max < maxTimestampInRows; max = atomic.LoadInt64(&db.maxTimestamp) { if atomic.CompareAndSwapInt64(&db.maxTimestamp, max, maxTimestampInRows) { break } } } return nil }