EF 6: Connection resiliency y azure

Written by Unai Zorrilla on March 14th, 2013. Posted in SQL Azure

Seguro que de muchos es sabido que cuando trabajamos con Sql Database es posible que alguna de nuestras operaciones pueda fallar, independientemente de si usamos EF, NH o directamente con nuestro SqlClient, debido a inestabilidades de la red o problemas de back-end [si desea explorar el porque de la realidad de estos problemas le recomiendo este enlace].  Aunque en la realidad esto no es algo ni mucho menos habitual, puede pasar, y, por lo general, sino somos conscientes de ello suele dar bastantes dolores de cabeza, por lo aleatorio de los fallos. Por supuesto, hay muchos workaround para este problema, y como no, también para los que usamos actualmente EF,en cualquier de sus versiones como por ejemplo este que comenta mi buen amigo Cesar de La Torre. en un post de hace una eternidad. Con el fin de hacer  esto más sencillo para nosotros y en realidad para resolver otros posibles problema similares para este u otros motores relacionales el equipo de EF ya tiene en la rama de EF 6 nuevas funcionalidades.

Estas nuevas características se pueden concretar en los siguientes elementos:

IExecutionStrategy

Esta interfaz define el contrato para la pieza que tendrá como responsabilidad la ejecución de operaciones que potencialmente necesiten reintentarse,por ejemplo las operaciones sobre Sql Database. El contrato de esta interfaz no es nada complicado como podemos ver a continuación:

 

?namespace System.Data.Entity.Infrastructure
{
    using System.Diagnostics.CodeAnalysis;
    using System.Threading;
    using System.Threading.Tasks;

    public interface IExecutionStrategy
    {
        /// <summary>
        ///     Indicates whether this <see cref="IExecutionStrategy"/> might retry the execution after a failure.
        /// </summary>
        bool RetriesOnFailure { get; }

        /// <summary>
        ///     Executes the specified action.
        /// </summary>
        /// <param name="action">A delegate representing an executable action that doesn''t return any results.</param>
        void Execute(Action action);

        /// <summary>
        ///     Executes the specified function and returns the result.
        /// </summary>
        /// <typeparam name="TResult">The return type of <paramref name="func"/>.</typeparam>
        /// <param name="func">A delegate representing an executable action that returns the result of type <typeparamref name="TResult"/>.</param>
        /// <returns>The result from the action.</returns>
        TResult Execute<TResult>(Func<TResult> func);

#if !NET40

        /// <summary>
        ///     Executes the specified asynchronous action.
        /// </summary>
        /// <param name="taskAction">A function that returns a started task.</param>
        /// <returns>
        ///     A task that will run to completion if the original task completes successfully (either the
        ///     first time or after retrying transient failures). If the task fails with a non-transient error or
        ///     the retry limit is reached, the returned task will become faulted and the exception must be observed.
        /// </returns>
        Task ExecuteAsync(Func<Task> taskFunc);

        /// <summary>
        ///     Executes the specified asynchronous action.
        /// </summary>
        /// <param name="taskAction">A function that returns a started task.</param>
        /// <param name="cancellationToken">
        ///     A cancellation token used to cancel the retry operation, but not operations that are already in flight
        ///     or that already completed successfully.
        /// </param>
        /// <returns>
        ///     A task that will run to completion if the original task completes successfully (either the
        ///     first time or after retrying transient failures). If the task fails with a non-transient error or
        ///     the retry limit is reached, the returned task will become faulted and the exception must be observed.
        /// </returns>
        Task ExecuteAsync(Func<Task> taskFunc, CancellationToken cancellationToken);

        /// <summary>
        ///     Executes the specified asynchronous function and returns the result.
        /// </summary>
        /// <typeparam name="TResult">
        ///     The type parameter of the <see cref="Task{T}"/> returned by <paramref name="taskFunc"/>.
        /// </typeparam>
        /// <param name="taskFunc">A function that returns a started task of type <typeparamref name="TResult"/>.</param>
        /// <returns>
        ///     A task that will run to completion if the original task completes successfully (either the
        ///     first time or after retrying transient failures). If the task fails with a non-transient error or
        ///     the retry limit is reached, the returned task will become faulted and the exception must be observed.
        /// </returns>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> taskFunc);

        /// <summary>
        ///     Executes the specified asynchronous function and returns the result.
        /// </summary>
        /// <typeparam name="TResult">
        ///     The type parameter of the <see cref="Task{T}"/> returned by <paramref name="taskFunc"/>.
        /// </typeparam>
        /// <param name="taskFunc">A function that returns a started task of type <typeparamref name="TResult"/>.</param>
        /// <param name="cancellationToken">
        ///     A cancellation token used to cancel the retry operation, but not operations that are already in flight
        ///     or that already completed successfully.
        /// </param>
        /// <returns>
        ///     A task that will run to completion if the original task completes successfully (either the
        ///     first time or after retrying transient failures). If the task fails with a non-transient error or
        ///     the retry limit is reached, the returned task will become faulted and the exception must be observed.
        /// </returns>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> taskFunc, CancellationToken cancellationToken);

#endif

    }
}

 

La idea básica que hay detrás de esta interfaz es poder envolver una acción en un determinado ámbito capaz de decidir si las excepciones que se produzcan son o no candidatas para reintentarse, por ejemplo porque entendemos que es un fallo de Sql Database y no relacionado con el mapeo, consulta etc…, cada cierto tiempo. Por supuesto, decidir si una excepción  de una operación implica que la operación debe repetirse es algo configurable y no fijo, cuya implementación esta marcada por una nueva interfaz, que vemos a continuación.

 

IRetriableExceptionDetector

Mediante este contrato tendremos la posibilidad de especificar que excepciones son candidatas a “resolver” por medio de reintentos, el siguiente código representa este contrato

 

?// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Infrastructure
{
    public interface IRetriableExceptionDetector
    {
        /// <summary>
        ///     Determines whether the specified exception represents a transient failure that can be compensated by a retry.
        /// </summary>
        /// <param name="ex">The exception object to be verified.</param>
        /// <returns><c>true</c> if the specified exception is considered as transient, otherwise <c>false</c>.</returns>
        bool ShouldRetryOn(Exception ex);
    }
}

 

IRetryDelayStrategy

Este es el último elemento que necesitamos, y probablemente el más sencillo puesto que tiene como única finalidad marcar el tiempo entre reintentos de operaciones fallidas.

 

// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Infrastructure
{
    public interface IRetryDelayStrategy
    {
        /// <summary>
        ///     Determines whether the action should be retried and the delay before the next attempt.
        /// </summary>
        /// <param name="lastException">The exception thrown during the last execution attempt.</param>
        /// <returns>
        ///     Returns the delay indicating how long to wait for before the next execution attempt if the action should be retried;
        ///     <c>null</c> otherwise
        /// </returns>
        TimeSpan? GetNextDelay(Exception lastException);
    }
}

 

 

Bien, ahora que ya conocemos las piezas, ya podemos entender que tenemos para Sql Database, y esto, no es más que implementaciones concretas para estos contratos que podemos “inyectar” dentro de EF .

La implementación para el trabajo de Azure, Sql Database, se basa principalmente en la clase SqlAzureExecutionStrategy, que tendremos por defecto en el proveedor de Sql Server por defecto.

?// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.SqlServer
{
    using System.Data.Entity.Infrastructure;

    /// <summary>
    ///     An <see cref="ExecutionStrategy"/> that uses the <see cref="ExponentialRetryDelayStrategy"/> and
    ///     <see cref="SqlAzureRetriableExceptionDetector"/>.
    /// </summary>
    [DbProviderName("System.Data.SqlClient")]
    public class SqlAzureExecutionStrategy : ExecutionStrategy
    {
        public SqlAzureExecutionStrategy()
            : base(new ExponentialRetryDelayStrategy(), new SqlAzureRetriableExceptionDetector())
        {
        }
    }
}

Como observará, en realidad esto no tiene nada más que el marcador de tiempos, ExponentialRetryDelayStrategy, que cuyo nombre indica hace un incremento exponencial basado en la siguiente fórmula:min (minDelay + coefficient * random(1, maxRandomFactor) * (exponentialBase ^ retryCount – 1), maxDelay), y un detector de excepciones personalizado que permitirá reintentar las siguientes excepciones.

 

  • SqlException con diferentes numberos de error, revisar el código siguiente para ver los casos concretos.
  • Un TimeoutException.

 

 

Como se establece la estrategia que queremos usar? por ejemplo esta para Sql Database?, pues bien, tan simple como hacemos otras inyecciones, por medio de nuestro DbConfiguration y el método AddExecutionStrategy.

        /// <summary>
        ///     Call this method from the constructor of a class derived from <see cref="DbConfiguration" /> to add an
        ///     <see cref="IExecutionStrategy"/> for use with the associated provider.
        /// </summary>
        /// <remarks>
        ///     The <typeparamref name="T"/> type should have a <see cref="DbProviderNameAttribute"/> applied to it.
        ///     This method is provided as a convenient and discoverable way to add configuration to the Entity Framework.
        ///     Internally it works in the same way as using AddDependencyResolver to add an appropriate resolver for
        ///     <see cref="IExecutionStrategy" />. This means that, if desired, the same functionality can be achieved using
        ///     a custom resolver or a resolver backed by an Inversion-of-Control container.
        /// </remarks>
        /// <typeparam name="T"> The type that implements <see cref="IExecutionStrategy"/>. </typeparam>
        /// <param name="getExecutionStrategy"> A function that returns a new instance of an execution strategy. </param>
        protected internal void AddExecutionStrategy<T>(Func<T> getExecutionStrategy)
            where T : IExecutionStrategy
        {
            Check.NotNull(getExecutionStrategy, "getExecutionStrategy");

            _internalConfiguration.CheckNotLocked("AddExecutionStrategy");

            foreach (var providerInvariantNameAttribute in DbProviderNameAttribute.GetFromType(typeof(T)))
            {
                _internalConfiguration.AddDependencyResolver(
                    new ExecutionStrategyResolver<T>(providerInvariantNameAttribute.Name, /*serverName:*/ null, getExecutionStrategy));
            }
        }

Si nos fijamos un poco veremos como lo primero que hace este método es recuperar el proveedor para el que aplica esta estrategia, buscando un atributo, DbProviderName. Una vez localizado este atributo se establece el dependency resolver con la estrategia indicada. A partir de ese momento, ya tendremos disponible nuestra estrategia de reintentos..

 

 

Bueno, esto ha sido todo, espero que os sigan gustando las novedades de EF 6 y que sigáis viendo todo lo nuevo que poco a poco vamos teniendo

Unai

LinkedInMessengerShare

Novedades SQL Database

Written by Ibon Landa on October 10th, 2012. Posted in SQL Azure

Para muchos de vosotros seguro que ya no es una novedad, pero no había tenido tiempo es escribirlo hasta ahora y me parece que estas características que os comentaré aquí son bastante interesantes, por lo que no he podido pasar de comentarlas, aunque sea de forma muy resumida.

En primero lugar, por fin, SQL Database ya tiene soporte para consultas distribuidas y “linked servers”.

2677_SQLDB1_png-550x0

Además de esta característica, hay más novedades igual de importantes:

LinkedInMessengerShare

Reduccion de precios de SQL Azure de hasta el 75%

Written by Ibon Landa on February 20th, 2012. Posted in SQL Azure

Debido al éxito en el uso y adopción de SQL Azure y para dar respuesta tanto a usuarios de bases de datos muy pequeñas como a aquellos que hacen un uso más intensivo,  se ha anunciado una importante reducción de precios de entre el 48% y el 75%. Esta reducción de precios no es una oferta puntual sino una bajada de precios permanente y efectiva desde el anuncio.

GB

Precio anterior

Nuevo Precio

Precio/GB

Total % Reducción

5

$49.95

$25.99

$5.20

48%

10

$99.99

$45.99

$4.60

54%

25

$299.97

$75.99

$3.04

75%

50

$499.95

$125.99

$2.52

75%

100

$499.95

$175.99

$1.76

65%

150

$499.95

$225.99

$1.51

55%


El detalle completo lo tenéis en este artículo y en la página de precios.

LinkedInMessengerShare

Release de SQL Azure Import/Export

Written by Ibon Landa on January 24th, 2012. Posted in SQL Azure

Hoy mismo se ha anunciado la release de la funcionalidad de import y export, una más que útil funcionalidad para la exportación e importación de los datos de información de una base de datos SQL Azure.

Así mismo también se presenta un ejemplo de uso, una herramienta cliente, que puede ser más que útil para poder importar el contenido exportado al storage (el bacpac) a un servidor SQL Server on-premise y que también permite generar un bacpac a partir de una base de datos on-premise para lugar poder importar el contenido de la misma a SQL Azure.

sqlazureimport

LinkedInMessengerShare

SQL Azure Q4 2011 Service Release

Written by Ibon Landa on December 17th, 2011. Posted in SQL Azure

Esta misma semana Microsoft ha publicado una nueva release de SQL Azure con interesantes y esperadas novedades. Aquí tenéis el anuncio oficial en blog de Windows Azure.

Las principales novedades son:

  • El límite de tamaño de una base de datos de SQL Azure Business Edition se ha incrementado de 50 a 150 GB.

El coste máximo para una base de datos individual, establecido en 354.56 € estadounidenses por mes de facturación, no se modifica.

Este límite de 354.56 € estadounidenses para SQL Azure Business Edition por base de datos y mes se basa en los cargos acumulados para el mes de facturación completo por cada base de datos. El límite de facturación permite escalar la base de datos de 50 a 150 GB sin coste adicional, lo que reduce el precio efectivo para volúmenes mayores.Por ejemplo, un cliente que use una base de datos de 150 GB durante un mes de facturación completo observará una reducción efectiva del precio de un 67%.

  • SQL Federation

SQL Azure Federation simplifica enormemente el escalado horizontal (sharding), posibilitando que el usuario pueda aumentar o disminuir las bases de datos de la aplicación de forma dinámica y sin que este hecho provoque una parada del servicio.

Es el usuario quién decide los miembros de la federación, es quién decide cuándo añadir o disminuir miembros y es quién decide en base a qué criterio debe realizarse la partición de la información.

Podéis encontrar más información aquí.

  • Nuevo portal de administración, basado en SL5 y con información muy interesante para la monitorización de base de datos.

SQL Azure - 1

  • Nueva CTP de la funcionalidad de export/import.

http://www.estoyenlanube.com/sql-azure-importexport/ 

  • Posibilidad de especificar el collation de la base de datos.
LinkedInMessengerShare