// Adds a new change to a channel log. func (db *Database) AddToChangeLog(channelName string, entry channels.LogEntry, parentRevID string) error { if channelName == "*" && !EnableStarChannelLog { return nil } var fullUpdate bool var removedCount int fullUpdateAttempts := 0 logDocID := channelLogDocID(channelName) err := db.Bucket.WriteUpdate(logDocID, 0, func(currentValue []byte) ([]byte, walrus.WriteOptions, error) { // (Be careful: this block can be invoked multiple times if there are races!) // Should I do a full update of the change log, removing older entries to limit its size? // This has to be done occasionaly, but it's slower than simply appending to it. This // test is a heuristic that seems to strike a good balance in practice: fullUpdate = AlwaysCompactChangeLog || (len(currentValue) > 20000 && (rand.Intn(100) < len(currentValue)/5000)) removedCount = 0 if len(currentValue) == 0 { channelLog := channels.ChangeLog{} channelLog.Add(entry) return encodeChannelLog(&channelLog), walrus.Raw, nil } if fullUpdate { fullUpdateAttempts++ var newValue bytes.Buffer removedCount = channels.TruncateEncodedChangeLog(bytes.NewReader(currentValue), MaxChangeLogLength-1, &newValue) if removedCount > 0 { entry.Encode(&newValue, parentRevID) return newValue.Bytes(), walrus.Raw, nil } } w := bytes.NewBuffer(make([]byte, 0, 50000)) entry.Encode(w, parentRevID) currentValue = append(currentValue, w.Bytes()...) return currentValue, walrus.Raw, nil }) /*if fullUpdate { base.Log("Removed %d entries from %q", removedCount, channelName) } else if fullUpdateAttempts > 0 { base.Log("Attempted to remove entries %d times but failed", fullUpdateAttempts) }*/ return err }
// Writes new changes to my channel log document. func (c *channelLogWriter) addToChangeLog_(entries []*changeEntry) { var err error dbExpvars.Add("channelLogAdds", 1) logDocID := channelLogDocID(c.channelName) // A fraction of the time we will do a full update and clean stuff out. fullUpdate := AlwaysCompactChangeLog || len(entries) > MaxChangeLogLength/2 || rand.Intn(MaxChangeLogLength/len(entries)) == 0 if !fullUpdate { // Non-full update; just append the new entries: w := bytes.NewBuffer(make([]byte, 0, 100*len(entries))) for _, entry := range entries { entry.logEntry.Encode(w, entry.parentRevID) } data := w.Bytes() err = c.bucket.Append(logDocID, data) if err == nil { base.LogTo("ChannelLog", "Appended %d sequence(s) to %q", len(entries), c.channelName) dbExpvars.Add("channelLogAppends", 1) } else if base.IsDocNotFoundError(err) { // Append failed due to doc not existing, so fall back to full update err = nil fullUpdate = true } else { base.Warn("Error appending to %q -- %v", len(entries), c.channelName, err) } } if fullUpdate { // Full update: do a CAS-based read+write: fullUpdateAttempts := 0 var oldChangeLogCount, newChangeLogCount int err = c.bucket.WriteUpdate(logDocID, 0, func(currentValue []byte) ([]byte, walrus.WriteOptions, error) { fullUpdateAttempts++ numToKeep := MaxChangeLogLength - len(entries) if len(currentValue) == 0 || numToKeep <= 0 { // If log was missing or empty, or will be entirely overwritten, create a new one: entriesToWrite := entries if numToKeep < 0 { entriesToWrite = entries[-numToKeep:] } channelLog := channels.ChangeLog{} for _, entry := range entriesToWrite { channelLog.Add(*entry.logEntry) } newChangeLogCount = len(entriesToWrite) oldChangeLogCount = newChangeLogCount return encodeChannelLog(&channelLog), walrus.Raw, nil } else { // Append to an already existing change log: var newValue bytes.Buffer var nRemoved int nRemoved, newChangeLogCount = channels.TruncateEncodedChangeLog( bytes.NewReader(currentValue), numToKeep, numToKeep/2, &newValue) for _, entry := range entries { entry.logEntry.Encode(&newValue, entry.parentRevID) } oldChangeLogCount = nRemoved + newChangeLogCount newChangeLogCount += len(entries) return newValue.Bytes(), walrus.Raw, nil } }) if err == nil { dbExpvars.Add("channelLogRewrites", 1) dbExpvars.Add("channelLogRewriteCollisions", int64(fullUpdateAttempts-1)) base.LogTo("ChannelLog", "Wrote %d sequences (was %d now %d) to %q in %d attempts", len(entries), oldChangeLogCount, newChangeLogCount, c.channelName, fullUpdateAttempts) } else { base.Warn("Error writing %d sequence(s) to %q -- %v", len(entries), c.channelName, err) } } }