The Boring Database

Databases are tools, and the best tools operate in the background without drawing any attention.
We decrease friction, simplify the experience, and take care of the hard work.

Enjoy the tranquility of development without any trade-offs
and focus on what really matters.

JOIN OVER 1000 CUSTOMERS WORLDWIDE

Toyota
Verizon
Medicaid
Capgemini
Rakuten Kobo
JetBrains
Particular
View.DO
Starnet
Amberwood Trading
Sparkling Logic
Incomm Incentives
Simple Store
Serverflex
Toyota
Verizon
Medicaid
Capgemini
Rakuten Kobo
JetBrains
Particular
View.DO
Starnet
Amberwood Trading
Sparkling Logic
Incomm Incentives
Simple Store
Serverflex

What they say about us

As a document database it remains true to the core principles of these type of storage mechanisms. Somehow it managed to combine the best of relational databases with that of document databases.


Hadi Hariri VP of Developer Advocacy

RavenDB is so smooth, I don’t notice it.


Jeremy Holt, CEO in the Amberwood Trading

Considering how huge the amount of data sent back and forth in a single quote document is, the fact that RavenDB can store the entire document and continue to perform well is really impressive.


Peter Tiedeman, Senior Principal Software Developer in the Configit

Easy implementation, nice UI, cloud options


Developer in the Miscellaneous Industry

Easy implementation even for people with little experience with NoSQL


Developer in the Miscellaneous Industry

RavenDB is a convenient, no hassle, noSql implementation that is blazing fast.


Senior Consultant II in the Services Industry

Very quick phase for get up and running in prod (setup, learning, working).


Business Innovation Director in the Business Innovation Director

A Multi Model Database Ripe for Building Microservices.


Software Quality Tester in the Finance Industry

Customer focused with very minimum response time support.


Procurement Specialist in the Services Industry

A performant data storage solution that scales as we need it


Director of Software Engineering in the Services Industry

Best in class NO SQL database for embedded IOT


Line Manager Software & Embedded Systems in the Manufacturing Industry

Feature distributed-time-series

Use time-series in your next finance, IoT, or healthcare application, where chronological order of data points is crucial for trend analysis. anomaly detection, and predictive modeling.

Take advantage of :

  • incremental time-series which avoids conflicts and a cost of synchronization for a real-time updates,
  • built-in RQL support for data extraction,
  • indexing capabilities for advanced querying scenarios,
  • rollup and retention for data aggregation and cleanup
from Companies  
    where id() = 'companies/55-A'
    select timeseries(
        from StockPrices 
        between '2020-01-01' and '2020-06-30' 
        group by '1 month'
        select min(), max()
    ) as StockPrices
var stocks =
    session.TimeSeriesFor("companies/55-A")
        .Get(from
             : new DateTime(2020, 1, 1), to
             : new DateTime(2020, 6, 30))
        .GroupBy(g => g.Timestamp.Month)
        .Select(g => new {Month = g.Key, Min = g.Min(x => x.Value.Close),
                          Max = g.Max(x => x.Value.Close)})
        .ToList();
from itertools import groupby

with self.store.open_session() as session:
    stocks = session.typed_time_series_for(StockPrice, "users/karmel").get()
    grouped_stocks = groupby(stocks, key=lambda x: x.timestamp.month)

    def get_min_max(group):
        values = [x for x in group]
        close_values = [x.value.close for x in values]
        return {
            "Month": values[0].timestamp.month,
            "Min": min(close_values),
            "Max": max(close_values),
        }

    stocks = [get_min_max(group) for _, group in grouped_stocks]
String query = "from Companies  " +
  "    where id() = 'companies/55-A' " +
  "    select timeseries( " +
  "        from StockPrices " +
  "        between '2020-01-01' and '2020-06-30' " +
  "        group by '1 month' " +
  "        select min(), max()" +
  "    ) as StockPrices";

IRawDocumentQuery < StockPrice > stocks = session.advanced().rawQuery(StockPrice.class, query);
$query = "from Companies  " .
	"    where id() = 'companies/55-A' " .
	"    select timeseries( " .
	"        from StockPrices " .
	"        between '2020-01-01' and '2020-06-30' " .
	"        group by '1 month' " .
	"        select min(), max()" .
	"    ) as StockPrices";

$stocks = $session->advanced()->rawQuery(StockPrice::class, $query);
const query = "from Companies  " +
	"    where id() = 'companies/55-A' " +
	"    select timeseries( " +
	"        from StockPrices " +
	"        between '2020-01-01' and '2020-06-30' " +
	"        group by '1 month' " +
	"        select min(), max()" +
	"    ) as StockPrices";

const stocks: StockPrice[] = await session.advanced.rawQuery(query, StockPrice).all();

Feature full-text-search

With no additional lines of code, search for language-aware text values, execute highlighting, retrieve suggestions, and set up value boosting in single, multiple or complex fields.

Improve your search capabilities with our brand new tailored Corax solution to push the performance to the limits or take advantage of the industry battle-tested Lucene search engine.

If this is not enough, build your own custom analyzers or custom sorters and use them in your static indexes to achieve desired search outcome.

// Single term 
from "Employees"
where search(Notes, "University")

// Multiple term 
from "Employees" 
where search(Notes, "University Sales Japanese")

// Complex objects with multiple terms 
from "Companies" where search(Address, "USA London")

// Nested values with boosting 
from "Companies"
where boost(search(Address.City, "Po*"), 100) 
   or boost(search(Address.Country, "Po*"), 1000)
// Single term
List employees = session.Query()
    .Search(x => x.Notes, "University")
    .ToList();

// Multiple term
List employees = session.Query()
    .Search(x => x.Notes, "University Sales Japanese")
    .ToList();

// Complex objects with multiple terms
List companies = session.Query()
    .Search(x => x.Address, "USA London")
    .ToList();
# Single term
employees1 = list(session.query_collection("Employees").search("Notes", "University"))

# Multiple term
employees2 = list(
    session.query_collection("Employees").search("Notes", "University Sales Japanese")
)

# Complex objects with multiple terms
companies1 = list(session.query_collection("Companies").search("Address", "USA London"))

# Nested values with boosting
companies2 = list(
    session.query_collection("Companies")
    .search("Address.City", "Po*")
    .boost(100)
    .or_else()
    .search("Address.Country", "Po*")
    .boost(1000)
)
// Single term
List employees = session.query(Employee.class)
		.search("Notes", "University")
		.toList();

// Multiple term
List employees = session.query(Employee.class)
		.search("Notes", "University Sales Japanese")
		.toList();

// Complex objects with multiple terms
List companies = session.query(Company.class)
		.search("Address", "USA London")
		.toList();
// Single term
$employees = $session->query(Employee::class)
    ->search("Notes", "University")
    ->toList();

// Multiple term
$employees = $session->query(Employee::class)
	->search("Notes", "University Sales Japanese")
    ->toList();

// Complex objects with multiple terms
$companies = $session->query(Company::class)
	->search("Address", "USA London")
    ->toList();
// Single term
const employees: Employee[] = await session.query(Employee)
	.search("Notes", "University")
	.all();

// Multiple term
const employees: Employee[] = await session.query(Employee)
	.search("Notes", "University Sales Japanese")
	.all();

// Complex objects with multiple terms
const employees: Employee[] = await session.query(Employee)
	.search("Address", "USA London")
	.all();

Feature distributed-counters

Creating a voting or pooling system can be challenging due to the synchronization and aggregation issues involved. We are simplifying this process to just a few lines of code and taking the heavy burden on ourselves.

With counters in a distributed environment, you can consume a tremendous amount of data in a short time span and still enjoy the final results in a swift manner.

// Vote
var counters = session.CountersFor("candidate/1");
counters.Increment("Votes");
session.SaveChanges();

// Check results
var counters = session.CountersFor("candidate/1");
var votes = counters.Get("Votes");
# Vote
counters = session.counters_for("candidate/1")
counters.increment("Votes")
session.save_changes()

# Check results
counters = session.counters_for("candidate/1")
votes = counters.get("Votes")
// Vote
ISessionDocumentCounters counters = session.countersFor("candidate/1");
counters.increment("Votes");
session.saveChanges();

// Check results
ISessionDocumentCounters counters = session.countersFor("candidate/1");
Long votes = counters.get("Votes");
// Vote
$counters = $session->countersFor("candidate/1");
$counters->increment("Votes");
$session->saveChanges();

// Check results
$counters = $session->countersFor("candidate/1");
$votes = $counters->get("Votes");
// Vote
const counters = session.countersFor("candidate/1");
counters.increment("Votes");
await session.saveChanges();

// Check results
const counters = session.countersFor("candidate/1");
const votes: number = await counters.get("Votes");

Feature self-optimizing-queries

Full Table Scan is the #1 enemy for each backend developer. With us, you cannot go wrong. There is no possibility to perform a query without an index.

But it doesn’t mean that you need to create one for each query your application does! We are reactive to what you do. We learn the behavior of your application and create auto indexes for you. Do not worry, they are automatically merged together to create a minimal set of indexes that will cover all your application needs.

For us, being Boring means that you can sleep at night instead of waiting for the next Full Table Scan nightmare to happen.

// New index will be created
from Companies
where Name = "Island Trading"

// Previous index will get merged with new one
from Companies
where Address.Country = "UK"

// I still have one index, but all three queries can be handled by it
// Amazing!
from Companies
where Phone = "(171) 555-0297"
# New index will be created
companies1 = list(
    session.query(object_type=Company).where_equals("name", "Island Trading")
)

# Previous index will get merged with new one
companies2 = list(
    session.query(object_type=Company).where_equals("address.country", "UK")
)

# I still have one index, but all three queries can be handled by it
# Amazing !
companies3 = list(
    session.query(object_type=Company).where_equals("phone", "(171) 555-0297")
)
// New index will be created
List < Company > companies1 = session.query(Company.class)
  .whereEquals("Name", "Island Trading")
  .toList();

// Previous index will get merged with new one
List < Company > companies2 = session.query(Company.class)
  .whereEquals("Address.Country", "UK")
  .toList();

// I still have one index, but all three queries can be handled by it
// Amazing!
List < Company > companies3 = session.query(Company.class)
  .whereEquals("Phone", "(171) 555-0297")
  .toList();
// New index will be created
$companies1 = $session->query(Company::class)
	->whereEquals("Name", "Island Trading")
    ->toList();

// Previous index will get merged with new one
$companies2 = $session->query(Company::class)
    ->whereEquals("Address.Country", "UK")
    ->toList();

// I still have one index, but all three queries can be handled by it
// Amazing!
$companies3 = $session->query(Company::class)
    ->whereEquals("Phone", "(171) 555-0297")
    ->toList();
const companies1: Company[] = await session.query(Company)
	.whereEquals("Name", "Island Trading")
	.all();

// Previous index will get merged with new one
const companies2: Company[] = await session.query(Company)
	.whereEquals("Address.Country", "UK")
	.all();

// I still have one index, but all three queries can be handled by it
// Amazing!
const companies3: Company[] = await session.query(Company)
	.whereEquals("Phone", "(171) 555-0297")
	.all();

Feature spatial-search-and-facets

Are you looking for a place to eat, or maybe a nearby taxi? Or are you just browsing through some online stores? Our built-in Spatial and Faceted Search support can help you achieve this, with a vast set of supported standards like WKT for Spatial or range aggregations for Facets. All of that is integrated into our RQL querying language.

// Find all Employees in a 20km circle from 47.623473, -122.3060097 coordinates
from Employees
where spatial.within(
    spatial.point(Address.Location.Latitude, Address.Location.Longitude),
    spatial.circle(20, 47.623473, -122.3060097)

    // Return facets aggregated by
    // - Brand
    // - Five Ranges
    from index "Cameras/ByFeatures"
    select facet(Brand,
      sum(UnitsInStock), avg(Price), min(Price), max(MegaPixels)),
    facet(Price < 200.0,
      Price >= 200.0 and Price < 400.0,
      Price >= 400.0 and Price < 600.0,
      Price >= 600.0 and Price < 800.0,
      Price >= 800.0,
      sum(UnitsInStock), avg(Price), min(Price), max(MegaPixels))
#  Find all Employees in a 20km circle from 47.623473, -122.3060097 coordinates
	employees = list(
		session.query(object_type=Employee).spatial(
			PointField("address.location.latitude", "address.location.longitude"),
			lambda f: f.within_radius(20, 47.623473, -122.3060097),
		)
	)

	# Return facets aggregated by
	# - Brand
	# - Five Ranges

	range = RangeBuilder.for_path("price")
	product_aggregations = (
		session.query_index("Cameras/ByFeatures")
		.aggregate_by(
			lambda f: f.by_field("brand")
			.sum_on("units_in_stock")
			.average_on("price")
			.min_on("price")
			.max_on("mega_pixels")
		)
		.and_aggregate_by(
			lambda f: f.by_ranges(
				range.is_less_than(200),
				range.is_greater_than_or_equal_to(200).is_less_than(400),
				range.is_greater_than_or_equal_to(400).is_less_than(600),
				range.is_greater_than_or_equal_to(600).is_less_than(800),
				range.is_greater_than_or_equal_to(800),
			)
			.sum_on("units_in_stock")
			.average_on("price")
			.min_on("price")
			.max_on("mega_pixels")
		)
		.execute()
	)
// Find all Employees in a 20km circle from 47.623473, -122.3060097 coordinates
List < Employee > query = session.query(Employee.class)
  .spatial(
    new PointField("Address.Location.Latitude", "Address.Location.Longitude"),
    f -> f.withinRadius(20, 47.623473, -122.3060097))
  .toList();

// Return facets aggregated by
// - Brand
// - Five Ranges
RangeBuilder < Integer > range = new RangeBuilder < > ("price");
Map < String, FacetResult > results = session.query(Object.class, Query.index("Cameras/ByFeatures"))
  .aggregateBy(f -> f
    .byField("brand")
    .sumOn("units_in_stock")
    .averageOn("price")
    .minOn("price")
    .maxOn("mega_pixels"))
  .andAggregateBy(f -> f.byRanges(
      range.isLessThan(200),
      range.isGreaterThanOrEqualTo(200).isLessThan(400),
      range.isGreaterThanOrEqualTo(400).isLessThan(600),
      range.isGreaterThanOrEqualTo(600).isLessThan(800),
      range.isGreaterThanOrEqualTo(800)
    )
    .sumOn("units_in_stock")
    .averageOn("price")
    .minOn("price")
    .maxOn("mega_pixels"))
  .execute();
// Find all Employees in a 20km circle from 47.623473, -122.3060097 coordinates
$query = $session->query(Employee::class)
	->spatial(
        new PointField("Address.Location.Latitude", "Address.Location.Longitude"),
        function($f) { return $f->withinRadius(20, 47.623473, -122.3060097); }
    )
	->toList();

// Return facets aggregated by
// - Brand
// - Five Ranges
/** @var array  $results */
$range = new RangeBuilder("price");
$results = $session->query(null, Query::index("Cameras/ByFeatures"))
	->aggregateBy(function($f) {
        return $f
            ->byField("brand")
            ->sumOn("units_in_stock")
            ->averageOn("price")
            ->minOn("price")
            ->maxOn("mega_pixels");
    })
    ->spatial("LocationCoordinates", )
    ->andAggregateBy(function($f) use ($range) { return
            $f->byRanges(
                $range->isLessThan(200),
                $range->isGreaterThanOrEqualTo(200)->isLessThan(400),
                $range->isGreaterThanOrEqualTo(400)->isLessThan(600),
                $range->isGreaterThanOrEqualTo(600)->isLessThan(800),
                $range->isGreaterThanOrEqualTo(800)
            )
            ->sumOn("units_in_stock")
            ->averageOn("price")
            ->minOn("price")
            ->maxOn("mega_pixels");
    })
	->execute();
// Find all Employees in a 20km circle from 47.623473, -122.3060097 coordinates
const query: Employee[] = await session.query(Employee)
	.spatial(new PointField("Address.Location.Latitude", "Address.Location.Longitude"),
		f => f.withinRadius(20, 47.623473, -122.3060097))
	.all();

// Return facets aggregated by
// - Brand
// - Five Ranges

const range = new RangeBuilder("price");
const results: Map < string, FacetResult > = await session.query({
		indexName: "Cameras/ByFeatures"
	})
	.aggregateBy(f => f
		.byField("brand")
		.sumOn("units_in_stock")
		.averageOn("price")
		.minOn("price")
		.maxOn("mega_pixels"))
	.andAggregateBy(f => f
		.byRanges(
			range.isLessThan(200),
			range.isGreaterThanOrEqualTo(200).isLessThan(400),
			range.isGreaterThanOrEqualTo(400).isLessThan(600),
			range.isGreaterThanOrEqualTo(600).isLessThan(800),
			range.isGreaterThanOrEqualTo(800)
		)
		.sumOn("units_in_stock")
		.averageOn("price")
		.minOn("price")
		.maxOn("mega_pixels"))
	.execute();

Feature data-subscriptions

Writing a piece of code that pulls data every now and then in a distributed environment is not only inefficient but also a cumbersome and non-trivial task. With our Data Subscriptions, we take the heavy lifting on ourselves and guarantee that every single item will be processed without any additional infrastructure.

Why waste time maintaining the code, when you can just subscribe to an RQL-based subscription and focus on the business code.

// Create
var name = await DocumentStore.Subscriptions.CreateAsync();

// Open
var worker = DocumentStore.Subscriptions.GetSubscriptionWorker(name);

// Subscribe
_ = worker.Run(batch =>
{
    // Issue an Invoice or Send an Email
});
# Create
    name = self.store.subscriptions.create_for_class(Order)

    # Open
    worker = self.store.subscriptions.get_subscription_worker(SubscriptionWorkerOptions(name), Order)

    # Subscribe
    def _do_work(batch: SubscriptionBatch):
        with batch.open_session() as session:
            for item in batch.items:
                order = item.result
                    
                # Issue an Invoice, Send an Email
                    
                order.shipped_at = datetime.now()
                    
                session.save_changes()

    worker.run(_do_work)
// Create
SubscriptionCreationOptions subscriptionCreationOptions = new SubscriptionCreationOptions();
subscriptionCreationOptions.setQuery("from Orders where ShippedAt is null");
String name = documentStore.subscriptions().create(subscriptionCreationOptions);

// Open
SubscriptionWorker < Order > worker = documentStore.subscriptions().getSubscriptionWorker(Order.class, name);

// Subscribe
worker.run(batch -> {
  try (IDocumentSession session = batch.openSession()) {
    for (SubscriptionBatchBase.Item < Order > item: batch.getItems()) {
      Order result = item.getResult();

      // Issue an Invoice, Send an Email

      result.setShippedAt(new Date());
      session.saveChanges();
    }
  }
});

// Nested values with boosting
List < Company > companies2 = session.query(Company.class, Query.collection("Companies"))
  .search("Address.City", "Po*")
  .boost(100)
  .orElse()
  .search("Address.Country", "Po*")
  .boost(1000)
  .toList();
// Create
        const subscriptionCreationOptions: SubscriptionCreationOptions = {
            query: "from Orders where ShippedAt is null"
        };
        const name = await documentStore.subscriptions.create(subscriptionCreationOptions);

        // Open
        const worker = documentStore.subscriptions.getSubscriptionWorker({
            documentType: Order,
            subscriptionName: name
        });

        // Subscribe
        worker.on("batch", async (batch, callback) => {
            const session = batch.openSession();
            try {
                for (const item: Item of batch.items) {
                    const result: Order = item.result;

                    // Issue an Invoice, Send an Email

                    result.shippedAt = new Date();
                    await session.saveChanges();
                }
            } finally {
                session.dispose();
            }
            callback();
        });

        // Nested values with boosting
        const companies2: Company[] = await session.query({
            collection: "Companies",
            documentType: Company
        })
            .search("Address.City", "Po*")
            .boost(100)
            .orElse()
            .search("Address.Country", "Po*")
            .boost(1_000)
            .all();

Instant Database Cluster with just a Click

Quick and Transparent Setup

Available on 6 continents, 100+ regions across 
Azure, AWS, and Google Cloud

Deploy a ready-to-go system in a few minutes
and scale it as your business grows

Easy Product Management

Enjoy automatic instance setup, maintenance and upgrades. All of this without any effort from your side and with 24/7 support.

Flexible

Scale your instances as your business grows, optimize the costs by taking advantage of our long-term contracts.

Automatic Backups & Security

With high availability, automatic failover, data backups and X.509 authentication and authorization be sure that your data is safe and sound on every possible level.

“After years of fixing mistakes that others did in SQL, developing a world-renown application to find common ones, I realized that the problem is not with people, but with technology. That day I started RavenDB.”
Oren Eini
CEO & Founder
“Selecting a front-end and back-end technology can be done within few hours, but selecting a database takes weeks and dealing with it takes up to 50% of the development time. Sounds like a waste, there must be another way.”
Paweł Pekról
CTO

Nothing beats a hands-on experience

Join our path and feel the difference

Try RavenDB