April 22, 2024

EdgeDB Cloud Free Tier & how we stack up vs. PlanetScale, Supabase, Neon

We're thrilled to announce the general availability of the EdgeDB Cloud Free Tier. This marks a significant step forward in making EdgeDB more accessible and competitive. But what really sets EdgeDB Cloud apart? Let's dive into the details and a bit of benchmarking!

Simply put, EdgeDB Cloud is a place where you host your EdgeDB instances with minimum fuss. EdgeDB Cloud (the service) and EdgeDB (the software) have been co-developed closely to deliver great performance and enhanced developer experience.

Here’s what makes EdgeDB Cloud stand out:

The CLI: Install with a single curl command, or via npx edgedb, and manage both EdgeDB Cloud and local EdgeDB instances effortlessly.

The Web UI: The Cloud console lets you control all aspects of your EdgeDB Cloud database: adding and editing data, visualizing schemas, a command-line terminal, and much more.

Cloud Dashboard
Data Editor
Web REPL
Schema Browser

Fully Integrated Auth: We support multiple OAuth providers and an email/password authentication flow, with many more features set to be announced this week. This feature is tightly integrated with EdgeDB itself and its Access Policies framework. Completely open-source and free, and the number of active users it supports is unlimited.

Simple and secure configuration: Cloud instance access is authorized by granular secret keys like any modern API should be. Arcane connection strings with shared credentials are a thing of the past.

Great performance: we have benchmarks to show!

To mark the availability of EdgeDB Cloud Free Tier we extended our IMDBench database benchmark suite with the ability to run against popular database cloud services. Previously we used simulated network latency to show the real-world impact of database interface (in)efficiency, but today we are able to do this using real services.

As a reminder, IMDBench models a basic IMDB-like website, featuring endpoints for accessing detailed movie information, actors, directors, reviews, and more. This setup provides a good framework for meaningful performance evaluation using slightly non-trivial queries.

In this run we are comparing EdgeDB to Prisma and Drizzle — two very popular JavaScript ORM libraries running on popular cloud databases with different levels of geographical proximity between the application and the database.

Want to look at the kind of queries we're running?

Consider this IMDBench query example: fetching data about a movie by its ID. This includes retrieving the movie’s title, image, description, calculating its average rating, and fetching ordered lists of its directors, cast, and user reviews.

Here are a few examples on what this might look like in EdgeQL, Prisma, and Drizzle. Notice how remarkably close EdgeQL’s syntax is to its TypeScript query builder version.

EdgeQL
EdgeQL in TypeScript
Prisma
Drizzle
Copy
select Movie {
  id,
  image,
  title,
  year,
  description,
  avg_rating := math::mean(.reviews.score),

  directors: {
    id,
    full_name,
    image,
  }
  order by Movie.directors@list_order empty last
    then Movie.directors.last_name,

  cast: {
    id,
    full_name,
    image,
  }
  order by Movie.cast@list_order empty last
    then Movie.cast.last_name,

  reviews := (
    select Movie.<movie[is Review] {
      id,
      body,
      rating,
      author: {
        id,
        name,
        image,
      }
    }
    order by .creation_time desc
  ),
}
filter .id = <uuid>$id
Copy
e.params({id: e.uuid}, ($) =>
  e.select(e.Movie, (movie) => ({
    id: true,
    image: true,
    title: true,
    year: true,
    description: true,
    avg_rating: e.math.mean(movie.reviews.score),
    directors: {
      id: true,
      full_name: true,
      image: true,
      order_by: [
        {
          expression: movie.directors["@list_order"],
          empty: e.EMPTY_LAST,
        },
        movie.directors.last_name,
      ],
    },
    cast: {
      id: true,
      full_name: true,
      image: true,
      order_by: [
        {
          expression: movie.cast["@list_order"],
          empty: e.EMPTY_LAST,
        },
        movie.cast.last_name,
      ],
    },
    reviews: e.select(movie[".<movie[IS Review]"], (review) => ({
      id: true,
      body: true,
      rating: true,
      author: {
        id: true,
        name: true,
        image: true,
      },
      order_by: {
        expression: review.creation_time,
        direction: e.DESC
      }
    })),
    filter_single: {id: $.id}
  }))
);
Copy
async movieDetails(id) {
  const result = await client.$transaction([
    client.movies.findUnique({
      where: {
        id: id,
      },
      select: {
        id: true,
        image: true,
        title: true,
        year: true,
        description: true,

        directors: {
          select: {
            person: {
              select: {
                id: true,
                first_name: true,
                middle_name: true,
                last_name: true,
                image: true,
              },
            },
          },
          orderBy: [
            {
              list_order: 'asc',
            },
            {
              person: {
                last_name: 'asc',
              },
            },
          ],
        },
        cast: {
          select: {
            person: {
              select: {
                id: true,
                first_name: true,
                middle_name: true,
                last_name: true,
                image: true,
              },
            },
          },
          orderBy: [
            {
              list_order: 'asc',
            },
            {
              person: {
                last_name: 'asc',
              },
            },
          ],
        },

        reviews: {
          orderBy: {
            creation_time: 'desc',
          },
          select: {
            id: true,
            body: true,
            rating: true,
            author: {
              select: {
                id: true,
                name: true,
                image: true,
              },
            },
          },
        },
      },
    }),

    client.reviews.aggregate({
      _avg: {
        rating: true,
      },
      where: {
        movie: {
          id: id,
        },
      },
    }),
  ]);

  result[0].avg_rating = result[1]._avg.rating;
  // move the "person" object one level closer
  // to "directors" and "cast"
  for (let fname of ['directors', 'cast']) {
    result[0][fname] = result[0][fname].map((rel) => {
      return {
        id: rel.person.id,
        full_name: `${rel.person.first_name} ${rel.person.last_name}`,
        image: rel.person.image,
      };
    });
  }

  return JSON.stringify(result[0]);
}
Copy
const preparedAvgRating = db
  .select({
    id: schema.reviews.movieId,
    avgRating: avg(schema.reviews.rating).mapWith(Number),
  })
  .from(schema.reviews)
  .groupBy(schema.reviews.movieId)
  .where(eq(schema.reviews.movieId, sql`any(${ids})`))
  .prepare("avgRating");

const preparedMovieDetails = db.query.movies
  .findFirst({
    columns: {
      id: true,
      image: true,
      title: true,
      year: true,
      description: true,
    },
    extras: {
      avg_rating: sql`${sql.placeholder("avgRating")}`.as("avg_rating"),
    },
    with: {
      directors: {
        columns: {},
        with: {
          person: {
            columns: {
              id: true,
              image: true,
            },
            extras: {
              full_name: fullName.as("full_name"),
            },
          },
        },
        orderBy: [
          // unsupported Drizzle features as of writing
          asc(schema.directors.listOrder), // .nullsLast()
          // asc(schema.persons.lastName),
        ],
      },
      cast: {
        columns: {},
        with: {
          person: {
            columns: {
              id: true,
              image: true,
            },
            extras: {
              full_name: fullName.as("full_name"),
            },
          },
        },
        orderBy: [
          // unsupported Drizzle features as of writing
          asc(schema.directors.listOrder), // .nullsLast()
          // asc(schema.persons.lastName),
        ],
      },
      reviews: {
        columns: {
          id: true,
          body: true,
          rating: true,
        },
        with: {
          author: {
            columns: {
              id: true,
              name: true,
              image: true,
            },
          },
        },
        orderBy: [desc(schema.reviews.creationTime)],
      },
    },
    where: eq(schema.movies.id, sql.placeholder("id")),
  })
  .prepare("movieDetails");


async movieDetails(id: number): Promise<any> {
  // `extras` doesn't support aggregations yet
  const rs = await preparedAvgRating.execute({
    ids: `[${id}]`,
  });
  let avgRating: number = 0;
  if (rs.length > 0) {
    avgRating = rs[0].avgRating;
  }
  return await preparedMovieDetails.execute({ avgRating, id });
}

Here’s what we’re testing here:

  • EdgeDB Cloud: Basic “Paid-tier” plan at $39/month, offering 1 compute unit with 1/4 vCPU and 2 GiB RAM.

  • Supabase: “Pro-tier” plan at $25/month plus “Small” add-on for $15/month, equaling to a shared 2-core ARM vCPU with 2GB RAM.

  • Neon: “Launch” plan, with auto-suspend disabled and the autoscaler min/max set to 0.5 compute units. This plan includes 1/2 vCPU and 2GiB RAM. The total cost for this setup, boldly assuming the database is used every hour of the month, would be approximately $52/month (calculated as $19 + (24 hours * 30 days - 300 included hours) * $0.16 * 0.5 for half a compute unit).

  • PlanetScale: “PS-10” plan at $39/month, providing 1/8 vCPU and 1GiB RAM. Though PlanetScale runs MySQL, we decided to include it as a comparison point due to its popularity.

The client application is running on a c7a.xlarge AWS EC2 instance (4vCPU, 8GiB RAM).

The database instance configurations are within the same price range and have comparable compute resources. Databases and clients were deployed to the same cloud region (AWS us-east-1 or us-east-2 in most cases).

Running atop PostgreSQL, EdgeDB handles a range of tasks—from converting EdgeQL to SQL, managing the EdgeDB Auth API, and rendering its user interface, to balancing client connections. We’ve invested significant effort to fine-tune both EdgeDB and PostgreSQL to ensure optimal performance. Pay specific attention to the latency chart: EdgeDB completed 99% queries in 2.9ms, or 6x quicker than than the nearest contender — Drizzle querying Neon.

This is the best case scenario where latency between the app and the database is lowest one data center is lowest, typically under one millisecond. The results are even more dramatic if the app is further away from the database. Below are full benchmark reports for some of those scenarios:

Performance benchmarks are straightforward to interpret—they clearly show what’s faster and what’s slower under specific tests or conditions. However, what benchmarks often fail to convey is how convenient it is to use a tool, or, in other words, whether the developer experience is satisfying.

While achieving high performance with raw SQL or ORMs is certainly possible, it isn’t effortless and inevitably complicates things — leading to more unreadable and unmaintainable code, as well as requiring various hacks.

At EdgeDB, our goal is not merely to create the fastest database at the expense of usability. On the contrary, we designed EdgeDB from the ground up with the principles of robustness, type-safety, and composability in querying capabilities. The great performance we observe is a direct consequence of these foundational choices.

The launch of our free tier is more than just an offer — it’s a commitment to providing developers with the best possible tools to create and scale applications effortlessly.

We envision EdgeDB Cloud to be a powerful data layer that gives you a state-of-the-art database experience along with application-level services like integrated Auth, AI (more on this soon!), and storage.

Most importantly, we believe that EdgeDB Cloud has to integrate deeply with the tools you use daily, like GitHub, Vercel, and many others, to ensure smooth workflow. Stay tuned for more announcements this week as we have a lot of new exciting things coming! ❤️

In the meantime, try it out by creating your first EdgeDB Cloud instance now!