Optimization and fault tolerance in distributed transaction with Node.JS GraphQL Servers
LyLuongThien
68 views
31 slides
Jun 28, 2024
Slide 1 of 31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
About This Presentation
As I wrap up my journey at Groove Technology, I had the opportunity to share insights and advancements in optimizing and ensuring fault tolerance in Node.js GraphQL servers. My presentation, held on June 27, 2024, encapsulated the hard work and dedication of the team, highlighting key strategies and...
As I wrap up my journey at Groove Technology, I had the opportunity to share insights and advancements in optimizing and ensuring fault tolerance in Node.js GraphQL servers. My presentation, held on June 27, 2024, encapsulated the hard work and dedication of the team, highlighting key strategies and implementations that have significantly enhanced server performance and reliability.
Size: 1.2 MB
Language: en
Added: Jun 28, 2024
Slides: 31 pages
Slide Content
Optimization and fault tolerance in distributed transaction with Node.JS GraphQL Servers Present by Thien Ly on June 27, 2024
Table of content * Intro * Optimization - Graphql Field Loader in Groove’s internal projects - Implement Cancelable GraphQL resolver or CircuiltBreaker - Manage multiple values emitter by Observer pattern - Convert CPU intensive task to Node.js C++ addon * Fault tolerance - Graceful shutdown - Distributed transactions: Two-Phase Commit vs Saga pattern * Q&A
Introduction
Intro Node .js® is a free, open-source, cross-platform JavaScript runtime environment GraphQL is an open-source data query and manipulation language for APIs and a query runtime engine
Intro Optimization refers to the process of improving the efficiency, performance, and resource utilization of software code Fault tolerance refers to the ability of a system to continue operating in the event of a failure or fault
Optimization
Graphql Field Loader in Groove’s internal projects H ow GraphQL Field Loader optimizes data fetching by batching queries and reducing round-trips to databases or other data sources. I mprovements seen in the internal projects in terms of reduced latency and improved overall server performance. Demo in internal project
Graphql Field Loader in Groove’s internal projects Simple implement of inline field loader: const resolvers = { Query: { industry: async () => { const industry = db.getOne(); /** * Inline field loader * industryDetails will only be called * if industryDetails appears in client query * @returns */ const industryDetails = function industryDetailsLoader (){ return api.get(industry.code) } return { data, industryDetails } }, }, };
Graphql Field Loader in Groove’s internal projects The inline field loader is a function, industryDetailsLoader , defined within the resolver for the industry field. This function is designed to be conditionally executed based on the client's query. Purpose The purpose of the inline field loader is to optimize data fetching by only calling the industryDetails API if the industryDetails field is explicitly requested by the client. This approach avoids unnecessary API calls, improving performance and reducing resource usage when the additional details are not needed.
Graphql Field Loader in Groove’s internal projects Example Usage If the client query includes industryDetails , the industryDetailsLoader will be executed to fetch additional data. If industryDetails is not part of the query, the function will not be called, thereby saving the overhead of the API request. query { industry { data industryDetails }} In this example, the industryDetails field would trigger the industryDetailsLoader function, whereas if the query only requested data , the loader function would not be executed
Implement Cancelable GraphQL Resolver/Circuit Breaker Demo in internal project class CancelableCircuitBreaker { constructor ( private resolver: GraphQLResolver, private timeout: number, public cancelEvent: null | EventEmitter = null ) {} wrap() { return <GraphQLResolver>( async (...args) => { const winner = await Promise.race([ new Promise((resolve) => setTimeout(() => resolve( "Timeout" ), this .timeout) ), new Promise((resolve) => context.req.on( "close" , () => resolve( "ClientRequestClosed" )) ), this .cancelEvent ? new Promise((resolve) => this .cancelEvent?.on( "close" , () => resolve( "ClientRequestClosed" ) ) ) : null , this .resolver(...args), ].filter(Boolean)); switch (winner) { case "Timeout" : throw new http.RequestTimeoutError(); case "Close" : console.log( "Client cancelled request before it complete" ); return null ; default : return winner; // return data of resolver as normal } }); } }
Implement Cancelable GraphQL Resolver/Circuit Breaker The purpose of the CancelableCircuitBreaker class is to enhance a GraphQL resolver with additional control mechanisms for improved reliability and responsiveness. It achieves this by: Timeout Management : Ensuring that the resolver execution does not exceed a specified time limit, throwing a RequestTimeoutError if it does. Client Disconnection Handling : Detecting if the client disconnects before the resolver completes and returning null in such cases. External Cancellation : Allowing for external signals to cancel the resolver execution, which is useful for dynamic cancellation requirements outside of the immediate client request context. These features help in making the GraphQL server more robust by preventing long-running operations from hanging and efficiently managing resources during request cancellations.
Manage multiple values emitter by Observer pattern in DCA Re-implement Observer pattern with vanilla typescript Demo in internal project
Converting CPU Intensive Tasks to Node.js C++ Addon Node.js C++ Addon: hello world step by step: https://nodejs.org/api/addons.html#hello-world Create the C++ file: Save the above code in a file (e.g., addon.cpp). Compile the addon: Use node-gyp to compile the addon. You'll need a binding.gyp file to configure compilation. Require in Node.js: Once compiled, require the addon in your Node.js application.
Converting CPU Intensive Tasks to Node.js C++ Addon Source JS:
Converting CPU Intensive Tasks to Node.js C++ Addon Converted to Cpp Addon 1 :
Converting CPU Intensive Tasks to Node.js C++ Addon Converted to Cpp Addon 2:
Fault Tolerance
Graceful shutdown ensure ongoing operations are completed, connections are closed gracefully, and data integrity is maintained Benefits : minimizing downtime, avoiding data loss, and enhancing overall server reliability Graceful Shutdown in Node.js
A simple Implementation that everyone forgets: Graceful Shutdown in Node.js
A distributed transaction is a set of operations on data that is performed across two or more data repositories (especially databases). Internal project with distributed transactions: API server(postgres) - elasticsearch services - SalesForce(as a database) Distributed Transactions: Two-Phase Commit vs Saga Pattern
Two-Phase Commit : traditional approach of Two-Phase Commit for ensuring data consistency across distributed systems, highlighting its drawbacks like blocking behavior and scalability issues. Saga Pattern : an alternative for managing distributed transactions asynchronously, ensuring eventual consistency and handling failures more gracefully in complex workflows. Distributed Transactions: Two-Phase Commit vs Saga Pattern
Distributed Transactions: Two-Phase Commit - 2PC
Distributed Transactions: Two-Phase Commit - 2PC
2PC ensures atomicity across multiple databases or services in distributed transactions. Workflow: Prepare Phase: Coordinator node asks participants to prepare for committing or aborting. Commit Phase: If all participants agree to commit during the prepare phase, the coordinator sends a commit request; otherwise, it sends an abort request. Advantages: Guarantees atomicity (either all participants commit or none). Relatively straightforward implementation in some cases. Disadvantages: Blocking: Can lead to blocking issues if any participant fails or hangs during the transaction. Scalability: Coordination overhead and potential latency issues can affect scalability. Distributed Transactions: Two-Phase Commit - 2PC
Saga breaks down a distributed transaction into smaller, individual transactions that can be independently committed or rolled back. Workflow: Each service in the saga manages its own transactional logic. Uses compensating transactions to rollback changes if a later step fails. Advantages: Non-blocking: Each transaction can proceed independently, reducing blocking issues. Scalability: Distributed nature allows for better scalability compared to 2PC. Flexibility: Can handle long-lived transactions and failures more gracefully. Disadvantages: Complexity: Requires careful design to ensure correctness and consistency. Consistency Maintenance: Harder to ensure global consistency across all services involved. Distributed Transactions: Saga pattern
Use 2PC when strong consistency (atomicity) is critical and the transaction scope is relatively contained. Use Saga when you need scalability, non-blocking behavior, and can tolerate eventual consistency within the system. Distributed Transactions
Operating systems use lock managers to organise and serialise the access to resources. A distributed lock manager (DLM) runs in every machine in a cluster, with an identical copy of a cluster-wide lock database. In this way a DLM provides software applications which are distributed across a cluster on multiple machines with a means to synchronize their accesses to shared resources (wiki). Distributed Locks with Redis: Redlock Image: https://dzone.com/articles/distributed-lock-implementation-with-redis Distributed Transactions: Lock