Git Product home page Git Product logo

krystal's Introduction

The Krystal Project

Contribution

All java code should be formatted using the google-java-formatter.

Latest Releases

Clojars Project

Clojars Project

Clojars Project

Clojars Project

Clojars Project

Clojars Project

///// This doc is a WIP

Super-charging the development of synchronous and asynchronous business workflows.

Introduction

The Krystal Project facilitates a developer-friendly way to write high-performant, complex, business logic which is easily maintainable and observabe. The project encompasses the following major components.

  • Vajram: A programming model which allows developers to design and write code for synchronous scatter gather business logic in a 'bottom-up' (choreographed) manner.
  • Krystex: A runtime environment which executes synchronous parts of the code written in vajram programming model in an optimal way by understanding static dependencies between pieces of code, creating a logical Directed-Acyclic-Graph, and executing the DAG with maximal concurrency.
  • Honeycomb: An asynchronous workflow orchestrator which orchestrates the asynchronous parts of worflows written in the vajram programming model.

Bottom-up vs. Top-down programming

The Krystal project supports two ways of writing business logic - Bottom-up and top-down.

Bottom-up programming or Choreography

Bottom-up programming is a paradigm in which a developer focuses on one atomic piece of business logic and it's dependencies without being aware of when, how and in what order the code will be executed with respect to other business logic in the application. The orchestration of the execution of the code is the reponsibility of the platform runtime, which analyzes the static dependencies declared by each piece of business logic and orchestrates the execution of the all the different pieces of the business logic in the most optimal fashion by building a Directed Acyclic Graph. This also called Choreography.

Top-down programming or Orchestration

Top-down programming is a paradigm in which the developer wants complete control over when and in what order a piece of business logic is executed. This means that the developer has an awareness of the uber workflow and a design intent for ordering of the various operations which are part of the workflow in a specific pre-determined order. This is also called Orchestration.

Example 1

Let's say a developer wants to write a piece of business logic that returns the population of the capital of a country, and let us say there are two exisinting APIs which return the capital of a given country and the pupulation of a given city respectively.

Bottom-up programming

//Pseudo-code
name: populationOfCapital
inputs: country
dependencies: 
  city = capital(country)
  population = population(city)
return:
  population

Top-down programming

inputs: country
workflow:
  return 
    getCapitalOfCountry(country)
    .getPopulationOfCity()
    .returnValue()

Example 2

Let's say a developer is coding the recipe for making a pizza

Bottom-up programming

//Pseudo-code
----------
name: dough
owner: dev1 (kneader)
inputs: water, flour
dependencies: []
return:
  rest(water + flour, 5hours)
----------
name: pizzabase
owner: dev2 (base_maker)
inputs: water, flour, thickness, radius
dependencies: 
  dough = dough(water, flour)
return 
  shape(dough, thickness, radius)
----------
name: preheated_oven
owner: dev3 (oven_manager)
inputs: temp, time
dependencies: []
return
  preheat_oven(temp, time)
----------
name: standard_wheat_pizza
owner : dev4 (storefront)
inputs: water, wheat_flour, cheese, toppings[]
dependencies:
  base = pizzabase(water, wheat_flour, thickness = 5mm, radius = 9inch)
  oven = preheated_oven(200degCel, 15min)
return
  bake(oven, base + cheese + toppings, 20min, 200degCel)
  

As you can see, each piece of business logic is owned by a separate owner. And no single owner knows the complete end to end workflow. With each kryon declaring their local dependencies and interacting with those dependencies, the final pizza get's made without any single developer knowing the complete recipe. They just need to focus on their part of the problem statement (recipe).

Topdown programming

Pseudo code
---------
Owner: Dev5 (chef)
return:
       startWorkflow("Standard_wheat_pizza")
          .preheatOven(200degCel, 15min)
          .as(preheatedOven)
          .take(water, wheatFlour)
          .makeDough(water, flour)
          .as(dough)
          .makeBase(dough, 5mm, 9inch)
          .as(base)
          .add(cheese).on(base)
          .add(toppings).on(cheese)
          .as(unbakedPizza)
          .bake(unbakedPizza).in(preheatedOven, 15min)
          .return()

As you can see here, a single developer has the complete knowledge of the recipe and has coded that logic into a single uber workflow.

Design Goals

Components of the Krystal Project try to adhere to the following design goals:

  1. Separation of functional contracts and non-functional implementation details of dependencies: Developers coding a piece of functional business logic which depends on some other piece of business logic should be completely shielded from non-functional implementation details of the dependency as well as the runtime details of the environment in which the business logic is executing. Here non-functional requirements include:
    • Session-level Caching
    • Concurrency mode (multithreaded thread-per-task model, vs. reactive vs. hybrid model)
    • Batching and batch size of dependency inputs
  2. Minimize lateral and upward impact of code changes: If all the business logic executing to fulfill a request is seen as a Directed acyclic graph, a code change being made in a kryon in the graph should not impact the implementation of any other kryon in the graph which is a direct descendant of it.
  3. Zero glue code: If there is business logic A and business logic B, all the relevant business logic should be contained within the artefact which represents each piece of business logic.
  4. Out-of the box Non-intrusive batching of service-call inputs
  5. Optimal end-to-end execution: This programming model is designed to be adopted for executing complex business logic in systems that power features and user experiences which directly are accessed by end users of websites with heavy traffic, thus strongly needing a low-latency, high-throughput execution runtime.
    • Minimize use of native OS threads
    • Avoid the possibility of developers erroneously blocking on thread-blocking-code pre-maturely.
    • Streaming-over-network-connection capable
  6. Avoid unnecessary bottlenecks and latency long poles: ...
  7. Developer friendliness:
    • Minimal bootstrapping overhead
    • Dependency discovery
    • Code generation
    • Single point of coding - One-to-one atomic mapping of coding units and functional requirements.
    • Minimize mental-overhead and avoid anti patterns inherent to reactive coding
    • Programming-language-native developer experience: For example, type safety.
  8. Observability - application owners get this Out of the Box:
    • Circuit Breaking
    • Live Service Call Dashboards
    • Degradation levers
    • Metrics
    • Logging
    • DAG visualization of a request
  9. Testability
    • Backward-incompatibility detection
    • Mocking-out-of-the box
    • Declarative unit test definition
    • Auto-generated unit test code/templates
  10. Programming language agnostic spec definitions: Although the project currently supports the java programming language, keeping the programming-model spec language-agnostic allows the programming model to have implementations in different languages and allows application owners/feature-developers to choose a language of their choice.

Krystal developer FAQs

Version bump up to Krystal

Follow the below mentioned steps for the Krystal version bump -

  1. Build and publish vajram-codegen with new version to local maven repo. Ensure the vajram-codegen dependencies version should not be updated.
  2. Update the new version in Krystal project build.gradle file and revert the vajram-codegen version to previous version. Do a complete build and publish to local maven repo.
  3. Update the vajram-codegen to the new version.

Example : Need to update version from 1.6 to 1.7

  1. vajram-codegen (build.gradle) update
  • update version from 1.6 to 1.7
  • Build and gradle publishToMavenLocal in krystal root directory
  1. krystal (build.gradle) update
  • update version from 1.6 to 1.7
  • set classpath 'com.flipkart.krystal:vajram:'+ project.krystal_version in vajram-codegen's buildscript block to classpath 'com.flipkart.krystal:vajram:1.6'
  • Build and gradle publishToMavenLocal in krystal root directory
  1. Final update
  • revert classpath 'com.flipkart.krystal:vajram:1.6'+ project.krystal_version in vajram-codegen's buildscript block to classpath 'com.flipkart.krystal:vajram:'+ project.krystal_version
  • Build and publishToMavenLocal and publish in krystal root directory

krystal's People

Contributors

ajitraj-08-08 avatar ajitraj88 avatar dibyanideb avatar digvijay-rawat avatar nahata-p-aditya avatar prateek-kr-fk avatar ramanvesh avatar regunathb avatar shivendra1401 avatar vinisha-parwal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

krystal's Issues

#VajramLang Create the grammar for a new programming language for vajrams

This is currently for educational, readability and explicability purposes.
The idea is to design a high level programming language grammar that allows us to express the ideas of vajram/knode in a clear and concise manner.
This concise grammar and code can guide the design of vajrams in java to be as close to the ideal as possible.

At a later time, this programming language grammar can be developed into a full-fledged programming language (either JVM based, or otherwise) allowing us to write native vajrams without annotations and code generation.

Complete support for IO Vajrams and IO Nodes #Vajram #Krystex

The current implementation is aimed to be generic and should work for IO Vajrams and Nodes as well. But most of the code is gated behind entity.isBlocking() checks. Remove these checks, test that IO Vajrams and Nodes are working, make necessary changes and add test cases for the same

Make `Inputs` class an interface which can be implemented per vajram

Currently, Inputs is a final class encapsulating a map. This means every time we need to access any single input from the class we need to a ImmutableMap lookup. While an ImmutableMap lookup is O(1) and might be acceptable in common scenarios, this can add up to consume some significant (single digit %) CPU in a framework like Krsytal where we process thousands of requests per second and each request accesses inputs multiple times. While the ImmutableMap lookup might be inevitable in some scenarios, it can be avoided in other scenarios where the call is being made from inside vajram code which is data type and input schema aware, rather than krystex code which is data type and input schema unaware.

To take advantage of this difference, we can do the following:
We make Inputs class an interface and let the vajram-codegen auto-generate an implementation of Inputs. This implemented class will have the ability to access individual inputs without map look-ups if the caller knows the input name at compile time. Even in cases where the inputs are accessed from krystex, we can autogenerate a switch-cased input accessor which could be faster than a hash map lookup (This needs to be verified and quantified. Also, if switching over strings (input names) is slower, we can consider assigning a unique int id to each input, and use that to refer to inputs instead. This will definitely improve performance of random access of inputs from krystex since switching over integers is faster than switching over strings and looking up hashmaps - this improvement needs to be quantified as well.
Refs:
https://stackoverflow.com/questions/27993819/hashmap-vs-switch-statement-performance
https://web.archive.org/web/20180818151450/http://java-performance.info/string-switch-implementation/
https://stackoverflow.com/questions/12020048/how-does-javas-switch-work-under-the-hood
https://www.artima.com/articles/control-flow-in-the-java-virtual-machine
)

The implication of this is that Inputs equality can get affected. Currently two Inputs are equal if they have the same key value pairs. But with auto-generated Inputs implementations, this behaviour might no be possible to achieve. I don't think Krystal relies on this behaviour anywhere, so this change should be fine, but this has to be verified first.

Move `VajramKryonGraph.validateMandatory` from `VajramKryonGraph` to auto-generated code #Vajram

Currently VajramKryonGraph.validateMandatory is taking ~10% of the CPU of Kryon.executeMainLogicIfPossible. Most of this time is going in Stream processing and iteration.

I idea is to move this validation into auto-generated vajram models code so that we can omit iteration and stream processing. Instead we can custom generate a validation method which access each input explicitly. This way we can avoid Inputs.getInputValue lookup as well.

#VajramTestHarness

Provide a test harness where devs can mock dependency vajrams and write unit test cases

#VajramOutputType declaration in the vajram yaml config file

Vajram output type should be defined in the vajram yaml config file. This will eliminate adding vajram response type declaration in the dependency details. Also this can be used in identifying duplicate vajrams during build based on input and output type if required.

#MinimizeCommandQueueUsage #Krystex

NodeCommands should be written to the command queue iff the nodeCommand originates from a thread different from the main Thread (Like IO reactor threads). This way, we minimize monitor locking - since command queue is thread safe, and use monitor locking only when needed (when different threads need to access the command queue).

In past local NFRs it was found that Krystex throughput and latency saw significant improvement when the commandQueue usage was optimized by replacing ThreadPoolExecutor (which uses ArrayBLockingQueue internally) With ForkJoinPool (which has a more sharderd queue model to maximize concurrency) (See #83). This seems to be because of contention in the command queue.

With more and more compute nodes being added (We expect the number of compute nodes in the call graph to be at least an order of mangnitude more than io nodes), avoiding command queue usage when the node command originates from the main thread can give us close to java method call performance or in the same ballpark

Avoid recomputing if session Input injection is needed by a Vajram #Vajram

Currently Kryon.getSortedDecorators is taking up ~7.5% of the CPU of Kryon.executeMainLogicIfPossible.
Out of this 50% is being taken up by

logicExecutionContext ->
                vajram.getInputDefinitions().stream()
                    .filter(inputDefinition -> inputDefinition instanceof Input<?>)
                    .map(inputDefinition -> ((Input<?>) inputDefinition))
                    .anyMatch(
                        input ->
                            input.sources() != null
                                && input.sources().contains(InputSource.

which is inside VajramKryonGraph.registerInputInjector.
Computing this every time is not needed as this is static and does not change during the runtime of the application. We can cache this inside VajramDefinition and reuse it.

#FlushSkippedDependencies Add support for Flushing complete dependency call graph when a node is skipped in #Krystex

Problem:
Currently the ResolverCommand.Skip feature and the FlushCommand feature do not interoperate well.
When a node is skipped, it immediately returns, and does not interact with its dependencies.
But the Flush functionality of Krystex only works if every dependantChain reliably flushes all its dependencies.

Requirement:
When a node receives a SkipCommand, force flush all its dependencies.

Testing:
Add test case to cover this case :
A depends on B and B depends on C.
A skips B
A flushes B
B should Flush C

Added a "disabled" test case:
KrystexVajramExecutorTest#flush_skippingADependency_flushesCompleteCallGraph

Fix this bug and enable this test case.

#WhatsWrongWithMe Implementation in #Krystex

Users of Krystex should be able to

  • Dump the complete call graph with inputs, outputs, exceptions and timestamps/latencies of each Kryon.
  • The dump can be a json or it can be in a format which can be loaded in a browser where the complete call graph and the facet values can be viewed as an interactable Graph. The user should be able to filter node in the graph based on tags (IO, compute, service)

#CachingDecorator

Implement Caching decorator and remove the caching logic from Kryons so that we can pick and choose when to use caching and when not.

#DefaultValueFunction Add support for computing default value for the vajram

In cases where the VajramLogic cannot be called due to reasons like circuitBreaker being open or semaphore exhaustion, a simple function in the vajram with an annotation like @DefaultValue should be called to compute and return the default value of the vajram.

  • The default value function's code cannot exit the JVM's boundaries (e.g no IO calls).
  • The default value function should have access to the input values of the vajram (not the dependency values)

Open questions:

  • Should the default value function have access to the exception thrown? (Need to collect use cases for this) (ref)
  • If the above is true, should we allow multiple default value methods with different exception signatures? (ref)
  • Should the default value be used only as a fallback in case of circuit open and bulkhead full? Or should clients have access to the default value for other scenarios like Timeouts, and other generic exceptions? (Need to collect use cases for this)

#KrystalIntellijPlugin Create Intellij plugin for #Krystal

Plugin should be able to

  • Show data type errors
  • Show error for missing resolvers for dependency inputs
  • Show cyclic dependency errors
  • Show sub-optimal resolution errors
  • Show error for duplicate resolvers
  • Auto-generate resolver code
  • Auto-generate vajram file skeleton via Create New (โŒ˜N) action, and via quick actions on errors.

Develop a java virtual-thread based Krystal Executor #Krystex

The existing implementation of Krystal Executor is based on a single-threaded, event-loop based model. Implement an executor based on java virtual threads of JDK 20.

The idea is that such an executor will have simpler call stacks potentially making understanding error logs and debugging easier.

Reduce CPU cost of using `java.util.Stream`s in Krystal code

Rough analysis of CPU flame charts show significant usage of CPU by Stream pipelines
For example, in one instance, filtering a list by applying a condition and finding the first match is taking 5x more CPU than the condition itself.
This means out of all the streams being used in Krystal, upto 4x CPU is being wasted by Streams.
If this can be reclaimed by replacing streams with for loops over iterators, we should do that.

Change Blocking and NonBlocking terminology across the code base #Vajram #Krystex

Code which makes IO calls which return CompletableFutures are called BlockingVajram and BlockingNode today. This is confusing. The code which is making IO calls don't actually block since they are using the non-blocking network call paradigm. These names need to be changed.

Suggestion:
Change Blocking to IO and NonBlocking to Compute (IOVajram, ComputeVajram, IONode, ComputeNode etc..)

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.