Giter Site home page Giter Site logo

zio / zio-query Goto Github PK

View Code? Open in Web Editor NEW
149.0 149.0 22.0 917 KB

Add efficient pipelining, batching, and caching to any data source

Home Page: https://zio.dev/zio-query

License: Apache License 2.0

Scala 100.00%
functional-programming query-optimization scala zio

zio-query's Introduction

ZIO Query

ZIO Query is a library for writing optimized queries to data sources in a high-level compositional style. It can add efficient pipelining, batching, and caching to any data source. ZIO Query helps us dramatically reduce load on data sources and improve performance.

Production Ready CI Badge Sonatype Releases Sonatype Snapshots javadoc ZIO Query

Introduction

Some key features of ZIO Query:

  • Batching — ZIO Query detects parts of composite queries that can be executed in parallel without changing the semantics of the query.
  • Pipelining — ZIO Query detects parts of composite queries that can be combined together for fewer individual requests to the data source.
  • Caching — ZIO Query can transparently cache read queries to minimize the cost of fetching the same item repeatedly in the scope of a query.

Compared with Fetch, ZIO Query supports response types that depend on request types, does not require higher-kinded types and implicits, supports ZIO environment and statically typed errors, and has no dependencies except for ZIO.

A ZQuery[R, E, A] is a purely functional description of an effectual query that may contain requests from one or more data sources, requires an environment R, and may fail with an E or succeed with an A.

Requests that can be performed in parallel, as expressed by zipWithPar and combinators derived from it, will automatically be batched. Requests that must be performed sequentially, as expressed by zipWith and combinators derived from it, will automatically be pipelined. This allows for aggressive data source specific optimizations. Requests can also be deduplicated and cached.

This allows for writing queries in a high level, compositional style, with confidence that they will automatically be optimized. For example, consider the following query from a user service.

Assume we have the following database access layer APIs:

def getAllUserIds: ZIO[Any, Nothing, List[Int]] = {
  // Get all user IDs e.g. SELECT id FROM users
  ZIO.succeed(???)
}

def getUserNameById(id: Int): ZIO[Any, Nothing, String] = {
  // Get user by ID e.g. SELECT name FROM users WHERE id = $id
  ZIO.succeed(???)
}

We can get their corresponding usernames from the database by the following code snippet:

val userNames = for {
  ids   <- getAllUserIds
  names <- ZIO.foreachPar(ids)(getUserNameById)
} yield names

It works, but this is not performant. It is going to query the underlying database N + 1 times, one for getAllUserIds and one for each call to getUserNameById.

In contrast, ZQuery will automatically optimize this to two queries, one for userIds and one for userNames:

lazy val getAllUserIds: ZQuery[Any, Nothing, List[Int]]    = ???
def getUserNameById(id: Int): ZQuery[Any, Nothing, String] = ???

lazy val userQuery: ZQuery[Any, Nothing, List[String]] = for {
  userIds   <- getAllUserIds
  userNames <- ZQuery.foreachPar(userIds)(getUserNameById)
} yield userNames

Installation

In order to use this library, we need to add the following line in our build.sbt file:

libraryDependencies += "dev.zio" %% "zio-query" % "0.7.0"

Example

Here is an example of using ZIO Query, which optimizes multiple database queries by batching all of them in one query:

import zio._
import zio.query._

object ZQueryExample extends ZIOAppDefault {
  case class GetUserName(id: Int) extends Request[Throwable, String]

  lazy val UserDataSource: DataSource.Batched[Any, GetUserName] =
    new DataSource.Batched[Any, GetUserName] {
      val identifier: String = "UserDataSource"

      def run(requests: Chunk[GetUserName])(implicit trace: Trace): ZIO[Any, Nothing, CompletedRequestMap] =
        requests.toList match {
          case request :: Nil =>
            val result: Task[String] = {
              // get user by ID e.g. SELECT name FROM users WHERE id = $id
              ZIO.succeed(???)
            }

            result.exit.map(CompletedRequestMap.single(request, _))

          case batch: Seq[GetUserName] =>
            val result: Task[List[(Int, String)]] = {
              // get multiple users at once e.g. SELECT id, name FROM users WHERE id IN ($ids)
              ZIO.succeed(???)
            }

            result.foldCause(
              CompletedRequestMap.failCause(requests, _),
              CompletedRequestMap.fromIterableWith(_)(kv => GetUserName(kv._1), kv => Exit.succeed(kv._2))
            )
        }
    }

  def getUserNameById(id: Int): ZQuery[Any, Throwable, String] =
    ZQuery.fromRequest(GetUserName(id))(UserDataSource)

  val query: ZQuery[Any, Throwable, List[String]] =
    for {
      ids <- ZQuery.succeed(1 to 10)
      names <- ZQuery.foreachPar(ids)(id => getUserNameById(id)).map(_.toList)
    } yield (names)

  def run = query.run.tap(usernames => Console.printLine(s"Usernames: $usernames"))
}

Resources

Documentation

Learn more on the ZIO Query homepage!

Contributing

For the general guidelines, see ZIO contributor's guide.

Code of Conduct

See the Code of Conduct

Support

Come chat with us on Badge-Discord.

License

License

zio-query's People

Contributors

adamgfraser avatar andrewpaprotsky avatar ghostdogpr avatar github-actions[bot] avatar khajavi avatar kyri-petrou avatar mijicd avatar minedeljkovic avatar nightscape avatar olegych avatar paulpdaniels avatar renovate[bot] avatar scala-steward avatar softinio avatar svranesevic avatar vigoo avatar yoohaemin avatar zio-assistant[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zio-query's Issues

Cache cannot be shared in concurrent queries

Simple repro:

import zio.*
import zio.query.*

object Main extends ZIOAppDefault {
  case object Req extends Request[Nothing, Unit]

  val ds: DataSource[Any, Req.type] =
    DataSource.fromFunctionZIO("MyDatasource")((_: Req.type) => ZIO.unit.delay(50.millis))

  def run =
    for {
      cache <- Cache.empty
      _     <- ZIO.foreachParDiscard(1 to 100)(_ => ZQuery.fromRequest(Req)(ds).runCache(cache))
    } yield ()
}

Throws:

Exception in thread "zio-fiber-5" zio.query.QueryFailure: Data source MyDatasource did not complete request Req.

I understand that this is not how the cache is meant to be shared between concurrent queries, but there are some cases where we can't run the queries in parallel via ZQuery.foreachPar. One use-case where we need to run queries in parallel while sharing the cache is when we process queries in a ZStream. In Caliban, we stream GraphQL @defer fields back to the client. The queries in the stream need to share the same cache otherwise we lose the ability to deduplicate requests to the same DataSource.

As a workaround, we are forced to process each effect in the ZStream sequentially (see ghostdogpr/caliban#1981 and attached issue). However, this doesn't perform well when we have multiple deferred responses.

Potential solution(?)

One potential solution to the issue would be for the Cache to use Promises instead of Refs. I did an experiment (see 034e5e5), and the approach seems to work.

However, I'm not sure whether there might be edge-cases where we'd be causing deadlocks, as well as what the performance implication of using Promises over Refs is. Any thoughts? Is this something that could potentially be of use?

Merge with zio-cache

zio-cache and zio-query are a match made in heaven. With zio-query, you get the pipelining and batching but the cache is recreated on every run and has no eviction strategy. With zio-cache, you get all these good things but there is no batching.

Is there a world where we could give run a Cache[k, e, v] that would cross execution boundaries? It would be especially useful in streaming pipelines where you want to mapM each element by pulling data from another service.

Implement some missing ZIO combinators

Combinators we are used to in ZIO don't yet exist in ZQuery.

ZQuery.access
ZQuery.accessM

There are definitely more.

This is a ticket to these specific combinators and perhaps some more.

Bad memory consumption

I bumped into a problem when zquery consumes a lot of memory and the execution does not finish.

I am using the zquery in the scenario like with 3 http calls:

  • 1 call gets all Users
  • 1 call gets all Addresses
  • 1 call gets all Payment methods
    Each of the requests succeeds separately.

The DataSource's are created for Addresses and Payment methods. For each user, I am trying to get the Address and a Payment method.
For a small number of entities the code works, the big amount of entities causes several gigabytes of memory to be consumed and the program never finishes.

There is a simple code to reproduce the problem without real http calls but a mocked http response https://github.com/melgenek/zquery-playground/blob/master/src/main/scala/Main.scala
The heap dump shows a significant amount of zio.Chunks, though I did not dig deep into the dump to see the whole picture.

Misleading comment about Fetch

Hi, Fetch author here, I no longer mantain the project but I have noticed that it is mentioned in the docs.

Compared with Fetch, ZIO Query supports pipelining, supports response types that depend on request types, does not require higher-kinded types and implicits, supports ZIO environment and statically typed errors, and has no dependencies except for ZIO.

While most of these are true, Fetch does support pipelining although I called it "batching" (grouping requests to the same data source in a batch). I'm not a native speaker so your language might be more accurate but the statement is false, I hope you correct it.

Congrats on this project btw, using a powerful effect type like ZIO makes the library a lot more ergonomic. I considered making a version of Fetch with ZIO but I no longer do Scala, I'm glad someone did!

`ZQuery.fromZIO` never applies DataSourceAspects

This bit me today when implementing a tracing wrapper for Caliban.

It seems like DataSourceAspects are never executed for ZQueries created via ZQuery.fromZIO, scastie here.

As a caller, this feels a inconsistent since I don't really have a way of knowing how the ZQuery was constructed, and it also means that currently isn't really a way to implement an aspect for ZQueries in the general sense.

I think it's due to the combination of how ZQuery.fromZIO is implemented along with the behaviour of Result.done.

I wonder if an alternative implementation of ZQuery.fromZIO could be to still treat it as a Result.Blocked whose blocking requests are BlockingRequests.empty and have to continuation be the ZIO passed in? I guess it will incur some slight overhead but behave consistently.

Stack Overflow Error

First of all let me say that this project is awesome! I'm using it via caliban and I'm impressed.

I have implemented a graphql server and everything is implemented using ZQuery effects.
I deploy the application via docker (tried the graalvm base image as well as openjdk - version 11)
and I randomly run into:

        at zio.query.internal.BlockedRequests.provideSomeEnvironment(BlockedRequests.scala:51)
        at zio.query.internal.BlockedRequests.provideSomeEnvironment$(BlockedRequests.scala:49)
        at zio.query.internal.BlockedRequests$Both.provideSomeEnvironment(BlockedRequests.scala:96)
        at zio.query.internal.BlockedRequests.provideSomeEnvironment(BlockedRequests.scala:52)
        at zio.query.internal.BlockedRequests.provideSomeEnvironment$(BlockedRequests.scala:49)

I can't replicate the bug in intelliJ however...

`ClassCastException` due to `Request` covariance

Request's being covariant in both parameters allows the handling DataSource to return a less specifically typed result, which leads to a ClassCastException (the same applies to the error channel).

Example:

  private final case class SubtypeRequest() extends Request[Nothing, Int]

  private val ds: DataSource[Any, SubtypeRequest] =
    DataSource.fromFunction[SubtypeRequest, Any]("covariance-test")(_ => "a string")

  override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = {
    ZQuery
      .fromRequest(SubtypeRequest())(ds)
      .run
      .flatMap((i: Int) => ZIO.logInfo(s"Result: $i")) // a defect here
  }

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: Cannot find preset's package (github>whitesource/merge-confidence:beta)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.