public async static Task PeriodicallyDisplayTopCountsState(string brokerList, RocksDb db, CancellationToken ct)
        {
            while (true)
            {
                await Task.Delay(10000, ct);
                var it = db.NewIterator(db.GetColumnFamily("counts")).SeekToFirst();

                var N = 5;
                var maxWords = new List<(int, string)>();
                while (it.Valid())
                {
                    var wc = (BitConverter.ToInt32(it.Value()), Encoding.UTF8.GetString(it.Key()));
                    if (maxWords.Count < N) { maxWords.Add(wc); }
                    else { if (wc.Item1 > maxWords[N-1].Item1) { maxWords[N-1] = wc; } }
                    maxWords.Sort((x, y) => y.Item1.CompareTo(x.Item1));
                    it.Next();
                }

                if (maxWords.Count > 0) { Console.WriteLine("Most frequently occuring words known to this instance:"); }
                foreach (var wc in maxWords)
                {
                    Console.WriteLine(" " + wc.Item2 + " " + wc.Item1);
                }
            }
        }
Example #2
0
 public ColumnDb(RocksDb rocksDb, DbOnTheRocks mainDb, string name)
 {
     _rocksDb      = rocksDb;
     _mainDb       = mainDb;
     _columnFamily = _rocksDb.GetColumnFamily(name);
     Name          = name;
 }
Example #3
0
        public Database(string path)
        {
            var columnFamilies = new ColumnFamilies {
                { BLOCKS_FAMILY, new ColumnFamilyOptions() },
                { BLOCK_INDEX_FAMILY, new ColumnFamilyOptions() },
                { TRANSACTIONS_FAMILY, new ColumnFamilyOptions() }
            };

            var options = new DbOptions()
                          .SetCreateIfMissing(true)
                          .SetCreateMissingColumnFamilies(true);

            db                 = RocksDb.Open(options, path, columnFamilies);
            blocksFamily       = db.GetColumnFamily(BLOCKS_FAMILY);
            blockIndexFamily   = db.GetColumnFamily(BLOCK_INDEX_FAMILY);
            transactionsFamily = db.GetColumnFamily(TRANSACTIONS_FAMILY);
        }
Example #4
0
 private ColumnFamilyHandle CreateColumnFamilyIfMissing(RocksDb db, string columnFamily)
 {
     try
     {
         return(db.GetColumnFamily(columnFamily));
     }
     catch
     {
         var options = new ColumnFamilyOptions();
         return(db.CreateColumnFamily(options, columnFamily));
     }
 }
Example #5
0
        public override byte[] Get(byte prefix, byte[] key)
        {
            var keyArray = RocksDbStore.GetKey(prefix, key);

            if (generalStorage.TryGetValue(keyArray, out var value))
            {
                return(value);
            }

            var columnFamily = db.GetColumnFamily(RocksDbStore.GENERAL_STORAGE_FAMILY);

            return(db.Get(keyArray, columnFamily));
        }
Example #6
0
        public DataContext(string path = "ether.db")
        {
            _lastTxIdDictionary = new Dictionary <string, long>();

            var options = new DbOptions()
                          .SetCreateIfMissing(true)
                          .SetCreateMissingColumnFamilies(true);

            // Create column families
            IEnumerable <string> cols           = null;
            ColumnFamilies       columnFamilies = new ColumnFamilies();

            try
            {
                cols = RocksDb.ListColumnFamilies(options, path);
                foreach (var col in cols)
                {
                    columnFamilies.Add(col, new ColumnFamilyOptions());
                }
            }
            catch (Exception e)
            {
                // Database not exist nothing todo
            }
            finally
            {
                _db = RocksDb.Open(options, path, columnFamilies);

                // Load column families to the dictionary
                _columnFamiliesDictionary = new Dictionary <string, ColumnFamilyHandle>();
                if (cols != null)
                {
                    foreach (var col in cols)
                    {
                        _columnFamiliesDictionary.Add(col, _db.GetColumnFamily(col));
                    }
                    foreach (var col in cols)
                    {
                        // Load latest transaction Ids
                        if (!col.Contains(':') && col != "default")
                        {
                            var lastTxIdstr = _db.Get("lastTxId", GetColFamily(col + ":lastid"));
                            _lastTxIdDictionary.Add(col,
                                                    string.IsNullOrEmpty(lastTxIdstr) ? 0 : long.Parse(lastTxIdstr));
                        }
                    }
                }
            }
        }
Example #7
0
        public CheckpointStore(string path)
        {
            db = RocksDb.OpenReadOnly(new DbOptions(), path, RocksDbStore.ColumnFamilies, false);
            var metadataColumnHandle = db.GetColumnFamily(RocksDbStore.METADATA_FAMILY);

            blocks          = new DataTracker <UInt256, BlockState>(db, RocksDbStore.BLOCK_FAMILY);
            transactions    = new DataTracker <UInt256, TransactionState>(db, RocksDbStore.TX_FAMILY);
            accounts        = new DataTracker <UInt160, AccountState>(db, RocksDbStore.ACCOUNT_FAMILY);
            _unspentCoins   = new DataTracker <UInt256, UnspentCoinState>(db, RocksDbStore.UNSPENT_COIN_FAMILY);
            spentCoins      = new DataTracker <UInt256, SpentCoinState>(db, RocksDbStore.SPENT_COIN_FAMILY);
            validators      = new DataTracker <ECPoint, ValidatorState>(db, RocksDbStore.VALIDATOR_FAMILY);
            assets          = new DataTracker <UInt256, AssetState>(db, RocksDbStore.ASSET_FAMILY);
            contracts       = new DataTracker <UInt160, ContractState>(db, RocksDbStore.CONTRACT_FAMILY);
            storages        = new DataTracker <StorageKey, StorageItem>(db, RocksDbStore.STORAGE_FAMILY);
            headerHashList  = new DataTracker <UInt32Wrapper, HeaderHashList>(db, RocksDbStore.HEADER_HASH_LIST_FAMILY);
            validatorsCount = new MetadataTracker <ValidatorsCountState>(db, RocksDbStore.VALIDATORS_COUNT_KEY, metadataColumnHandle);
            blockHashIndex  = new MetadataTracker <HashIndexState>(db, RocksDbStore.CURRENT_BLOCK_KEY, metadataColumnHandle);
            headerHashIndex = new MetadataTracker <HashIndexState>(db, RocksDbStore.CURRENT_HEADER_KEY, metadataColumnHandle);
        }
Example #8
0
        public ColumnFamilyHandle GetFamily(byte table)
        {
            if (!_families.TryGetValue(table, out var family))
            {
                try
                {
                    // Try to find the family

                    family = db.GetColumnFamily(table.ToString());
                    _families.Add(table, family);
                }
                catch (KeyNotFoundException)
                {
                    // Try to create the family

                    family = db.CreateColumnFamily(new ColumnFamilyOptions(), table.ToString());
                    _families.Add(table, family);
                }
            }

            return(family);
        }
Example #9
0
        private ColumnFamilyHandle GetColumnFamily(RocksDb db, Guid?chainId = null)
        {
            if (chainId is null)
            {
                return(null);
            }

            var cfName = chainId.ToString();

            ColumnFamilyHandle cf;

            try
            {
                cf = db.GetColumnFamily(cfName);
            }
            catch (KeyNotFoundException)
            {
                cf = db.CreateColumnFamily(_options, cfName);
            }

            return(cf);
        }
Example #10
0
            /// <summary>
            /// Provides access to and/or creates a RocksDb persistent key-value store.
            /// </summary>
            /// <param name="storeDirectory">
            /// The directory containing the key-value store.
            /// </param>
            /// <param name="defaultColumnKeyTracked">
            /// Whether the default column should be key-tracked.
            /// This will create two columns for the same data,
            /// one with just keys and the other with key and value.
            /// </param>
            /// <param name="additionalColumns">
            /// The names of any additional column families in the key-value store.
            /// If no additional column families are provided, all entries will be stored
            /// in the default column.
            /// Column families are analogous to tables in relational databases.
            /// </param>
            /// <param name="additionalKeyTrackedColumns">
            /// The names of any additional column families in the key-value store that
            /// should also be key-tracked. This will create two columns for the same data,
            /// one with just keys and the other with key and value.
            /// Column families are analogous to tables in relational databases.
            /// </param>
            /// <param name="readOnly">
            /// Whether the database should be opened read-only. This prevents modifications and
            /// creating unnecessary metadata files related to write sessions.
            /// </param>
            /// <param name="dropMismatchingColumns">
            /// If a store already exists at the given directory, whether any columns that mismatch the the columns that were passed into the constructor
            /// should be dropped. This will cause data loss and can only be applied in read-write mode.
            /// </param>
            /// <param name="rotateLogs">
            /// Have RocksDb rotate logs, useful for debugging performance issues. It will rotate logs every 12 hours,
            /// up to a maximum of 60 logs (i.e. 30 days). When the maximum amount of logs is reached, the oldest logs
            /// are overwritten in a circular fashion.
            ///
            /// Every time the RocksDb instance is open, the current log file is truncated, which means that if you
            /// open the DB more than once in a 12 hour period, you will only have partial information.
            /// </param>
            public RocksDbStore(
                string storeDirectory,
                bool defaultColumnKeyTracked                     = false,
                IEnumerable <string> additionalColumns           = null,
                IEnumerable <string> additionalKeyTrackedColumns = null,
                bool readOnly = false,
                bool dropMismatchingColumns = false,
                bool rotateLogs             = false)
            {
                m_storeDirectory = storeDirectory;

                m_defaults.DbOptions = new DbOptions()
                                       .SetCreateIfMissing(true)
                                       .SetCreateMissingColumnFamilies(true)
                                       // The background compaction threads run in low priority, so they should not hamper the rest of
                                       // the system. The number of cores in the system is what we want here according to official docs,
                                       // and we are setting this to the number of logical processors, which may be higher.
                                       .SetMaxBackgroundCompactions(Environment.ProcessorCount)
                                       .SetMaxBackgroundFlushes(1)
                                       .IncreaseParallelism(Environment.ProcessorCount / 2)
                                       // Ensure we have performance statistics for profiling
                                       .EnableStatistics();

                // A small comment on things tested that did not work:
                //  * SetAllowMmapReads(true) and SetAllowMmapWrites(true) produce a dramatic performance drop
                //  * SetUseDirectReads(true) disables the OS cache, and although that's good for random point lookups,
                //    it produces a dramatic performance drop otherwise.

                m_defaults.WriteOptions = new WriteOptions()
                                          // Disable the write ahead log to reduce disk IO. The write ahead log
                                          // is used to recover the store on crashes, so a crash will lose some writes.
                                          // Writes will be made in-memory only until the write buffer size
                                          // is reached and then they will be flushed to storage files.
                                          .DisableWal(1)
                                          // This option is off by default, but just making sure that the C# wrapper
                                          // doesn't change anything. The idea is that the DB won't wait for fsync to
                                          // return before acknowledging the write as successful. This affects
                                          // correctness, because a write may be ACKd before it is actually on disk,
                                          // but it is much faster.
                                          .SetSync(false);


                var blockBasedTableOptions = new BlockBasedTableOptions()
                                             // Use a bloom filter to help reduce read amplification on point lookups. 10 bits per key yields a
                                             // ~1% false positive rate as per the RocksDB documentation. This builds one filter per SST, which
                                             // means its optimized for not having a key.
                                             .SetFilterPolicy(BloomFilterPolicy.Create(10, false))
                                             // Use a hash index in SST files to speed up point lookup.
                                             .SetIndexType(BlockBasedTableIndexType.HashSearch)
                                             // Whether to use the whole key or a prefix of it (obtained through the prefix extractor below).
                                             // Since the prefix extractor is a no-op, better performance is achieved by turning this off (i.e.
                                             // setting it to true).
                                             .SetWholeKeyFiltering(true);

                m_defaults.ColumnFamilyOptions = new ColumnFamilyOptions()
                                                 .SetBlockBasedTableFactory(blockBasedTableOptions)
                                                 .SetPrefixExtractor(SliceTransform.CreateNoOp());

                if (rotateLogs)
                {
                    // Maximum number of information log files
                    m_defaults.DbOptions.SetKeepLogFileNum(60);

                    // Do not rotate information logs based on file size
                    m_defaults.DbOptions.SetMaxLogFileSize(0);

                    // How long before we rotate the current information log file
                    m_defaults.DbOptions.SetLogFileTimeToRoll((ulong)TimeSpan.FromHours(12).Seconds);
                }

                m_columns = new Dictionary <string, ColumnFamilyInfo>();

                additionalColumns           = additionalColumns ?? CollectionUtilities.EmptyArray <string>();
                additionalKeyTrackedColumns = additionalKeyTrackedColumns ?? CollectionUtilities.EmptyArray <string>();

                // The columns that exist in the store on disk may not be in sync with the columns being passed into the constructor
                HashSet <string> existingColumns;

                try
                {
                    existingColumns = new HashSet <string>(RocksDb.ListColumnFamilies(m_defaults.DbOptions, m_storeDirectory));
                }
                catch (RocksDbException)
                {
                    // If there is no existing store, an exception will be thrown, ignore it
                    existingColumns = new HashSet <string>();
                }

                // In read-only mode, open all existing columns in the store without attempting to validate it against the expected column families
                if (readOnly)
                {
                    var columnFamilies = new ColumnFamilies();
                    foreach (var name in existingColumns)
                    {
                        columnFamilies.Add(name, m_defaults.ColumnFamilyOptions);
                    }

                    m_store = RocksDb.OpenReadOnly(m_defaults.DbOptions, m_storeDirectory, columnFamilies, errIfLogFileExists: false);
                }
                else
                {
                    // For read-write mode, column families may be added, so set up column families schema
                    var columnsSchema = new HashSet <string>(additionalColumns);

                    // Default column
                    columnsSchema.Add(ColumnFamilies.DefaultName);

                    // For key-tracked column familiies, create two columns:
                    // 1: Normal column of { key : value }
                    // 2: Key-tracking column of { key : empty-value }
                    if (defaultColumnKeyTracked)
                    {
                        // To be robust to the RocksDB-selected default column name changing,
                        // just name the default column's key-tracking column KeyColumnSuffix
                        columnsSchema.Add(KeyColumnSuffix);
                    }

                    foreach (var name in additionalKeyTrackedColumns)
                    {
                        columnsSchema.Add(name);
                        columnsSchema.Add(name + KeyColumnSuffix);
                    }

                    // Figure out which columns are not part of the schema
                    var outsideSchemaColumns = new List <string>(existingColumns.Except(columnsSchema));

                    // RocksDB requires all columns in the store to be opened in read-write mode, so merge existing columns
                    // with the columns schema that was passed into the constructor
                    existingColumns.UnionWith(columnsSchema);

                    var columnFamilies = new ColumnFamilies();
                    foreach (var name in existingColumns)
                    {
                        columnFamilies.Add(name, m_defaults.ColumnFamilyOptions);
                    }

                    m_store = RocksDb.Open(m_defaults.DbOptions, m_storeDirectory, columnFamilies);

                    // Provide an opportunity to update the store to the new column family schema
                    if (dropMismatchingColumns)
                    {
                        foreach (var name in outsideSchemaColumns)
                        {
                            m_store.DropColumnFamily(name);
                            existingColumns.Remove(name);
                        }
                    }
                }

                var userFacingColumns = existingColumns.Where(name => !name.EndsWith(KeyColumnSuffix));

                foreach (var name in userFacingColumns)
                {
                    var isKeyTracked = existingColumns.Contains(name + KeyColumnSuffix);
                    m_columns.Add(name, new ColumnFamilyInfo()
                    {
                        Handle         = m_store.GetColumnFamily(name),
                        UseKeyTracking = isKeyTracked,
                        KeyHandle      = isKeyTracked ? m_store.GetColumnFamily(name + KeyColumnSuffix) : null,
                    });
                }

                m_columns.TryGetValue(ColumnFamilies.DefaultName, out m_defaultColumnFamilyInfo);
            }
Example #11
0
 static ColumnFamilyHandle GetColumnFamilyHandle(RocksDb db, string?columnFamilyName)
 {
     return(string.IsNullOrEmpty(columnFamilyName)
         ? db.GetDefaultColumnFamily() : db.GetColumnFamily(columnFamilyName));
 }
Example #12
0
            /// <summary>
            /// Provides access to and/or creates a RocksDb persistent key-value store.
            /// </summary>
            public RocksDbStore(RocksDbStoreArguments arguments)
            {
                m_storeDirectory = arguments.StoreDirectory;
                m_openBulkLoad   = arguments.OpenBulkLoad;

                m_defaults.DbOptions = new DbOptions()
                                       .SetCreateIfMissing(true)
                                       .SetCreateMissingColumnFamilies(true)
                                       // The background compaction threads run in low priority, so they should not hamper the rest of
                                       // the system. The number of cores in the system is what we want here according to official docs,
                                       // and we are setting this to the number of logical processors, which may be higher.
                                       // See: https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide#parallelism-options
#if !PLATFORM_OSX
                                       .SetMaxBackgroundCompactions(Environment.ProcessorCount)
                                       .SetMaxBackgroundFlushes(1)
#else
                                       // The memtable uses significant chunks of available system memory on macOS, we increase the number
                                       // of background flushing threads (low priority) and set the DB write buffer size. This allows for
                                       // up to 128 MB in memtables across all column families before we flush to disk.
                                       .SetMaxBackgroundCompactions(Environment.ProcessorCount / 4)
                                       .SetMaxBackgroundFlushes(Environment.ProcessorCount / 4)
                                       .SetDbWriteBufferSize(128 << 20)
#endif
                                       .IncreaseParallelism(Environment.ProcessorCount / 2);

                if (arguments.EnableStatistics)
                {
                    m_defaults.DbOptions.EnableStatistics();
                }

                if (arguments.OpenBulkLoad)
                {
                    m_defaults.DbOptions.PrepareForBulkLoad();
                }

                // Maximum number of information log files
                if (arguments.RotateLogsNumFiles != null)
                {
                    m_defaults.DbOptions.SetKeepLogFileNum(arguments.RotateLogsNumFiles.Value);
                }

                // Do not rotate information logs based on file size
                if (arguments.RotateLogsMaxFileSizeBytes != null)
                {
                    m_defaults.DbOptions.SetMaxLogFileSize(arguments.RotateLogsMaxFileSizeBytes.Value);
                }

                // How long before we rotate the current information log file
                if (arguments.RotateLogsMaxAge != null)
                {
                    m_defaults.DbOptions.SetLogFileTimeToRoll((ulong)arguments.RotateLogsMaxAge.Value.Seconds);
                }

                if (arguments.FastOpen)
                {
                    // max_file_opening_threads is defaulted to 16, so no need to update here.
                    RocksDbSharp.Native.Instance.rocksdb_options_set_skip_stats_update_on_db_open(m_defaults.DbOptions.Handle, true);
                }

                if (arguments.DisableAutomaticCompactions)
                {
                    m_defaults.DbOptions.SetDisableAutoCompactions(1);
                }

                // A small comment on things tested that did not work:
                //  * SetAllowMmapReads(true) and SetAllowMmapWrites(true) produce a dramatic performance drop
                //  * SetUseDirectReads(true) disables the OS cache, and although that's good for random point lookups,
                //    it produces a dramatic performance drop otherwise.

                m_defaults.WriteOptions = new WriteOptions()
                                          // Disable the write ahead log to reduce disk IO. The write ahead log
                                          // is used to recover the store on crashes, so a crash will lose some writes.
                                          // Writes will be made in-memory only until the write buffer size
                                          // is reached and then they will be flushed to storage files.
                                          .DisableWal(1)
                                          // This option is off by default, but just making sure that the C# wrapper
                                          // doesn't change anything. The idea is that the DB won't wait for fsync to
                                          // return before acknowledging the write as successful. This affects
                                          // correctness, because a write may be ACKd before it is actually on disk,
                                          // but it is much faster.
                                          .SetSync(false);


                var blockBasedTableOptions = new BlockBasedTableOptions()
                                             // Use a bloom filter to help reduce read amplification on point lookups. 10 bits per key yields a
                                             // ~1% false positive rate as per the RocksDB documentation. This builds one filter per SST, which
                                             // means its optimized for not having a key.
                                             .SetFilterPolicy(BloomFilterPolicy.Create(10, false))
                                             // Use a hash index in SST files to speed up point lookup.
                                             .SetIndexType(BlockBasedTableIndexType.HashSearch)
                                             // Whether to use the whole key or a prefix of it (obtained through the prefix extractor below).
                                             // Since the prefix extractor is a no-op, better performance is achieved by turning this off (i.e.
                                             // setting it to true).
                                             .SetWholeKeyFiltering(true);

                m_defaults.ColumnFamilyOptions = new ColumnFamilyOptions()
#if PLATFORM_OSX
                                                 // As advised by the official documentation, LZ4 is the preferred compression algorithm, our RocksDB
                                                 // dynamic library has been compiled to support this on macOS. Fallback to Snappy on other systems (default).
                                                 .SetCompression(CompressionTypeEnum.rocksdb_lz4_compression)
#endif
                                                 .SetBlockBasedTableFactory(blockBasedTableOptions)
                                                 .SetPrefixExtractor(SliceTransform.CreateNoOp());

                m_columns = new Dictionary <string, ColumnFamilyInfo>();

                // The columns that exist in the store on disk may not be in sync with the columns being passed into the constructor
                HashSet <string> existingColumns;
                try
                {
                    existingColumns = new HashSet <string>(RocksDb.ListColumnFamilies(m_defaults.DbOptions, m_storeDirectory));
                }
                catch (RocksDbException)
                {
                    // If there is no existing store, an exception will be thrown, ignore it
                    existingColumns = new HashSet <string>();
                }

                // In read-only mode, open all existing columns in the store without attempting to validate it against the expected column families
                if (arguments.ReadOnly)
                {
                    var columnFamilies = new ColumnFamilies();
                    foreach (var name in existingColumns)
                    {
                        columnFamilies.Add(name, m_defaults.ColumnFamilyOptions);
                    }

                    m_store = RocksDb.OpenReadOnly(m_defaults.DbOptions, m_storeDirectory, columnFamilies, errIfLogFileExists: false);
                }
                else
                {
                    // For read-write mode, column families may be added, so set up column families schema
                    var additionalColumns = arguments.AdditionalColumns ?? CollectionUtilities.EmptyArray <string>();
                    var columnsSchema     = new HashSet <string>(additionalColumns);

                    // Default column
                    columnsSchema.Add(ColumnFamilies.DefaultName);

                    // For key-tracked column familiies, create two columns:
                    // 1: Normal column of { key : value }
                    // 2: Key-tracking column of { key : empty-value }
                    if (arguments.DefaultColumnKeyTracked)
                    {
                        // To be robust to the RocksDB-selected default column name changing,
                        // just name the default column's key-tracking column KeyColumnSuffix
                        columnsSchema.Add(KeyColumnSuffix);
                    }

                    var additionalKeyTrackedColumns = arguments.AdditionalKeyTrackedColumns ?? CollectionUtilities.EmptyArray <string>();
                    foreach (var name in additionalKeyTrackedColumns)
                    {
                        columnsSchema.Add(name);
                        columnsSchema.Add(name + KeyColumnSuffix);
                    }

                    // Figure out which columns are not part of the schema
                    var outsideSchemaColumns = new List <string>(existingColumns.Except(columnsSchema));

                    // RocksDB requires all columns in the store to be opened in read-write mode, so merge existing columns
                    // with the columns schema that was passed into the constructor
                    existingColumns.UnionWith(columnsSchema);

                    var columnFamilies = new ColumnFamilies();
                    foreach (var name in existingColumns)
                    {
                        columnFamilies.Add(name, m_defaults.ColumnFamilyOptions);
                    }

                    m_store = RocksDb.Open(m_defaults.DbOptions, m_storeDirectory, columnFamilies);

                    // Provide an opportunity to update the store to the new column family schema
                    if (arguments.DropMismatchingColumns)
                    {
                        foreach (var name in outsideSchemaColumns)
                        {
                            m_store.DropColumnFamily(name);
                            existingColumns.Remove(name);
                        }
                    }
                }

                var userFacingColumns = existingColumns.Where(name => !name.EndsWith(KeyColumnSuffix));

                foreach (var name in userFacingColumns)
                {
                    var isKeyTracked = existingColumns.Contains(name + KeyColumnSuffix);
                    m_columns.Add(name, new ColumnFamilyInfo()
                    {
                        Handle         = m_store.GetColumnFamily(name),
                        UseKeyTracking = isKeyTracked,
                        KeyHandle      = isKeyTracked ? m_store.GetColumnFamily(name + KeyColumnSuffix) : null,
                    });
                }

                m_columns.TryGetValue(ColumnFamilies.DefaultName, out m_defaultColumnFamilyInfo);
            }
Example #13
0
 public MetaDataCache(RocksDb db, byte key, ReadOptions?readOptions = null, WriteBatch?writeBatch = null, Func <T>?factory = null)
     : this(db, new byte[] { key }, db.GetColumnFamily(METADATA_FAMILY), readOptions, writeBatch, factory)
 {
 }
Example #14
0
 public DataTracker(RocksDb db, string familyName)
 {
     this.db      = db;
     columnFamily = db.GetColumnFamily(familyName);
 }
 public DataCache(RocksDb db, string familyName, ReadOptions?readOptions = null, WriteBatch?writeBatch = null)
     : this(db, db.GetColumnFamily(familyName), readOptions, writeBatch)
 {
 }
Example #16
0
            /// <summary>
            /// Provides access to and/or creates a RocksDb persistent key-value store.
            /// </summary>
            /// <param name="storeDirectory">
            /// The directory containing the key-value store.
            /// </param>
            /// <param name="defaultColumnKeyTracked">
            /// Whether the default column should be key-tracked.
            /// This will create two columns for the same data,
            /// one with just keys and the other with key and value.
            /// </param>
            /// <param name="additionalColumns">
            /// The names of any additional column families in the key-value store.
            /// If no additional column families are provided, all entries will be stored
            /// in the default column.
            /// Column families are analogous to tables in relational databases.
            /// </param>
            /// <param name="additionalKeyTrackedColumns">
            /// The names of any additional column families in the key-value store that
            /// should also be key-tracked. This will create two columns for the same data,
            /// one with just keys and the other with key and value.
            /// Column families are analogous to tables in relational databases.
            /// </param>
            /// <param name="readOnly">
            /// Whether the database should be opened read-only. This prevents modifications and
            /// creating unnecessary metadata files related to write sessions.
            /// </param>
            /// <param name="dropMismatchingColumns">
            /// If a store already exists at the given directory, whether any columns that mismatch the the columns that were passed into the constructor
            /// should be dropped. This will cause data loss and can only be applied in read-write mode.
            /// </param>
            public RocksDbStore(
                string storeDirectory,
                bool defaultColumnKeyTracked                     = false,
                IEnumerable <string> additionalColumns           = null,
                IEnumerable <string> additionalKeyTrackedColumns = null,
                bool readOnly = false,
                bool dropMismatchingColumns = false)
            {
                m_storeDirectory = storeDirectory;

                m_defaults.DbOptions = new DbOptions()
                                       .SetCreateIfMissing(true)
                                       .SetCreateMissingColumnFamilies(true);

                // Disable the write ahead log to reduce disk IO. The write ahead log
                // is used to recover the store on crashes, so a crash will lose some writes.
                // Writes will be made in-memory only until the write buffer size
                // is reached and then they will be flushed to storage files.
                m_defaults.WriteOptions = new WriteOptions().DisableWal(1);

                m_defaults.ColumnFamilyOptions = new ColumnFamilyOptions();

                m_columns = new Dictionary <string, ColumnFamilyInfo>();

                additionalColumns           = additionalColumns ?? CollectionUtilities.EmptyArray <string>();
                additionalKeyTrackedColumns = additionalKeyTrackedColumns ?? CollectionUtilities.EmptyArray <string>();

                // The columns that exist in the store on disk may not be in sync with the columns being passed into the constructor
                HashSet <string> existingColumns;

                try
                {
                    existingColumns = new HashSet <string>(RocksDb.ListColumnFamilies(m_defaults.DbOptions, m_storeDirectory));
                }
                catch (RocksDbException)
                {
                    // If there is no existing store, an exception will be thrown, ignore it
                    existingColumns = new HashSet <string>();
                }

                // In read-only mode, open all existing columns in the store without attempting to validate it against the expected column families
                if (readOnly)
                {
                    var columnFamilies = new ColumnFamilies();
                    foreach (var name in existingColumns)
                    {
                        columnFamilies.Add(name, m_defaults.ColumnFamilyOptions);
                    }

                    m_store = RocksDb.OpenReadOnly(m_defaults.DbOptions, m_storeDirectory, columnFamilies, errIfLogFileExists: false);
                }
                else
                {
                    // For read-write mode, column families may be added, so set up column families schema
                    var columnsSchema = new HashSet <string>(additionalColumns);

                    // Default column
                    columnsSchema.Add(ColumnFamilies.DefaultName);

                    // For key-tracked column familiies, create two columns:
                    // 1: Normal column of { key : value }
                    // 2: Key-tracking column of { key : empty-value }
                    if (defaultColumnKeyTracked)
                    {
                        // To be robust to the RocksDB-selected default column name changing,
                        // just name the default column's key-tracking column KeyColumnSuffix
                        columnsSchema.Add(KeyColumnSuffix);
                    }

                    foreach (var name in additionalKeyTrackedColumns)
                    {
                        columnsSchema.Add(name);
                        columnsSchema.Add(name + KeyColumnSuffix);
                    }

                    // Figure out which columns are not part of the schema
                    var outsideSchemaColumns = new List <string>(existingColumns.Except(columnsSchema));

                    // RocksDB requires all columns in the store to be opened in read-write mode, so merge existing columns
                    // with the columns schema that was passed into the constructor
                    existingColumns.UnionWith(columnsSchema);

                    var columnFamilies = new ColumnFamilies();
                    foreach (var name in existingColumns)
                    {
                        columnFamilies.Add(name, m_defaults.ColumnFamilyOptions);
                    }

                    m_store = RocksDb.Open(m_defaults.DbOptions, m_storeDirectory, columnFamilies);

                    // Provide an opportunity to update the store to the new column family schema
                    if (dropMismatchingColumns)
                    {
                        foreach (var name in outsideSchemaColumns)
                        {
                            m_store.DropColumnFamily(name);
                            existingColumns.Remove(name);
                        }
                    }
                }

                var userFacingColumns = existingColumns.Where(name => !name.EndsWith(KeyColumnSuffix));

                foreach (var name in userFacingColumns)
                {
                    var isKeyTracked = existingColumns.Contains(name + KeyColumnSuffix);
                    m_columns.Add(name, new ColumnFamilyInfo()
                    {
                        Handle         = m_store.GetColumnFamily(name),
                        UseKeyTracking = isKeyTracked,
                        KeyHandle      = isKeyTracked ? m_store.GetColumnFamily(name + KeyColumnSuffix) : null,
                    });
                }

                m_columns.TryGetValue(ColumnFamilies.DefaultName, out m_defaultColumnFamilyInfo);
            }
 public override byte[] Get(byte prefix, byte[] key)
 {
     var columnFamily = db.GetColumnFamily(GENERAL_STORAGE_FAMILY);
     return db.Get(GetKey(prefix, key), columnFamily);
 }
Example #18
0
 private ColumnFamilyHandle GetPartition()
 {
     return(_db.GetColumnFamily(partitionName));
 }
        /// <summary>
        ///     A transactional (exactly once) processing loop that reads individual words and updates
        ///     the corresponding total count state.
        ///
        ///     When a rebalance occurs (including on startup), the total count state for all assigned
        ///     partitions is reloaded before the loop commences to update it.
        /// </summary>
        /// <remarks>
        ///     Refer to Processor_MapWords for more detailed comments.
        /// </remarks>
        public static void Processor_AggregateWords(string brokerList, string clientId, RocksDb db, CancellationToken ct)
        {
            var TxnCommitPeriod = TimeSpan.FromSeconds(10);

            var cConfig = new ConsumerConfig
            {
                BootstrapServers = brokerList,
                GroupId          = ConsumerGroup_Aggregate,
                AutoOffsetReset  = AutoOffsetReset.Earliest,
                // This should be greater than the maximum amount of time required to read in
                // existing count state.
                MaxPollIntervalMs = 86400000,
                EnableAutoCommit  = false
            };

            ColumnFamilyHandle columnFamily = null;

            var lastTxnCommit = DateTime.Now;

            var producerState = new Dictionary <TopicPartition, ProducerState <string, int> >();

            using (var consumer = new ConsumerBuilder <string, Null>(cConfig)
                                  .SetPartitionsRevokedHandler((c, partitions) => {
                // clear rocksdb state.
                db.DropColumnFamily("counts");
                db.CreateColumnFamily(new ColumnFamilyOptions(), "counts");

                var tasks = new List <Task>();
                foreach (var p in producerState.Values)
                {
                    tasks.Add(Task.Run(() => {
                        p.Producer.AbortTransaction(DefaultTimeout);     // Note: Not cancellable yet.
                        p.Producer.Dispose();
                    }, ct));
                }
                if (tasks.Count > 0)
                {
                    Console.WriteLine("Aborting current AggregateWords transactions.");
                }
                Task.WaitAll(tasks.ToArray());
                producerState.Clear();
            })
                                  .SetPartitionsAssignedHandler((c, partitions) => {
                Console.WriteLine(
                    "** AggregateWords consumer group rebalanced. Partition assignment: [" +
                    string.Join(',', partitions.Select(p => p.Partition.Value)) +
                    "]");

                Trace.Assert(producerState.Count == 0, "Unexpected producer state");

                var tasks = new List <Task>();
                foreach (var tp in partitions)
                {
                    tasks.Add(Task.Run(() => {
                        var pConfig = new ProducerConfig
                        {
                            BootstrapServers = brokerList,
                            TransactionalId = TransactionalId_Aggregate + "-" + clientId + "-" + tp.Partition
                        };

                        var p = new ProducerBuilder <string, int>(pConfig).Build();
                        p.InitTransactions(DefaultTimeout);     // Note: Not cancellable yet.
                        p.BeginTransaction();
                        lock (producerState)
                        {
                            producerState.Add(tp, new ProducerState <string, int> {
                                Producer = p, Offset = Offset.Unset
                            });
                        }
                    }, ct));
                }
                Task.WaitAll(tasks.ToArray());

                columnFamily = db.GetColumnFamily("counts");
                LoadCountState(db, brokerList, partitions.Select(p => p.Partition), columnFamily, ct);
            })
                                  .Build())
            {
                consumer.Subscribe(Topic_Words);

                var wCount = 0;

                while (true)
                {
                    try
                    {
                        var cr = consumer.Consume(ct);
                        producerState[cr.TopicPartition].Offset = cr.Offset;

                        var kBytes   = Encoding.UTF8.GetBytes(cr.Message.Key);
                        var vBytes   = db.Get(kBytes, columnFamily);
                        var v        = vBytes == null ? 0 : BitConverter.ToInt32(vBytes);
                        var updatedV = v + 1;

                        db.Put(kBytes, BitConverter.GetBytes(updatedV), columnFamily);

                        while (true)
                        {
                            try
                            {
                                producerState[cr.TopicPartition].Producer.Produce(
                                    Topic_Counts, new Message <string, int> {
                                    Key = cr.Message.Key, Value = updatedV
                                });
                            }
                            catch (KafkaException e)
                            {
                                if (e.Error.Code == ErrorCode.Local_QueueFull)
                                {
                                    Thread.Sleep(TimeSpan.FromSeconds(1000));
                                    continue;
                                }
                                throw;
                            }
                            break;
                        }

                        wCount += 1;

                        if (DateTime.Now > lastTxnCommit + TxnCommitPeriod)
                        {
                            // Execute the transaction commits for each producer in parallel.
                            var tasks = new List <Task>();
                            foreach (var state in producerState)
                            {
                                if (state.Value.Offset == Offset.Unset)
                                {
                                    continue;
                                }

                                tasks.Add(Task.Run(() =>
                                {
                                    state.Value.Producer.SendOffsetsToTransaction(
                                        new List <TopicPartitionOffset> {
                                        new TopicPartitionOffset(state.Key, state.Value.Offset + 1)
                                    },
                                        consumer.ConsumerGroupMetadata, DefaultTimeout);
                                    state.Value.Producer.CommitTransaction(DefaultTimeout);
                                    state.Value.Offset = Offset.Unset;
                                    state.Value.Producer.BeginTransaction();
                                }));
                            }
                            Task.WaitAll(tasks.ToArray(), ct);

                            Console.WriteLine($"Committed AggregateWords transaction(s) comprising updates to {wCount} words.");
                            lastTxnCommit = DateTime.Now;
                            wCount        = 0;
                        }
                    }
                    catch (Exception)
                    {
                        consumer.Close();
                        break;
                    }
                }
            }
        }