Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Execution Performance #1392

Merged
merged 57 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
7c2839d
Started taking the resolver middleware apart
michaelstaib Mar 30, 2023
7c92a54
wip
michaelstaib Mar 30, 2023
ac49714
wip
michaelstaib Mar 31, 2023
51320ab
wip
michaelstaib Mar 31, 2023
761761b
wip
michaelstaib Mar 31, 2023
36096f0
wip
michaelstaib Mar 31, 2023
862b117
Fixed mistakes in type interceptor
michaelstaib Apr 4, 2023
06eb21b
Removed several unecessary transformation steps
michaelstaib Apr 4, 2023
965b3d1
Fixes several compile issues and other issues
michaelstaib Apr 4, 2023
29cc2dd
Fixes several compile issues
michaelstaib Apr 4, 2023
703d70b
Reworked the SQL pagination handler
michaelstaib Apr 11, 2023
a8a777d
cleanup
michaelstaib Apr 11, 2023
08755cb
Update src/Service/Services/ExecutionHelper.cs
michaelstaib Apr 11, 2023
cbd0ec4
Registered state middleware
michaelstaib Apr 11, 2023
6f5778f
refinements
michaelstaib Apr 12, 2023
6545440
refinements
michaelstaib Apr 12, 2023
eace4db
refinements
michaelstaib Apr 12, 2023
cab2c79
Update src/Service/Resolvers/SqlPaginationUtil.cs
michaelstaib Apr 19, 2023
f197b91
Update src/Service/Resolvers/JsonObjectExtensions.cs
michaelstaib Apr 19, 2023
01fd103
Update src/Service/Resolvers/JsonObjectExtensions.cs
michaelstaib Apr 19, 2023
b272a11
Merge branch 'main' into mst/execution-perf
Aniruddh25 Apr 27, 2023
9fa6775
Added some tests
michaelstaib May 1, 2023
7379a05
Fixed Comment
michaelstaib May 1, 2023
450dda5
Fixes list resolvers for SQL server.
michaelstaib May 3, 2023
b24bcc2
Fixes path enrolement for metadata.
michaelstaib May 3, 2023
6778c30
Fixes whitespaces
michaelstaib May 3, 2023
8ee2117
Merge branch 'main' into mst/execution-perf
Aniruddh25 May 3, 2023
01f6d96
Merge branch 'main' into mst/execution-perf
Aniruddh25 May 3, 2023
db05bca
resolve merge conflicts with last version updates.
seantleonard Dec 12, 2023
94fcbda
fix conflicts
seantleonard Dec 12, 2023
dc05e83
fix pure resolver context switching and key not found/key already exi…
seantleonard Dec 13, 2023
1d51ee6
Fix metadata key storage and acquisition.
seantleonard Dec 14, 2023
e74d456
Apply latest from main that was improperly captured during conflict r…
seantleonard Dec 14, 2023
491cfed
fix date and uuid.
seantleonard Dec 15, 2023
7190041
Merge branch 'main' into mst/execution-perf
seantleonard Dec 15, 2023
356d6e6
dotnet format ran to clear up issues.
seantleonard Dec 15, 2023
21844dd
Fixed ArrayPoolWriter Resizing
michaelstaib Dec 18, 2023
ceebdd6
Merge branch 'main' into mst/execution-perf
seantleonard Jan 2, 2024
dbd01fc
Merge branch 'main' into mst/execution-perf
seantleonard Jan 8, 2024
0eab963
Merge branch 'main' into mst/execution-perf
seantleonard Jan 24, 2024
2da215f
Don't crash for cosmos requests since CosmosQueryEngine doesn't set a…
seantleonard Jan 25, 2024
2f03079
Fix the method ResolveListType for cosmos such that json is properly …
seantleonard Jan 26, 2024
a3d95db
Merge branch 'main' into mst/execution-perf
seantleonard Jan 26, 2024
04ec3e0
fix merge errors.
seantleonard Jan 26, 2024
908d730
updated comments per code review
seantleonard Feb 8, 2024
ab8bdcd
add many many comments explaining how key/value pairs are used/stored…
seantleonard Feb 9, 2024
7a319f9
updated comments and properly cleanup/dispose jsondocument usage
seantleonard Feb 9, 2024
f25bc4c
Merge branch 'main' into mst/execution-perf
Aniruddh25 Feb 12, 2024
b9e15ee
Merge branch 'main' into mst/execution-perf
Aniruddh25 Feb 13, 2024
eb9df20
fix grammar in comments
seantleonard Feb 13, 2024
4ca36e9
Merge branch 'main' into mst/execution-perf
seantleonard Feb 14, 2024
4ccf379
Merge branch 'main' into mst/execution-perf
Aniruddh25 Feb 26, 2024
941573c
Update comments per pr feedback. Elaborate on jsonvaluetype resolved …
seantleonard Feb 29, 2024
6426833
Merge branch 'mst/execution-perf' of https://github.com/michaelstaib/…
seantleonard Feb 29, 2024
e22980b
Merge branch 'Azure:main' into mst/execution-perf
seantleonard Feb 29, 2024
2a833c5
add temp fix for DW JSON result differing from MSSQL json result. DW …
seantleonard Feb 29, 2024
518eb2e
update commit that StringValues[key] returns StringValues.Empty if ke…
seantleonard Mar 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/Core/Models/GraphQLFilterParsers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Azure.DataApiBuilder.Service.Exceptions;
using Azure.DataApiBuilder.Service.GraphQLBuilder.Directives;
using Azure.DataApiBuilder.Service.GraphQLBuilder.Queries;
using Azure.DataApiBuilder.Service.Services;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using Microsoft.AspNetCore.Http;
Expand Down Expand Up @@ -65,12 +66,12 @@ public Predicate Parse(
string dataSourceName = _configProvider.GetConfig().GetDataSourceNameFromEntityName(entityName);
ISqlMetadataProvider metadataProvider = _metadataProviderFactory.GetMetadataProvider(dataSourceName);

InputObjectType filterArgumentObject = ResolverMiddleware.InputObjectTypeFromIInputField(filterArgumentSchema);
InputObjectType filterArgumentObject = ExecutionHelper.InputObjectTypeFromIInputField(filterArgumentSchema);

List<PredicateOperand> predicates = new();
foreach (ObjectFieldNode field in fields)
{
object? fieldValue = ResolverMiddleware.ExtractValueFromIValueNode(
object? fieldValue = ExecutionHelper.ExtractValueFromIValueNode(
value: field.Value,
argumentSchema: filterArgumentObject.Fields[field.Name.Value],
variables: ctx.Variables);
Expand All @@ -85,7 +86,7 @@ public Predicate Parse(
bool fieldIsAnd = string.Equals(name, $"{PredicateOperation.AND}", StringComparison.OrdinalIgnoreCase);
bool fieldIsOr = string.Equals(name, $"{PredicateOperation.OR}", StringComparison.OrdinalIgnoreCase);

InputObjectType filterInputObjectType = ResolverMiddleware.InputObjectTypeFromIInputField(filterArgumentObject.Fields[name]);
InputObjectType filterInputObjectType = ExecutionHelper.InputObjectTypeFromIInputField(filterArgumentObject.Fields[name]);
if (fieldIsAnd || fieldIsOr)
{
PredicateOperation op = fieldIsAnd ? PredicateOperation.AND : PredicateOperation.OR;
Expand Down Expand Up @@ -509,7 +510,7 @@ private Predicate ParseAndOr(
List<PredicateOperand> operands = new();
foreach (IValueNode field in fields)
{
object? fieldValue = ResolverMiddleware.ExtractValueFromIValueNode(
object? fieldValue = ExecutionHelper.ExtractValueFromIValueNode(
value: field,
argumentSchema: argumentSchema,
ctx.Variables);
Expand Down Expand Up @@ -598,11 +599,11 @@ public static Predicate Parse(
{
List<PredicateOperand> predicates = new();

InputObjectType argumentObject = ResolverMiddleware.InputObjectTypeFromIInputField(argumentSchema);
InputObjectType argumentObject = ExecutionHelper.InputObjectTypeFromIInputField(argumentSchema);
foreach (ObjectFieldNode field in fields)
{
string name = field.Name.ToString();
object? value = ResolverMiddleware.ExtractValueFromIValueNode(
object? value = ExecutionHelper.ExtractValueFromIValueNode(
value: field.Value,
argumentSchema: argumentObject.Fields[field.Name.Value],
variables: ctx.Variables);
Expand Down
188 changes: 188 additions & 0 deletions src/Core/Resolvers/ArrayPoolWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Buffers;

/// <summary>
/// A helper to write to pooled arrays.
/// </summary>
internal sealed class ArrayPoolWriter : IBufferWriter<byte>, IDisposable
{
private const int INITIAL_BUFFER_SIZE = 512;
private byte[] _buffer;
private int _capacity;
private int _start;
private bool _disposed;

/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolWriter"/> class.
/// </summary>
public ArrayPoolWriter()
{
_buffer = ArrayPool<byte>.Shared.Rent(INITIAL_BUFFER_SIZE);
_capacity = _buffer.Length;
_start = 0;
}

/// <summary>
/// Gets the part of the buffer that has been written to.
/// </summary>
/// <returns>
/// A <see cref="ReadOnlyMemory{T}"/> of the written portion of the buffer.
/// </returns>
public ReadOnlyMemory<byte> GetWrittenMemory()
=> _buffer.AsMemory()[.._start];

/// <summary>
/// Gets the part of the buffer that has been written to.
/// </summary>
/// <returns>
/// A <see cref="ReadOnlySpan{T}"/> of the written portion of the buffer.
/// </returns>
public ReadOnlySpan<byte> GetWrittenSpan()
=> _buffer.AsSpan()[.._start];

/// <summary>
/// Advances the writer by the specified number of bytes.
/// </summary>
/// <param name="count">
/// The number of bytes to advance the writer by.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if <paramref name="count"/> is negative or
/// if <paramref name="count"/> is greater than the
/// available capacity on the internal buffer.
/// </exception>
public void Advance(int count)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(ArrayPoolWriter));
}

if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count));
}

if (count > _capacity)
{
throw new ArgumentOutOfRangeException(nameof(count), count, "Cannot advance past the end of the buffer.");
}

_start += count;
_capacity -= count;
}

/// <summary>
/// Gets a <see cref="Memory{T}"/> to write to.
/// </summary>
/// <param name="sizeHint">
/// The minimum size of the returned <see cref="Memory{T}"/>.
/// </param>
/// <returns>
/// A <see cref="Memory{T}"/> to write to.
/// </returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if <paramref name="sizeHint"/> is negative.
/// </exception>
public Memory<byte> GetMemory(int sizeHint = 0)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(ArrayPoolWriter));
}

if (sizeHint < 0)
{
throw new ArgumentOutOfRangeException(nameof(sizeHint));
}

int size = sizeHint < 1 ? INITIAL_BUFFER_SIZE : sizeHint;
EnsureBufferCapacity(size);
return _buffer.AsMemory().Slice(_start, size);
}

/// <summary>
/// Gets a <see cref="Span{T}"/> to write to.
/// </summary>
/// <param name="sizeHint">
/// The minimum size of the returned <see cref="Span{T}"/>.
/// </param>
/// <returns>
/// A <see cref="Span{T}"/> to write to.
/// </returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if <paramref name="sizeHint"/> is negative.
/// </exception>
public Span<byte> GetSpan(int sizeHint = 0)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(ArrayPoolWriter));
}

if (sizeHint < 0)
{
throw new ArgumentOutOfRangeException(nameof(sizeHint));
}

int size = sizeHint < 1 ? INITIAL_BUFFER_SIZE : sizeHint;
EnsureBufferCapacity(size);
return _buffer.AsSpan().Slice(_start, size);
}

/// <summary>
/// Ensures that the internal buffer has the needed capacity.
/// </summary>
/// <param name="neededCapacity">
/// The needed capacity on the internal buffer.
/// </param>
private void EnsureBufferCapacity(int neededCapacity)
{
// check if we have enough capacity available on the buffer.
if (_capacity < neededCapacity)
{
// if we need to expand the buffer we first capture the original buffer.
byte[] buffer = _buffer;

// next we determine the new size of the buffer, we at least double the size to avoid
// expanding the buffer too often.
int newSize = buffer.Length * 2;

// if that new buffer size is not enough to satisfy the needed capacity
// we add the needed capacity to the doubled buffer capacity.
if (neededCapacity > newSize - _start)
{
newSize += neededCapacity;
}

// next we will rent a new array from the array pool that supports
// the new capacity requirements.
_buffer = ArrayPool<byte>.Shared.Rent(newSize);

// the rented array might have a larger size than the needed capacity,
// so we will take the buffer length and calculate from that the free capacity.
_capacity += _buffer.Length - buffer.Length;

// finally we copy the data from the original buffer to the new buffer.
buffer.AsSpan().CopyTo(_buffer);

// last but not least we return the original buffer to the array pool.
ArrayPool<byte>.Shared.Return(buffer);
}
}

/// <inheritdoc/>
public void Dispose()
{
if (!_disposed)
{
ArrayPool<byte>.Shared.Return(_buffer);
_buffer = Array.Empty<byte>();
_capacity = 0;
_start = 0;
_disposed = true;
}
}
}
12 changes: 6 additions & 6 deletions src/Core/Resolvers/CosmosQueryEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,14 @@ public Task<IActionResult> ExecuteAsync(StoredProcedureRequestContext context, s
}

/// <inheritdoc />
public JsonDocument ResolveInnerObject(JsonElement element, IObjectField fieldSchema, ref IMetadata metadata)
public JsonElement ResolveObject(JsonElement element, IObjectField fieldSchema, ref IMetadata metadata)
{
//TODO: Try to avoid additional deserialization/serialization here.
return JsonDocument.Parse(element.ToString());
return element;
}

/// <inheritdoc />
public object ResolveListType(JsonElement element, IObjectField fieldSchema, ref IMetadata metadata)
/// metadata is not used in this method, but it is required by the interface.
public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMetadata metadata)
{
IType listType = fieldSchema.Type;
// Is the List type nullable? [...]! vs [...]
Expand All @@ -217,10 +217,10 @@ public object ResolveListType(JsonElement element, IObjectField fieldSchema, ref

if (listType.IsObjectType())
{
return JsonSerializer.Deserialize<List<JsonElement>>(element);
return JsonSerializer.Deserialize<List<JsonElement>>(array);
}

return JsonSerializer.Deserialize(element, fieldSchema.RuntimeType);
return JsonSerializer.Deserialize(array, fieldSchema.RuntimeType);
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Resolvers/IQueryEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ public interface IQueryEngine
/// <summary>
/// Resolves a jsonElement representing an inner object based on the field's schema and metadata
/// </summary>
public JsonDocument? ResolveInnerObject(JsonElement element, IObjectField fieldSchema, ref IMetadata metadata);
public JsonElement ResolveObject(JsonElement element, IObjectField fieldSchema, ref IMetadata metadata);

/// <summary>
/// Resolves a jsonElement representing a list type based on the field's schema and metadata
/// </summary>
public object? ResolveListType(JsonElement element, IObjectField fieldSchema, ref IMetadata metadata);
public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMetadata? metadata);
}
}
91 changes: 91 additions & 0 deletions src/Core/Resolvers/JsonObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Buffers;
using System.Text.Json;
using System.Text.Json.Nodes;

/// <summary>
/// This extension class provides helpers to convert a mutable JSON object
/// to a JSON element or JSON document.
/// </summary>
internal static class JsonObjectExtensions
{
/// <summary>
/// Converts a mutable JSON object to an immutable JSON element.
/// </summary>
/// <param name="obj">
/// The mutable JSON object to convert.
/// </param>
/// <returns>
/// An immutable JSON element.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="obj"/> is <see langword="null"/>.
/// </exception>
public static JsonElement ToJsonElement(this JsonObject obj)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}

// we first write the mutable JsonObject to the pooled buffer and avoid serializing
// to a full JSON string.
using ArrayPoolWriter buffer = new();
obj.WriteTo(buffer);

// next we take the reader here and parse the JSON element from the buffer.
Utf8JsonReader reader = new(buffer.GetWrittenSpan());

// the underlying JsonDocument will not use pooled arrays to store metadata on it ...
// this JSON element can be safely returned.
return JsonElement.ParseValue(ref reader);
}

/// <summary>
/// Converts a mutable JSON object to an immutable JSON document.
/// </summary>
/// <param name="obj">
/// The mutable JSON object to convert.
/// </param>
/// <returns>
/// An immutable JSON document.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="obj"/> is <see langword="null"/>.
/// </exception>
public static JsonDocument ToJsonDocument(this JsonObject obj)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}

// we first write the mutable JsonObject to the pooled buffer and avoid serializing
// to a full JSON string.
using ArrayPoolWriter buffer = new();
obj.WriteTo(buffer);

// next we parse the JSON document from the buffer.
// this JSON document will be disposed by the GraphQL execution engine.
return JsonDocument.Parse(buffer.GetWrittenMemory());
}

private static void WriteTo(this JsonObject obj, IBufferWriter<byte> bufferWriter)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}

if (bufferWriter == null)
{
throw new ArgumentNullException(nameof(bufferWriter));
}

using Utf8JsonWriter writer = new(bufferWriter);
obj.WriteTo(writer);
writer.Flush();
}
}
Loading