20 #ifdef RIPPLED_REPORTING
22 #include <cassandra.h>
25 #include <ripple/basics/Slice.h>
26 #include <ripple/basics/StringUtilities.h>
27 #include <ripple/basics/contract.h>
28 #include <ripple/basics/strHex.h>
29 #include <ripple/nodestore/Backend.h>
30 #include <ripple/nodestore/Factory.h>
31 #include <ripple/nodestore/Manager.h>
32 #include <ripple/nodestore/impl/DecodedBlob.h>
33 #include <ripple/nodestore/impl/EncodedBlob.h>
34 #include <ripple/nodestore/impl/codec.h>
35 #include <ripple/protocol/digest.h>
36 #include <boost/asio/steady_timer.hpp>
37 #include <boost/filesystem.hpp>
49 #include <nudb/nudb.hpp>
60 writeCallback(CassFuture* fut,
void* cbData);
62 readCallback(CassFuture* fut,
void* cbData);
64 class CassandraBackend :
public Backend
70 makeStatement(
char const* query,
std::size_t params)
72 CassStatement* ret = cass_statement_new(query, params);
74 cass_statement_set_consistency(ret, CASS_CONSISTENCY_QUORUM);
78 ss <<
"nodestore: Error setting query consistency: " << query
79 <<
", result: " << rc <<
", " << cass_error_desc(rc);
80 Throw<std::runtime_error>(ss.
str());
87 size_t const keyBytes_;
89 Section
const config_;
98 [](CassSession* session) {
100 CassFuture* fut = cass_session_close(session);
101 cass_future_wait(fut);
102 cass_future_free(fut);
103 cass_session_free(session);
108 const CassPrepared* insert_ =
nullptr;
109 const CassPrepared* select_ =
nullptr;
112 boost::asio::io_context ioContext_;
118 uint32_t maxRequestsOutstanding = 10000000;
136 Section
const& keyValues,
138 : j_(journal), keyBytes_(keyBytes), config_(keyValues)
142 ~CassandraBackend()
override
163 open(
bool createIfMissing)
override
168 JLOG(j_.
error()) <<
"database is already open";
173 CassCluster* cluster = cass_cluster_new();
175 Throw<std::runtime_error>(
176 "nodestore:: Failed to create CassCluster");
179 get<std::string>(config_,
"secure_connect_bundle");
181 if (!secureConnectBundle.
empty())
185 if (cass_cluster_set_cloud_secure_connection_bundle(
186 cluster, secureConnectBundle.
c_str()) != CASS_OK)
188 JLOG(j_.
error()) <<
"Unable to configure cloud using the "
189 "secure connection bundle: "
190 << secureConnectBundle;
191 Throw<std::runtime_error>(
192 "nodestore: Failed to connect using secure connection "
200 get<std::string>(config_,
"contact_points");
201 if (contact_points.
empty())
203 Throw<std::runtime_error>(
204 "nodestore: Missing contact_points in Cassandra config");
206 CassError rc = cass_cluster_set_contact_points(
207 cluster, contact_points.
c_str());
211 ss <<
"nodestore: Error setting Cassandra contact_points: "
212 << contact_points <<
", result: " << rc <<
", "
213 << cass_error_desc(rc);
215 Throw<std::runtime_error>(ss.
str());
218 int port = get<int>(config_,
"port");
221 rc = cass_cluster_set_port(cluster, port);
225 ss <<
"nodestore: Error setting Cassandra port: " << port
226 <<
", result: " << rc <<
", " << cass_error_desc(rc);
228 Throw<std::runtime_error>(ss.
str());
232 cass_cluster_set_token_aware_routing(cluster, cass_true);
233 CassError rc = cass_cluster_set_protocol_version(
234 cluster, CASS_PROTOCOL_VERSION_V4);
238 ss <<
"nodestore: Error setting cassandra protocol version: "
239 <<
", result: " << rc <<
", " << cass_error_desc(rc);
241 Throw<std::runtime_error>(ss.
str());
244 std::string username = get<std::string>(config_,
"username");
248 << get<std::string>(config_,
"password").c_str()
250 cass_cluster_set_credentials(
253 get<std::string>(config_,
"password").c_str());
257 rc = cass_cluster_set_num_threads_io(cluster, workers);
261 ss <<
"nodestore: Error setting Cassandra io threads to " << workers
262 <<
", result: " << rc <<
", " << cass_error_desc(rc);
263 Throw<std::runtime_error>(ss.
str());
266 cass_cluster_set_request_timeout(cluster, 2000);
268 rc = cass_cluster_set_queue_size_io(
270 maxRequestsOutstanding);
275 ss <<
"nodestore: Error setting Cassandra max core connections per "
277 <<
", result: " << rc <<
", " << cass_error_desc(rc);
283 std::string certfile = get<std::string>(config_,
"certfile");
287 boost::filesystem::path(certfile).
string(), std::ios::in);
291 ss <<
"opening config file " << certfile;
292 Throw<std::system_error>(
298 if (fileStream.bad())
301 ss <<
"reading config file " << certfile;
302 Throw<std::system_error>(
306 CassSsl* context = cass_ssl_new();
307 cass_ssl_set_verify_flags(context, CASS_SSL_VERIFY_NONE);
308 rc = cass_ssl_add_trusted_cert(context, cert.c_str());
312 ss <<
"nodestore: Error setting Cassandra ssl context: " << rc
313 <<
", " << cass_error_desc(rc);
314 Throw<std::runtime_error>(ss.
str());
317 cass_cluster_set_ssl(cluster, context);
318 cass_ssl_free(context);
321 std::string keyspace = get<std::string>(config_,
"keyspace");
322 if (keyspace.
empty())
324 Throw<std::runtime_error>(
325 "nodestore: Missing keyspace in Cassandra config");
328 std::string tableName = get<std::string>(config_,
"table_name");
329 if (tableName.
empty())
331 Throw<std::runtime_error>(
332 "nodestore: Missing table name in Cassandra config");
335 cass_cluster_set_connect_timeout(cluster, 10000);
337 CassStatement* statement;
339 bool setupSessionAndTable =
false;
340 while (!setupSessionAndTable)
343 session_.reset(cass_session_new());
346 fut = cass_session_connect_keyspace(
347 session_.get(), cluster, keyspace.
c_str());
348 rc = cass_future_error_code(fut);
349 cass_future_free(fut);
353 ss <<
"nodestore: Error connecting Cassandra session keyspace: "
354 << rc <<
", " << cass_error_desc(rc);
360 query <<
"CREATE TABLE IF NOT EXISTS " << tableName
361 <<
" ( hash blob PRIMARY KEY, object blob)";
363 statement = makeStatement(query.
str().c_str(), 0);
364 fut = cass_session_execute(session_.get(), statement);
365 rc = cass_future_error_code(fut);
366 cass_future_free(fut);
367 cass_statement_free(statement);
368 if (rc != CASS_OK && rc != CASS_ERROR_SERVER_INVALID_QUERY)
371 ss <<
"nodestore: Error creating Cassandra table: " << rc
372 <<
", " << cass_error_desc(rc);
378 query <<
"SELECT * FROM " << tableName <<
" LIMIT 1";
379 statement = makeStatement(query.
str().c_str(), 0);
380 fut = cass_session_execute(session_.get(), statement);
381 rc = cass_future_error_code(fut);
382 cass_future_free(fut);
383 cass_statement_free(statement);
386 if (rc == CASS_ERROR_SERVER_INVALID_QUERY)
388 JLOG(j_.
warn()) <<
"table not here yet, sleeping 1s to "
389 "see if table creation propagates";
395 ss <<
"nodestore: Error checking for table: " << rc <<
", "
396 << cass_error_desc(rc);
402 setupSessionAndTable =
true;
405 cass_cluster_free(cluster);
407 bool setupPreparedStatements =
false;
408 while (!setupPreparedStatements)
412 query <<
"INSERT INTO " << tableName
413 <<
" (hash, object) VALUES (?, ?)";
414 CassFuture* prepare_future =
415 cass_session_prepare(session_.get(), query.
str().c_str());
418 rc = cass_future_error_code(prepare_future);
423 cass_future_free(prepare_future);
426 ss <<
"nodestore: Error preparing insert : " << rc <<
", "
427 << cass_error_desc(rc);
433 insert_ = cass_future_get_prepared(prepare_future);
438 cass_future_free(prepare_future);
441 query <<
"SELECT object FROM " << tableName <<
" WHERE hash = ?";
443 cass_session_prepare(session_.get(), query.
str().c_str());
446 rc = cass_future_error_code(prepare_future);
451 cass_future_free(prepare_future);
454 ss <<
"nodestore: Error preparing select : " << rc <<
", "
455 << cass_error_desc(rc);
461 select_ = cass_future_get_prepared(prepare_future);
466 cass_future_free(prepare_future);
467 setupPreparedStatements =
true;
471 ioThread_ =
std::thread{[
this]() { ioContext_.run(); }};
474 if (config_.exists(
"max_requests_outstanding"))
476 maxRequestsOutstanding =
477 get<int>(config_,
"max_requests_outstanding");
489 cass_prepared_free(insert_);
494 cass_prepared_free(select_);
510 JLOG(j_.
trace()) <<
"Fetching from cassandra";
511 CassStatement* statement = cass_prepared_bind(select_);
512 cass_statement_set_consistency(statement, CASS_CONSISTENCY_QUORUM);
513 CassError rc = cass_statement_bind_bytes(
514 statement, 0,
static_cast<cass_byte_t const*
>(key), keyBytes_);
517 cass_statement_free(statement);
518 JLOG(j_.
error()) <<
"Binding Cassandra fetch query: " << rc <<
", "
519 << cass_error_desc(rc);
526 fut = cass_session_execute(session_.get(), statement);
527 rc = cass_future_error_code(fut);
531 ss <<
"Cassandra fetch error";
533 ++counters_.readRetries;
534 ss <<
": " << cass_error_desc(rc);
537 }
while (rc != CASS_OK);
539 CassResult
const* res = cass_future_get_result(fut);
540 cass_statement_free(statement);
541 cass_future_free(fut);
543 CassRow
const* row = cass_result_first_row(res);
546 cass_result_free(res);
550 cass_byte_t
const* buf;
552 rc = cass_value_get_bytes(cass_row_get_column(row, 0), &buf, &bufSize);
555 cass_result_free(res);
557 JLOG(j_.
error()) <<
"Cassandra fetch result error: " << rc <<
", "
558 << cass_error_desc(rc);
559 ++counters_.readErrors;
563 nudb::detail::buffer bf;
566 DecodedBlob decoded(key, uncompressed.
first, uncompressed.
second);
567 cass_result_free(res);
569 if (!decoded.wasOk())
572 JLOG(j_.
error()) <<
"Cassandra error decoding result: " << rc
573 <<
", " << cass_error_desc(rc);
574 ++counters_.readErrors;
577 *pno = decoded.createObject();
582 canFetchBatch()
override
587 struct ReadCallbackData
589 CassandraBackend& backend;
590 const void*
const key;
598 CassandraBackend& backend,
599 const void*
const key,
608 , numFinished(numFinished)
609 , batchSize(batchSize)
613 ReadCallbackData(ReadCallbackData
const& other) =
default;
620 JLOG(j_.
trace()) <<
"Fetching " << numHashes
621 <<
" records from Cassandra";
630 cbs.
push_back(std::make_shared<ReadCallbackData>(
632 static_cast<void const*
>(hashes[i]),
639 assert(results.size() == cbs.
size());
642 cv.
wait(lck, [&numFinished, &numHashes]() {
643 return numFinished == numHashes;
646 JLOG(j_.
trace()) <<
"Fetched " << numHashes
647 <<
" records from Cassandra";
648 return {results,
ok};
652 read(ReadCallbackData& data)
654 CassStatement* statement = cass_prepared_bind(select_);
655 cass_statement_set_consistency(statement, CASS_CONSISTENCY_QUORUM);
656 CassError rc = cass_statement_bind_bytes(
657 statement, 0,
static_cast<cass_byte_t const*
>(
data.key), keyBytes_);
660 size_t batchSize =
data.batchSize;
661 if (++(
data.numFinished) == batchSize)
662 data.cv.notify_all();
663 cass_statement_free(statement);
664 JLOG(j_.
error()) <<
"Binding Cassandra fetch query: " << rc <<
", "
665 << cass_error_desc(rc);
669 CassFuture* fut = cass_session_execute(session_.get(), statement);
671 cass_statement_free(statement);
673 cass_future_set_callback(fut, readCallback,
static_cast<void*
>(&data));
674 cass_future_free(fut);
677 struct WriteCallbackData
679 CassandraBackend* backend;
684 NodeStore::EncodedBlob e;
686 std::chrono::steady_clock::time_point
begin;
689 nudb::detail::buffer bf;
692 uint32_t currentRetries = 0;
698 : backend(f), no(nobj), totalWriteRetries(retries)
708 write(WriteCallbackData& data,
bool isRetry)
719 if (!isRetry && numRequestsOutstanding_ > maxRequestsOutstanding)
721 JLOG(j_.
trace()) << __func__ <<
" : "
722 <<
"Max outstanding requests reached. "
723 <<
"Waiting for other requests to finish";
724 ++counters_.writesDelayed;
725 throttleCv_.
wait(lck, [
this]() {
726 return numRequestsOutstanding_ < maxRequestsOutstanding;
731 CassStatement* statement = cass_prepared_bind(insert_);
732 cass_statement_set_consistency(statement, CASS_CONSISTENCY_QUORUM);
733 CassError rc = cass_statement_bind_bytes(
736 static_cast<cass_byte_t const*
>(
data.e.getKey()),
740 cass_statement_free(statement);
742 ss <<
"Binding cassandra insert hash: " << rc <<
", "
743 << cass_error_desc(rc);
744 JLOG(j_.
error()) << __func__ <<
" : " << ss.
str();
745 Throw<std::runtime_error>(ss.
str());
747 rc = cass_statement_bind_bytes(
750 static_cast<cass_byte_t const*
>(
data.compressed.first),
751 data.compressed.second);
754 cass_statement_free(statement);
756 ss <<
"Binding cassandra insert object: " << rc <<
", "
757 << cass_error_desc(rc);
758 JLOG(j_.
error()) << __func__ <<
" : " << ss.
str();
759 Throw<std::runtime_error>(ss.
str());
762 CassFuture* fut = cass_session_execute(session_.get(), statement);
763 cass_statement_free(statement);
765 cass_future_set_callback(fut, writeCallback,
static_cast<void*
>(&data));
766 cass_future_free(fut);
772 JLOG(j_.
trace()) <<
"Writing to cassandra";
773 WriteCallbackData*
data =
774 new WriteCallbackData(
this, no, counters_.writeRetries);
776 ++numRequestsOutstanding_;
781 storeBatch(
Batch const& batch)
override
783 for (
auto const& no : batch)
794 syncCv_.
wait(lck, [
this]() {
return numRequestsOutstanding_ == 0; });
804 Throw<std::runtime_error>(
"not implemented");
808 getWriteLoad()
override
814 setDeletePath()
override
824 fdRequired()
const override
830 counters()
const override
836 writeCallback(CassFuture* fut,
void* cbData);
839 readCallback(CassFuture* fut,
void* cbData);
846 readCallback(CassFuture* fut,
void* cbData)
848 CassandraBackend::ReadCallbackData& requestParams =
849 *
static_cast<CassandraBackend::ReadCallbackData*
>(cbData);
851 CassError rc = cass_future_error_code(fut);
855 ++(requestParams.backend.counters_.readRetries);
856 JLOG(requestParams.backend.j_.warn())
857 <<
"Cassandra fetch error : " << rc <<
" : " << cass_error_desc(rc)
864 requestParams.backend.read(requestParams);
868 auto finish = [&requestParams]() {
869 size_t batchSize = requestParams.batchSize;
870 if (++(requestParams.numFinished) == batchSize)
871 requestParams.cv.notify_all();
873 CassResult
const* res = cass_future_get_result(fut);
875 CassRow
const* row = cass_result_first_row(res);
878 cass_result_free(res);
879 JLOG(requestParams.backend.j_.error())
880 <<
"Cassandra fetch get row error : " << rc <<
", "
881 << cass_error_desc(rc);
885 cass_byte_t
const* buf;
887 rc = cass_value_get_bytes(cass_row_get_column(row, 0), &buf, &bufSize);
890 cass_result_free(res);
891 JLOG(requestParams.backend.j_.error())
892 <<
"Cassandra fetch get bytes error : " << rc <<
", "
893 << cass_error_desc(rc);
894 ++requestParams.backend.counters_.readErrors;
898 nudb::detail::buffer bf;
902 requestParams.key, uncompressed.
first, uncompressed.
second);
903 cass_result_free(res);
905 if (!decoded.wasOk())
907 JLOG(requestParams.backend.j_.fatal())
908 <<
"Cassandra fetch error - data corruption : " << rc <<
", "
909 << cass_error_desc(rc);
910 ++requestParams.backend.counters_.readErrors;
914 requestParams.result = decoded.createObject();
923 writeCallback(CassFuture* fut,
void* cbData)
925 CassandraBackend::WriteCallbackData& requestParams =
926 *
static_cast<CassandraBackend::WriteCallbackData*
>(cbData);
927 CassandraBackend& backend = *requestParams.backend;
928 auto rc = cass_future_error_code(fut);
931 JLOG(backend.j_.error())
932 <<
"ERROR!!! Cassandra insert error: " << rc <<
", "
933 << cass_error_desc(rc) <<
", retrying ";
934 ++requestParams.totalWriteRetries;
938 ++requestParams.currentRetries;
940 std::make_shared<boost::asio::steady_timer>(
942 timer->async_wait([timer, &requestParams, &backend](
943 const boost::system::error_code& error) {
944 backend.write(requestParams,
true);
949 backend.counters_.writeDurationUs +=
950 std::chrono::duration_cast<std::chrono::microseconds>(
953 --(backend.numRequestsOutstanding_);
955 backend.throttleCv_.notify_all();
956 if (backend.numRequestsOutstanding_ == 0)
957 backend.syncCv_.notify_all();
958 delete &requestParams;
964 class CassandraFactory :
public Factory
972 ~CassandraFactory()
override
978 getName()
const override
986 Section
const& keyValues,
988 Scheduler& scheduler,
991 return std::make_unique<CassandraBackend>(keyBytes, keyValues, journal);
997 Section
const& keyValues,
999 Scheduler& scheduler,
1000 nudb::context& context,
1003 return std::make_unique<CassandraBackend>(keyBytes, keyValues, journal);
1007 static CassandraFactory cassandraFactory;