NuevoSQL = NoSQL+ACID

NuevoSQL = NoSQL+ACID
Hasta hace poco, Odnoklassniki almacenaba alrededor de 50 TB de datos procesados ​​en tiempo real en SQL Server. Para un volumen de este tipo, es casi imposible proporcionar un acceso rápido y confiable, e incluso tolerante a fallas, al centro de datos utilizando un DBMS SQL. Normalmente, en tales casos, se utiliza uno de los almacenamientos NoSQL, pero no todo se puede transferir a NoSQL: algunas entidades requieren garantías de transacción ACID.

Esto nos llevó al uso del almacenamiento NewSQL, es decir, un DBMS que proporciona tolerancia a fallas, escalabilidad y rendimiento de los sistemas NoSQL, pero al mismo tiempo mantiene las garantías ACID familiares a los sistemas clásicos. Hay pocos sistemas industriales de esta nueva clase en funcionamiento, por lo que nosotros mismos implementamos un sistema de este tipo y lo pusimos en operación comercial.

Cómo funciona y qué sucedió: lea debajo del corte.

Hoy en día, la audiencia mensual de Odnoklassniki es de más de 70 millones de visitantes únicos. Nosotros Estamos entre los cinco primeros redes sociales más grandes del mundo y entre los veinte sitios en los que los usuarios pasan más tiempo. La infraestructura de OK maneja cargas muy elevadas: más de un millón de solicitudes HTTP/segundo por frente. Partes de una flota de servidores de más de 8000 unidades están ubicadas unas cerca de otras, en cuatro centros de datos de Moscú, lo que permite una latencia de red de menos de 1 ms entre ellas.

Usamos Cassandra desde 2010, comenzando con la versión 0.6. Hoy en día hay varias docenas de clusters en funcionamiento. El clúster más rápido procesa más de 4 millones de operaciones por segundo y el más grande almacena 260 TB.

Sin embargo, todos estos son clústeres NoSQL ordinarios que se utilizan para almacenamiento. débilmente coordinado datos. Queríamos reemplazar el principal almacenamiento consistente, Microsoft SQL Server, que se ha utilizado desde la fundación de Odnoklassniki. El almacenamiento constaba de más de 300 máquinas SQL Server Standard Edition, que contenían 50 TB de datos: entidades comerciales. Estos datos se modifican como parte de transacciones ACID y requieren alta consistencia.

Para distribuir datos entre nodos de SQL Server, utilizamos vertical y horizontal. fraccionamiento (fragmentación). Históricamente, utilizamos un esquema de fragmentación de datos simple: cada entidad estaba asociada con un token, una función del ID de la entidad. Las entidades con el mismo token se colocaron en el mismo servidor SQL. La relación maestro-detalle se implementó para que los tokens de los registros principal y secundario siempre coincidieran y estuvieran ubicados en el mismo servidor. En una red social, casi todos los registros se generan en nombre del usuario, lo que significa que todos los datos del usuario dentro de un subsistema funcional se almacenan en un servidor. Es decir, una transacción comercial casi siempre involucraba tablas de un servidor SQL, lo que hizo posible garantizar la coherencia de los datos utilizando transacciones ACID locales, sin la necesidad de utilizar lento y poco confiable transacciones ACID distribuidas.

Gracias a la fragmentación y a acelerar SQL:

  • No utilizamos restricciones de clave externa, ya que al fragmentar el ID de la entidad puede estar ubicado en otro servidor.
  • No utilizamos procedimientos almacenados ni activadores debido a la carga adicional en la CPU DBMS.
  • No utilizamos JOIN debido a todo lo anterior y a muchas lecturas aleatorias del disco.
  • Fuera de una transacción, utilizamos el nivel de aislamiento de lectura no confirmada para reducir los interbloqueos.
  • Solo realizamos transacciones cortas (en promedio, menos de 100 ms).
  • No utilizamos ACTUALIZAR y ELIMINAR de varias filas debido a la gran cantidad de interbloqueos; actualizamos solo un registro a la vez.
  • Siempre realizamos consultas solo en índices: para nosotros, una consulta con un plan de escaneo de tabla completo significa sobrecargar la base de datos y provocar que falle.

Estos pasos nos permitieron exprimir casi el máximo rendimiento de los servidores SQL. Sin embargo, los problemas se hicieron cada vez más numerosos. Mirémoslos.

Problemas con SQL

  • Dado que utilizamos fragmentación autoescrita, los administradores agregaron nuevos fragmentos manualmente. Durante todo este tiempo, las réplicas de datos escalables no atendieron las solicitudes.
  • A medida que crece el número de registros en la tabla, la velocidad de inserción y modificación disminuye; al agregar índices a una tabla existente, la velocidad disminuye en un factor; la creación y recreación de índices ocurre con tiempo de inactividad.
  • Tener una pequeña cantidad de Windows para SQL Server en producción dificulta la gestión de la infraestructura.

Pero el principal problema es

Tolerancia a fallos

El servidor SQL clásico tiene poca tolerancia a fallos. Digamos que tiene un solo servidor de base de datos y falla una vez cada tres años. Durante este tiempo, el sitio estará inactivo durante 20 minutos, lo cual es aceptable. Si tiene 64 servidores, el sitio dejará de funcionar una vez cada tres semanas. Y si tiene 200 servidores, entonces el sitio no funciona todas las semanas. Esto es un problema.

¿Qué se puede hacer para mejorar la tolerancia a fallos de un servidor SQL? Wikipedia nos invita a construir clúster de alta disponibilidad: donde en caso de falla de alguno de los componentes existe uno de respaldo.

Esto requiere una flota de equipos costosos: numerosas duplicaciones, fibra óptica, almacenamiento compartido y la inclusión de una reserva no funciona de manera confiable: alrededor del 10% de las conmutaciones terminan con la falla del nodo de respaldo como un tren detrás del nodo principal.

Pero la principal desventaja de un clúster de alta disponibilidad es su disponibilidad nula si falla el centro de datos en el que se encuentra. Odnoklassniki tiene cuatro centros de datos y debemos garantizar el funcionamiento en caso de fallo total en uno de ellos.

Para esto podríamos usar Multimaestro replicación integrada en SQL Server. Esta solución es mucho más cara debido al coste del software y adolece de problemas bien conocidos con la replicación: retrasos impredecibles en las transacciones con la replicación sincrónica y retrasos en la aplicación de las replicaciones (y, como resultado, modificaciones perdidas) con la replicación asincrónica. el implícito resolución manual de conflictos hace que esta opción sea completamente inaplicable para nosotros.

Todos estos problemas requerían una solución radical y comenzamos a analizarlos en detalle. Aquí debemos familiarizarnos con lo que hace principalmente SQL Server: transacciones.

Transacción sencilla

Consideremos la transacción más simple, desde el punto de vista de un programador de SQL aplicado: agregar una foto a un álbum. Los álbumes y las fotografías se guardan en placas diferentes. El álbum cuenta con un mostrador de fotografías público. Entonces dicha transacción se divide en los siguientes pasos:

  1. Bloqueamos el álbum por clave.
  2. Crea una entrada en la tabla de fotos.
  3. Si la foto tiene estado público, agregue un contador de fotos públicas al álbum, actualice el registro y confirme la transacción.

O en pseudocódigo:

TX.start("Albums", id);
Album album = albums.lock(id);
Photo photo = photos.create(…);

if (photo.status == PUBLIC ) {
    album.incPublicPhotosCount();
}
album.update();

TX.commit();

Vemos que el escenario más común para una transacción comercial es leer datos de la base de datos en la memoria del servidor de aplicaciones, cambiar algo y guardar los nuevos valores en la base de datos. Normalmente en una transacción de este tipo actualizamos varias entidades, varias tablas.

Al ejecutar una transacción, puede ocurrir una modificación simultánea de los mismos datos desde otro sistema. Por ejemplo, Antispam puede decidir que el usuario es sospechoso de alguna manera y, por lo tanto, todas las fotos del usuario ya no deberían ser públicas, deben enviarse para moderación, lo que significa cambiar photo.status a algún otro valor y desactivar los contadores correspondientes. Obviamente, si esta operación ocurre sin garantías de atomicidad de aplicación y aislamiento de modificaciones competidoras, como en ACID, entonces el resultado no será el que se necesita: o el contador de fotos mostrará el valor incorrecto o no todas las fotos se enviarán para moderación.

A lo largo de toda la existencia de Odnoklassniki se ha escrito una gran cantidad de código similar, que manipula varias entidades comerciales dentro de una sola transacción. Basado en la experiencia de migraciones a NoSQL desde Coherencia eventual Sabemos que el mayor desafío (y la inversión de tiempo) proviene del desarrollo de código para mantener la coherencia de los datos. Por lo tanto, consideramos que el requisito principal para el nuevo almacenamiento era la provisión de transacciones ACID reales para la lógica de la aplicación.

Otros requisitos no menos importantes fueron:

  • Si el centro de datos falla, debe estar disponible tanto la lectura como la escritura en el nuevo almacenamiento.
  • Mantener el ritmo de desarrollo actual. Es decir, cuando se trabaja con un nuevo repositorio, la cantidad de código debe ser aproximadamente la misma, no debería ser necesario agregar nada al repositorio, desarrollar algoritmos para resolver conflictos, mantener índices secundarios, etc.
  • La velocidad del nuevo almacenamiento tenía que ser bastante alta, tanto en la lectura de datos como en el procesamiento de transacciones, lo que significaba que no eran aplicables soluciones académicamente rigurosas, universales pero lentas, como, por ejemplo, confirmaciones de dos fases.
  • Escalado automático sobre la marcha.
  • Utilizando servidores baratos y habituales, sin necesidad de adquirir hardware exótico.
  • Posibilidad de desarrollo de almacenamiento por parte de desarrolladores de la empresa. Es decir, se dio prioridad a soluciones propietarias o de código abierto, preferentemente en Java.

Decisiones decisiones

Analizando posibles soluciones, llegamos a dos posibles opciones de arquitectura:

La primera es tomar cualquier servidor SQL e implementar la tolerancia a fallas, el mecanismo de escalado, el clúster de conmutación por error, la resolución de conflictos y las transacciones ACID distribuidas, confiables y rápidas necesarias. Calificamos esta opción como muy no trivial y que requiere mucha mano de obra.

La segunda opción es tomar un almacenamiento NoSQL listo para usar con escalado implementado, un clúster de conmutación por error, resolución de conflictos e implementar transacciones y SQL usted mismo. A primera vista, incluso la tarea de implementar SQL, sin mencionar las transacciones ACID, parece una tarea que llevará años. Pero luego nos dimos cuenta de que el conjunto de funciones SQL que utilizamos en la práctica está tan lejos de ANSI SQL como Casandra CQL lejos de ANSI SQL. Al observar aún más de cerca CQL, nos dimos cuenta de que se acercaba bastante a lo que necesitábamos.

Casandra y CQL

Entonces, ¿qué tiene de interesante Cassandra, qué capacidades tiene?

En primer lugar, aquí puede crear tablas que admitan varios tipos de datos; puede SELECCIONAR o ACTUALIZAR en la clave principal.

CREATE TABLE photos (id bigint KEY, owner bigint,…);
SELECT * FROM photos WHERE id=?;
UPDATE photos SET … WHERE id=?;

Para garantizar la coherencia de los datos de réplica, Cassandra utiliza enfoque de quórum. En el caso más simple, esto significa que cuando se colocan tres réplicas de la misma fila en diferentes nodos del clúster, la escritura se considera exitosa si la mayoría de los nodos (es decir, dos de tres) confirmaron el éxito de esta operación de escritura. . Los datos de la fila se consideran consistentes si, durante la lectura, la mayoría de los nodos fueron sondeados y confirmados. Por lo tanto, con tres réplicas, se garantiza una coherencia de datos completa e instantánea si falla un nodo. Este enfoque nos permitió implementar un esquema aún más confiable: enviar siempre solicitudes a las tres réplicas, esperando una respuesta de las dos más rápidas. En este caso se descarta la respuesta tardía de la tercera réplica. Un nodo que tarda en responder puede tener problemas graves: frenos, recolección de basura en la JVM, recuperación directa de memoria en el kernel de Linux, falla de hardware, desconexión de la red. Sin embargo, esto no afecta de ninguna manera las operaciones o datos del cliente.

El enfoque cuando contactamos tres nodos y recibimos una respuesta de dos se llama especulación: se envía una solicitud de réplicas adicionales incluso antes de que "caiga".

Otro beneficio de Cassandra es Batchlog, un mecanismo que garantiza que un lote de cambios realizados se apliquen por completo o no se apliquen en absoluto. Esto nos permite resolver A en ACID: atomicidad lista para usar.

Lo más parecido a las transacciones en Cassandra son las llamadas “transacciones ligeras". Pero están lejos de ser transacciones ACID “reales”: de hecho, esta es una oportunidad para hacer CAS sobre datos de un solo registro, mediante consenso utilizando el protocolo Paxos de peso pesado. Por tanto, la velocidad de dichas transacciones es baja.

Lo que nos faltaba en Cassandra

Entonces, tuvimos que implementar transacciones ACID reales en Cassandra. Con el cual podríamos implementar fácilmente otras dos características convenientes del DBMS clásico: índices rápidos y consistentes, que nos permitirían realizar selecciones de datos no solo por la clave principal, y un generador regular de ID monótonos de incremento automático.

Cono

Así nació un nuevo DBMS Cono, que consta de tres tipos de nodos de servidor:

  • Almacenamiento: servidores Cassandra (casi) estándar responsables de almacenar datos en discos locales. A medida que crece la carga y el volumen de datos, su cantidad se puede escalar fácilmente a decenas y cientos.
  • Coordinadores de transacciones: garantizan la ejecución de las transacciones.
  • Los clientes son servidores de aplicaciones que implementan operaciones comerciales e inician transacciones. Puede haber miles de clientes de este tipo.

NuevoSQL = NoSQL+ACID

Los servidores de todo tipo forman parte de un clúster común, utilizan el protocolo de mensajes interno de Cassandra para comunicarse entre sí y chismes para intercambiar información del cluster. Con Heartbeat, los servidores aprenden sobre fallas mutuas, mantienen un esquema de datos único: tablas, su estructura y replicación; esquema de partición, topología del clúster, etc.

Clientela

NuevoSQL = NoSQL+ACID

En lugar de controladores estándar, se utiliza el modo Fat Client. Dicho nodo no almacena datos, pero puede actuar como coordinador para la ejecución de solicitudes, es decir, el propio Cliente actúa como coordinador de sus solicitudes: consulta réplicas de almacenamiento y resuelve conflictos. Este no sólo es más fiable y rápido que el controlador estándar, que requiere comunicación con un coordinador remoto, sino que también permite controlar la transmisión de solicitudes. Fuera de una transacción abierta en el cliente, las solicitudes se envían a los repositorios. Si el cliente ha abierto una transacción, todas las solicitudes dentro de la transacción se envían al coordinador de transacciones.
NuevoSQL = NoSQL+ACID

Coordinador de transacciones C*One

El coordinador es algo que implementamos para C*One desde cero. Es responsable de gestionar las transacciones, los bloqueos y el orden en que se aplican las transacciones.

Para cada transacción atendida, el coordinador genera una marca de tiempo: cada transacción posterior es mayor que la transacción anterior. Dado que el sistema de resolución de conflictos de Cassandra se basa en marcas de tiempo (de dos registros en conflicto, el que tiene la última marca de tiempo se considera actual), el conflicto siempre se resolverá a favor de la transacción posterior. Así implementamos reloj lamport - una forma económica de resolver conflictos en un sistema distribuido.

Cerraduras

Para garantizar el aislamiento, decidimos utilizar el método más simple: bloqueos pesimistas basados ​​en la clave principal del registro. En otras palabras, en una transacción, primero se debe bloquear un registro, solo luego leerlo, modificarlo y guardarlo. Sólo después de una confirmación exitosa se puede desbloquear un registro para que las transacciones competidoras puedan usarlo.

Implementar dicho bloqueo es sencillo en un entorno no distribuido. En un sistema distribuido, hay dos opciones principales: implementar el bloqueo distribuido en el clúster o distribuir las transacciones para que las transacciones que involucran el mismo registro siempre sean atendidas por el mismo coordinador.

Dado que en nuestro caso los datos ya están distribuidos entre grupos de transacciones locales en SQL, se decidió asignar grupos de transacciones locales a los coordinadores: un coordinador realiza todas las transacciones con tokens del 0 al 9, el segundo, con tokens del 10 al 19, etcétera. Como resultado, cada una de las instancias del coordinador se convierte en el maestro del grupo de transacciones.

Luego, los bloqueos se pueden implementar en forma de un HashMap banal en la memoria del coordinador.

Fallos del coordinador

Dado que un coordinador atiende exclusivamente a un grupo de transacciones, es muy importante determinar rápidamente el hecho de su falla para que el segundo intento de ejecutar la transacción expire. Para que esto sea rápido y confiable, utilizamos un protocolo de escucha de quórum completamente conectado:

Cada centro de datos alberga al menos dos nodos coordinadores. Periódicamente, cada coordinador envía un mensaje de latido a los demás coordinadores y les informa sobre su funcionamiento, así como qué mensajes de latido recibió de qué coordinadores del clúster la última vez.

NuevoSQL = NoSQL+ACID

Al recibir información similar de otros como parte de sus mensajes de latido, cada coordinador decide por sí mismo qué nodos del clúster están funcionando y cuáles no, guiado por el principio de quórum: si el nodo X ha recibido información de la mayoría de los nodos del clúster sobre el normal recepción de mensajes del nodo Y, entonces Y funciona. Y viceversa, tan pronto como la mayoría informa que faltan mensajes del nodo Y, Y se ha negado. Es curioso que si el quórum informa al nodo X que ya no recibe mensajes de él, entonces el propio nodo X considerará que ha fallado.

Los mensajes de latido se envían con alta frecuencia, aproximadamente 20 veces por segundo, con un período de 50 ms. En Java, es difícil garantizar la respuesta de la aplicación dentro de los 50 ms debido a la duración comparable de las pausas causadas por el recolector de basura. Pudimos lograr este tiempo de respuesta utilizando el recolector de basura G1, que nos permite especificar un objetivo durante la duración de las pausas de GC. Sin embargo, a veces, muy raramente, las pausas del colector superan los 50 ms, lo que puede provocar una detección falsa de fallos. Para evitar que esto suceda, el coordinador no informa de un fallo de un nodo remoto cuando desaparece el primer mensaje de latido del mismo, sólo si han desaparecido varios seguidos, así es como logramos detectar un fallo del nodo coordinador en 200 EM.

Pero no basta con comprender rápidamente qué nodo ha dejado de funcionar. Necesitamos hacer algo al respecto.

Reserva

El esquema clásico implica, en caso de un fracaso maestro, iniciar una nueva elección utilizando uno de de moda universal algoritmos. Sin embargo, estos algoritmos tienen problemas bien conocidos con la convergencia temporal y la duración del proceso electoral en sí. Pudimos evitar tales retrasos adicionales utilizando un esquema de reemplazo de coordinadores en una red completamente conectada:

NuevoSQL = NoSQL+ACID

Digamos que queremos ejecutar una transacción en el grupo 50. Determinemos de antemano el esquema de reemplazo, es decir, qué nodos ejecutarán transacciones en el grupo 50 en caso de falla del coordinador principal. Nuestro objetivo es mantener la funcionalidad del sistema en caso de una falla del centro de datos. Determinemos que la primera reserva será un nodo de otro centro de datos y la segunda reserva será un nodo de un tercero. Este esquema se selecciona una vez y no cambia hasta que cambia la topología del clúster, es decir, hasta que ingresan nuevos nodos (lo que sucede muy raramente). El procedimiento para seleccionar un nuevo maestro activo si el antiguo falla será siempre el siguiente: la primera reserva pasará a ser maestro activo, y si ha dejado de funcionar, la segunda reserva pasará a ser maestro activo.

Este esquema es más confiable que el algoritmo universal, ya que para activar un nuevo maestro basta con determinar el fallo del anterior.

Pero, ¿cómo entenderán los clientes qué maestro está trabajando ahora? Es imposible enviar información a miles de clientes en 50 ms. Es posible una situación en la que un cliente envía una solicitud para abrir una transacción, sin saber aún que este maestro ya no está funcionando y la solicitud expirará. Para evitar que esto suceda, los clientes envían especulativamente una solicitud para abrir una transacción al maestro del grupo y a sus dos reservas a la vez, pero solo el que es el maestro activo en este momento responderá a esta solicitud. El cliente realizará todas las comunicaciones posteriores dentro de la transacción únicamente con el maestro activo.

Los maestros de respaldo colocan las solicitudes recibidas de transacciones que no son suyas en la cola de transacciones no nacidas, donde se almacenan durante algún tiempo. Si el maestro activo muere, el nuevo maestro procesa solicitudes para abrir transacciones desde su cola y responde al cliente. Si el cliente ya abrió una transacción con el antiguo maestro, entonces se ignora la segunda respuesta (y, obviamente, dicha transacción no se completará y el cliente la repetirá).

Cómo funciona la transacción

Digamos que un cliente envió una solicitud al coordinador para abrir una transacción para tal o cual entidad con tal o cual clave primaria. El coordinador bloquea esta entidad y la coloca en la tabla de bloqueo de la memoria. Si es necesario, el coordinador lee esta entidad del almacenamiento y almacena los datos resultantes en un estado de transacción en la memoria del coordinador.

NuevoSQL = NoSQL+ACID

Cuando un cliente quiere cambiar datos en una transacción, envía una solicitud al coordinador para modificar la entidad, y el coordinador coloca los nuevos datos en la tabla de estado de la transacción en la memoria. Esto completa la grabación; no se realiza ninguna grabación en el almacenamiento.

NuevoSQL = NoSQL+ACID

Cuando un cliente solicita sus propios datos modificados como parte de una transacción activa, el coordinador actúa de la siguiente manera:

  • si el ID ya está en la transacción, entonces los datos se toman de la memoria;
  • si el ID no está en la memoria, los datos faltantes se leen de los nodos de almacenamiento, se combinan con los que ya están en la memoria y el resultado se entrega al cliente.

Por lo tanto, el cliente puede leer sus propios cambios, pero otros clientes no ven estos cambios porque están almacenados solo en la memoria del coordinador; aún no están en los nodos de Cassandra.

NuevoSQL = NoSQL+ACID

Cuando el cliente envía una confirmación, el coordinador guarda el estado que estaba en la memoria del servicio en un lote registrado y lo envía como un lote registrado al almacenamiento de Cassandra. Las tiendas hacen todo lo necesario para garantizar que este paquete se aplique de forma atómica (completamente) y devuelven una respuesta al coordinador, quien libera los bloqueos y confirma el éxito de la transacción al cliente.

NuevoSQL = NoSQL+ACID

Y para revertir, el coordinador solo necesita liberar la memoria ocupada por el estado de la transacción.

Como resultado de las mejoras anteriores, implementamos los principios ACID:

  • Atomicidad. Esto es una garantía de que ninguna transacción quedará registrada parcialmente en el sistema, o se completarán todas sus suboperaciones o no se completará ninguna. Nos adherimos a este principio mediante lotes registrados en Cassandra.
  • Coherencia. Cada transacción exitosa, por definición, registra solo resultados válidos. Si, después de abrir una transacción y realizar parte de las operaciones, se descubre que el resultado no es válido, se realiza una reversión.
  • Aislamiento. Cuando se ejecuta una transacción, las transacciones concurrentes no deberían afectar su resultado. Las transacciones en competencia se aíslan mediante bloqueos pesimistas en el coordinador. Para lecturas fuera de una transacción, el principio de aislamiento se observa en el nivel de lectura confirmada.
  • Estabilidad. Independientemente de los problemas en los niveles inferiores (apagón del sistema, falla del hardware), los cambios realizados por una transacción completada con éxito deben permanecer preservados cuando se reanuden las operaciones.

Lectura por índices

Tomemos una tabla simple:

CREATE TABLE photos (
id bigint primary key,
owner bigint,
modified timestamp,
…)

Tiene ID (clave primaria), propietario y fecha de modificación. Debe realizar una solicitud muy simple: seleccione los datos del propietario con la fecha de cambio "para el último día".

SELECT *
WHERE owner=?
AND modified>?

Para que una consulta de este tipo se procese rápidamente, en un DBMS SQL clásico es necesario crear un índice por columnas (propietario, modificado). ¡Podemos hacerlo con bastante facilidad, ya que ahora contamos con garantías ACID!

Índices en C*One

Existe una tabla fuente con fotografías en las que el ID del registro es la clave principal.

NuevoSQL = NoSQL+ACID

Para un índice, C*One crea una nueva tabla que es una copia de la original. La clave es la misma que la expresión de índice y también incluye la clave principal del registro de la tabla fuente:

NuevoSQL = NoSQL+ACID

Ahora la consulta de "propietario del último día" se puede reescribir como una selección de otra tabla:

SELECT * FROM i1_test
WHERE owner=?
AND modified>?

El coordinador mantiene automáticamente la coherencia de los datos en las fotografías de la tabla de origen y la tabla de índice i1. Basándose únicamente en el esquema de datos, cuando se recibe un cambio, el coordinador genera y almacena un cambio no solo en la tabla principal, sino también en copias. No se realizan acciones adicionales en la tabla de índice, no se leen los registros y no se utilizan bloqueos. Es decir, agregar índices casi no consume recursos y prácticamente no tiene ningún efecto en la velocidad de aplicación de modificaciones.

Usando ACID, pudimos implementar índices similares a SQL. Son consistentes, escalables, rápidos, componibles y están integrados en el lenguaje de consulta CQL. No se requieren cambios en el código de la aplicación para admitir índices. Todo es tan simple como en SQL. Y lo más importante, los índices no afectan la velocidad de ejecución de las modificaciones a la tabla de transacciones original.

Qué pasó

Desarrollamos C*One hace tres años y lo lanzamos a operación comercial.

¿Qué obtuvimos al final? Evaluemos esto usando el ejemplo del subsistema de procesamiento y almacenamiento de fotografías, uno de los tipos de datos más importantes en una red social. No hablamos de los cuerpos de las fotografías en sí, sino de todo tipo de metainformación. Ahora Odnoklassniki tiene alrededor de 20 mil millones de registros de este tipo, el sistema procesa 80 mil solicitudes de lectura por segundo, hasta 8 mil transacciones ACID por segundo asociadas con la modificación de datos.

Cuando usamos SQL con factor de replicación = 1 (pero en RAID 10), la metainformación de la fotografía se almacenó en un grupo de alta disponibilidad de 32 máquinas que ejecutaban Microsoft SQL Server (más 11 copias de seguridad). También se asignaron 10 servidores para almacenar copias de seguridad. Un total de 50 coches caros. Al mismo tiempo, el sistema funcionó a carga nominal, sin reserva.

Después de migrar al nuevo sistema, recibimos un factor de replicación = 3: una copia en cada centro de datos. El sistema consta de 63 nodos de almacenamiento Cassandra y 6 máquinas coordinadoras, para un total de 69 servidores. Pero estas máquinas son mucho más baratas, su coste total es aproximadamente el 30% del coste de un sistema SQL. Al mismo tiempo, la carga se mantiene al 30%.

Con la introducción de C*One, la latencia también disminuyó: en SQL, una operación de escritura tardaba unos 4,5 ms. En C*One: aproximadamente 1,6 ms. La duración de la transacción es en promedio inferior a 40 ms, la confirmación se completa en 2 ms, la duración de lectura y escritura es en promedio de 2 ms. Percentil 99: solo 3-3,1 ms, el número de tiempos de espera se ha reducido 100 veces, todo debido al uso generalizado de la especulación.

Hasta ahora, la mayoría de los nodos de SQL Server han sido desmantelados; los nuevos productos se están desarrollando únicamente utilizando C*One. Adaptamos C*One para trabajar en nuestra nube una nube, lo que permitió acelerar el despliegue de nuevos clústeres, simplificar la configuración y automatizar la operación. Sin el código fuente, hacer esto sería mucho más difícil y engorroso.

Ahora estamos trabajando para transferir nuestras otras instalaciones de almacenamiento a la nube, pero esa es una historia completamente diferente.

Fuente: habr.com

Añadir un comentario