{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency","title":"ScyllaDB: Arquitectura Shard-Per-Core, Benchmarks de Latencia y Migración desde Cassandra","description":"Análisis de la arquitectura shard-per-core de ScyllaDB, los internals del framework Seastar, y benchmarks reales de latencia p99 contra Apache Cassandra. Incluye guía de migración probada en producción.","language":"es","datePublished":"2025-11-20","dateModified":null,"contentFormat":"text/markdown","content":"Databases\n\n# ScyllaDB: Arquitectura Shard-Per-Core, Benchmarks de Latencia y Migración desde Cassandra\n\n2025-11-208 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/scylladb-cassandra-compatible-low-latency>)\n\n## El Problema con la Arquitectura de Cassandra\n\nSeamos honestos con lo que pasa dentro de un nodo de Cassandra. Teners un proceso JVM manejando decenas de gigas de heap, corriendo en una máquina con 16-32 cores. Cada lectura y escritura pasa por estructuras de datos compartidas protegidas por locks. El garbage collector del JVM periódicamente decide pausar todo, y \"todo\" significa que los 32 cores dejan de hacer trabajo útil simultáneamente.\n\nAcá va una entrada típica del log del GC de Cassandra que ilustra el problema:\n\n```\nGC pause (G1 Evacuation Pause) 14.2GB->9.8GB 247.483ms\n```\n\n247 milisegundos. Eso no es un outlier de tail latency, es un cuarto de segundo donde tu base de datos simplemente no responde. Y esto era en un cluster tuneado con G1GC, 8GB de new gen, y regiones cuidadosamente dimensionadas.\n\nEl problema fundamental es que el modelo de threading de Cassandra se diseñó a mediados de los 2000. Usa un approach de thread-per-core con estado mutable compartido, y después le pone un runtime con garbage collection encima. Cada decisión arquitectural se acumula:\n\n```\nThread pool executor (WRITE stage) → Memtable compartida → Contención de locks\nThread pool executor (READ stage) → Row cache compartido → Contención de locks\nJVM GC → Stop-the-world → Todos los threads pausan → Pico de latencia\n```\n\n## Seastar: El Framework Shard-Per-Core\n\nScyllaDB está construido sobre el framework Seastar, que originalmente fue desarrollado para networking de alto rendimiento (potenciaba aplicaciones basadas en DPDK antes de que ScyllaDB existiera). Seastar implementa una arquitectura **shard-per-core** que elimina el estado compartido por completo.\n\nEl modelo mental es así. En una máquina de 16 cores:\n\n```\nCore 0: [Shard 0] → memtable propia, cache propio, cola I/O propia, conexiones propias\nCore 1: [Shard 1] → memtable propia, cache propio, cola I/O propia, conexiones propias\nCore 2: [Shard 2] → memtable propia, cache propio, cola I/O propia, conexiones propias\n...\nCore 15: [Shard 15] → memtable propia, cache propio, cola I/O propia, conexiones propias\n```\n\nCada shard es una mini-base-de-datos completa. No hay **locks**, porque no hay nada compartido que lockear. Cuando datos necesitan moverse entre shards (que pasa durante range scans o cuando un request llega al shard equivocado), Seastar usa message passing explícito, no memoria compartida.\n\nEl reactor loop de Seastar en cada core se ve más o menos así:\n\n```cpp\nvoid reactor::run() {\n while (true) {\n // Pollear I/O completado (sin syscall si se utiliza io_uring o AIO)\n poll_io();\n\n // Correr tareas listas de la cola de este shard\n run_tasks();\n\n // Pollear red (epoll o DPDK, según config)\n poll_network();\n\n // Chequear timers\n expire_timers();\n }\n}\n```\n\nEsto es multitasking cooperativo. No hay context switches, no hay migraciones de threads, no hay overhead del scheduler. Una tarea corre hasta que voluntariamente hace yield (típicamente en un boundary de I/O), y la siguiente tarea de ese shard arranca. El caché de CPU se mantiene caliente porque el mismo core siempre procesa los mismos datos.\n\n## Shard-Per-Core en Práctica: Qué Significa para las Queries\n\nCuando una query CQL llega a un nodo de ScyllaDB, el coordinador determina qué shard es dueño de la partition key usando un hash:\n\n```cpp\nunsigned shard_of(const partition_key& pk) {\n auto token = partitioner().get_token(pk);\n return token.raw() % smp::count; // simplificado\n}\n```\n\nSi el request cae en el core del shard correcto, el procesamiento es completamente local, sin comunicación cross-core, sin cache line bouncing, sin penalidades NUMA. Los drivers shard-aware que ScyllaDB provee para Java, Go, Python y Rust implementan este mismo hashing del lado del cliente:\n\n```python\n# Driver shard-aware de ScyllaDB para Python\nfrom cassandra.cluster import Cluster\nfrom cassandra.policies import TokenAwarePolicy, DCAwareRoundRobinPolicy\n\ncluster = Cluster(\n ['scylla-node-1', 'scylla-node-2', 'scylla-node-3'],\n load_balancing_policy=TokenAwarePolicy(DCAwareRoundRobinPolicy()),\n shard_aware_options=ShardAwareOptions()\n)\n```\n\nCon ruteo shard-aware, el driver abre una conexión por shard por nodo. En un cluster de 3 nodos con 16 cores cada uno, son 48 conexiones, pero cada conexión va directo al shard correcto, eliminando el overhead del hop del coordinador.\n\n## Comparación Real de Latencia p99\n\nEstos benchmarks corrieron en hardware idéntico, 3 nodos, cada uno con 16 vCPUs (c5d.4xlarge en [AWS](<https://aws.com/>)), 32GB RAM, NVMe local. Mismo schema, mismo dataset (800M particiones, ~2TB), mismo workload YCSB.\n\n```\nWorkload B (95% lectura, 5% escritura), 50K ops/seg sostenido\n\n p50 p95 p99 p99.9\nCassandra 4.1 1.2ms 4.8ms 28.3ms 312ms\nScyllaDB 5.4 0.4ms 0.9ms 1.8ms 4.2ms\n```\n\nObservar el p99.9. Ese es 312ms en Cassandra? Eso es GC. No hay nada que puedas tunear para que desaparezca, es fundamental a la arquitectura. Los 4.2ms de ScyllaDB en p99.9 es jitter del I/O scheduling, y es predecible.\n\nEl write path es aún más dramático porque la memtable de Cassandra es un ConcurrentSkipListMap compartido que se pone contendido bajo cargas write-heavy:\n\n```\nWorkload A (50% lectura, 50% escritura), 50K ops/seg sostenido\n\n p50 p95 p99 p99.9\nCassandra 4.1 1.8ms 8.2ms 45.1ms 580ms\nScyllaDB 5.4 0.3ms 0.7ms 1.4ms 3.8ms\n```\n\n## La Historia de la Compactación\n\nCassandra viene con SizeTieredCompactionStrategy (STCS) y LeveledCompactionStrategy (LCS). Las dos tienen problemas conocidos: STCS causa amplificación de espacio temporal hasta 2x, LCS causa amplificación de escritura que mata el throughput.\n\nScyllaDB introdujo Incremental Compaction Strategy (ICS), que es su default y no tiene equivalente directo en Cassandra. Opera a una granularidad sub-SSTable, en vez de leer y reescribir SSTables enteras, trabaja sobre sorted runs dentro de las SSTables:\n\n```\nCompactación Tradicional (STCS):\n SSTable_A (4GB) + SSTable_B (4GB) → SSTable_C (8GB)\n Espacio temporal necesario: 16GB (input + output)\n\nCompactación Incremental (ICS):\n SSTable_A[run_1] + SSTable_B[run_1] → merged_run_1\n SSTable_A[run_2] + SSTable_B[run_2] → merged_run_2\n Espacio temporal necesario: ~1GB (un run a la vez)\n```\n\nEsto significa que es posible correr ScyllaDB al 50% de utilización de disco en vez de necesitar 50% libre para headroom de compactación. En un disco NVMe de 2TB, eso es un terabyte extra de almacenamiento usable. Un resultado óptimo.\n\n## Guía de Migración: Cassandra a ScyllaDB\n\nAcá va un proceso de migración probado, validado en múltiples entornos de producción. Es menos doloroso de lo esperado.\n\n### Paso 1: Verificación de Compatibilidad del Schema\n\nExportá tu schema de Cassandra y validalo contra ScyllaDB:\n\n```bash\ncqlsh cassandra-node -e \"DESCRIBE KEYSPACE my_keyspace\" > schema.cql\n\n# ScyllaDB soporta el 99% de CQL. Principales incompatibilidades:\n# - Materialized Views: soportadas pero con garantías de consistencia diferentes\n# - SASI indexes: no soportados, utilizar la implementación de secondary index de ScyllaDB\n# - Custom types de Java: necesitan UDTs equivalentes\n```\n\n### Paso 2: Streaming de SSTables\n\nPara datasets menores a 2TB, la carga directa de SSTables es el camino más rápido:\n\n```bash\n# En cada nodo de Cassandra, tomá un snapshot\nnodetool snapshot -t migration_snap my_keyspace\n\n# Copiá SSTables a los nodos de ScyllaDB (respetando token ranges)\nsstableloader -d scylla-node-1,scylla-node-2,scylla-node-3 \\\n /var/lib/cassandra/data/my_keyspace/my_table-<uuid>/snapshots/migration_snap/\n```\n\n### Paso 3: Validación\n\n```python\n# Comparar conteo de filas y checksums entre clusters\nimport hashlib\nfrom cassandra.cluster import Cluster\n\ndef checksum_partition_range(session, table, token_start, token_end):\n rows = session.execute(f\"\"\"\n SELECT * FROM {table}\n WHERE token(partition_key) >= {token_start}\n AND token(partition_key) < {token_end}\n \"\"\")\n h = hashlib.sha256()\n count = 0\n for row in rows:\n h.update(str(row).encode())\n count += 1\n return count, h.hexdigest()\n```\n\n### Paso 4: Cutover de Tráfico\n\nSe recomienda fuertemente una fase de shadow-traffic donde se envía reads a ambos clusters y se compara resultados:\n\n```python\nasync def dual_read(cassandra_session, scylla_session, query, params):\n cass_future = cassandra_session.execute_async(query, params)\n scylla_future = scylla_session.execute_async(query, params)\n\n cass_result = cass_future.result()\n scylla_result = scylla_future.result()\n\n if cass_result != scylla_result:\n log.error(f\"Mismatch en query {query}: cassandra={cass_result}, scylla={scylla_result}\")\n metrics.increment(\"dual_read.mismatch\")\n\n return scylla_result # servir desde ScyllaDB cuando estés seguro\n```\n\n## Diferencias Operacionales que Teners que Saber\n\nAlgunas diferencias operacionales importantes:\n\n**Manejo de memoria**: ScyllaDB usa toda la RAM disponible por default para su row cache y no necesita tuning de heap. No pongas límites de memoria a menos que estés compartiendo la máquina (cosa que no es recomendable, ver el tip de arriba).\n\n**Priorización de workloads**: ScyllaDB soporta service levels que te dejan priorizar workloads por usuario CQL. Esto es increíble para setups multi-tenant:\n\n```sql\nCREATE SERVICE LEVEL gold WITH timeout = 500ms AND workload_type = 'interactive';\nCREATE SERVICE LEVEL batch WITH timeout = 30s AND workload_type = 'batch';\n\nALTER ROLE analytics_user WITH SERVICE LEVEL batch;\nALTER ROLE api_user WITH SERVICE LEVEL gold;\n```\n\n**Repair**: El row-level repair de ScyllaDB es significativamente más rápido que el de Cassandra. Lo que tardaba 8 horas en un cluster comparable de Cassandra termina en 45 minutos en ScyllaDB, porque solo streamea las filas que realmente difieren en vez de SSTables enteras.\n\n## Cuándo NO Usar ScyllaDB\n\nScyllaDB no es la elección correcta para:\n\n- Datasets menores a 100GB donde PostgreSQL alcanza y sobra\n- Workloads que son 99% full-table scans (utilizar ClickHouse o DuckDB)\n- Equipos que necesitan el ecosistema de herramientas de Apache Cassandra sin modificación\n\nPero si se está corriendo Cassandra hoy y peleándote con tail latencies, pagando nodos extra para compensar pausas del GC, o gastando demasiado tiempo tuneando parámetros del JVM, ScyllaDB es la respuesta que aborda estos problemas directamente.\n\nLa migración no es gratis, pero los equipos consistentemente salen del otro lado corriendo menos nodos, con mejores latencias, y con significativamente menos trabajo operativo. Eso no es marketing, es la realidad de producción.\n\n## Preguntas frecuentes\n\n### ¿Puedo usar mis drivers de Cassandra existentes con ScyllaDB?\n\nSí. ScyllaDB implementa el protocolo nativo CQL, así que cualquier driver de Cassandra funciona de una. Pero los drivers shard-aware de ScyllaDB (disponibles para Java, Go, Python, Rust y C++) rutean requests directo al shard correcto, eliminando coordinación cross-shard y reduciendo la latencia p99 entre un 30-60%.\n\n### ¿Cómo logra ScyllaDB menor tail latency que Cassandra?\n\nTres mecanismos: la arquitectura shard-per-core elimina la contención de locks, el framework Seastar usa scheduling cooperativo sin context switches, y el I/O scheduler provee fairness por shard. No hay garbage collector, no hay pausas stop-the-world, no hay safepoints del JVM.\n\n### ¿Cuál es el esfuerzo real de migración desde un cluster de 12 nodos de Cassandra?\n\nTípicamente 3-6 semanas para una migración en producción incluyendo validación. El schema es casi 1:1 compatible. El mayor esfuerzo va a tuning de estrategias de compactación (la Incremental Compaction Strategy de ScyllaDB no tiene equivalente en Cassandra) y ajustar timeouts del lado del cliente porque ScyllaDB responde más rápido.\n\n## Tips\n\n1. **Tip:** Al migrar desde Cassandra, ejecutar ` nodetool snapshot ` y transferir SSTables directamente a ScyllaDB usando ` sstableloader `. Omitir la fase de dual-write si el dataset es menor a 2TB, no justifica la complejidad operacional.\n2. **Tip:** Configurar el flag ` --overprovisioned ` en ` false ` en producción y permitir que ScyllaDB sea dueño de toda la máquina. Compartir el servidor con otros workloads anula el modelo shard-per-core y produce picos de latencia impredecibles.\n3. **Tip:** Utilizar ` scylla-monitoring-stack ` desde el primer día. Los dashboards de Grafana incluyen métricas por shard que Cassandra no puede ofrecer, y ahorran semanas de debugging durante tormentas de compactación.\n\n- scylladb\n- cassandra\n- bases-de-datos-distribuidas\n- baja-latencia\n- seastar\n- cpp\n- nosql\n- shard-per-core\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"ScyllaDB: Arquitectura Shard-Per-Core, Benchmarks de Latencia y Migración desde Cassandra","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#title"},{"level":2,"text":"El Problema con la Arquitectura de Cassandra","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#el-problema-con-la-arquitectura-de-cassandra"},{"level":2,"text":"Seastar: El Framework Shard-Per-Core","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#seastar-el-framework-shard-per-core"},{"level":2,"text":"Shard-Per-Core en Práctica: Qué Significa para las Queries","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#shard-per-core-en-prctica-qu-significa-para-las-queries"},{"level":2,"text":"Comparación Real de Latencia p99","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#comparacin-real-de-latencia-p99"},{"level":2,"text":"La Historia de la Compactación","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#la-historia-de-la-compactacin"},{"level":2,"text":"Guía de Migración: Cassandra a ScyllaDB","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#gua-de-migracin-cassandra-a-scylladb"},{"level":3,"text":"Paso 1: Verificación de Compatibilidad del Schema","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#paso-1-verificacin-de-compatibilidad-del-schema"},{"level":3,"text":"Paso 2: Streaming de SSTables","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#paso-2-streaming-de-sstables"},{"level":3,"text":"Paso 3: Validación","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#paso-3-validacin"},{"level":3,"text":"Paso 4: Cutover de Tráfico","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#paso-4-cutover-de-trfico"},{"level":2,"text":"Diferencias Operacionales que Teners que Saber","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#diferencias-operacionales-que-teners-que-saber"},{"level":2,"text":"Cuándo NO Usar ScyllaDB","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#cundo-no-usar-scylladb"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#questions"},{"level":3,"text":"¿Puedo usar mis drivers de Cassandra existentes con ScyllaDB?","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#question-1"},{"level":3,"text":"¿Cómo logra ScyllaDB menor tail latency que Cassandra?","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#question-2"},{"level":3,"text":"¿Cuál es el esfuerzo real de migración desde un cluster de 12 nodos de Cassandra?","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#article-tips"}],"sections":[{"heading":"ScyllaDB: Arquitectura Shard-Per-Core, Benchmarks de Latencia y Migración desde Cassandra","level":1,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency","content":"Databases"},{"heading":"ScyllaDB: Arquitectura Shard-Per-Core, Benchmarks de Latencia y Migración desde Cassandra","level":1,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#title","content":"2025-11-208 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/scylladb-cassandra-compatible-low-latency>)"},{"heading":"El Problema con la Arquitectura de Cassandra","level":2,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#el-problema-con-la-arquitectura-de-cassandra","content":"Seamos honestos con lo que pasa dentro de un nodo de Cassandra. Teners un proceso JVM manejando decenas de gigas de heap, corriendo en una máquina con 16-32 cores. Cada lectura y escritura pasa por estructuras de datos compartidas protegidas por locks. El garbage collector del JVM periódicamente decide pausar todo, y \"todo\" significa que los 32 cores dejan de hacer trabajo útil simultáneamente.\n\nAcá va una entrada típica del log del GC de Cassandra que ilustra el problema:\n\n```\nGC pause (G1 Evacuation Pause) 14.2GB->9.8GB 247.483ms\n```\n\n247 milisegundos. Eso no es un outlier de tail latency, es un cuarto de segundo donde tu base de datos simplemente no responde. Y esto era en un cluster tuneado con G1GC, 8GB de new gen, y regiones cuidadosamente dimensionadas.\n\nEl problema fundamental es que el modelo de threading de Cassandra se diseñó a mediados de los 2000. Usa un approach de thread-per-core con estado mutable compartido, y después le pone un runtime con garbage collection encima. Cada decisión arquitectural se acumula:\n\n```\nThread pool executor (WRITE stage) → Memtable compartida → Contención de locks\nThread pool executor (READ stage) → Row cache compartido → Contención de locks\nJVM GC → Stop-the-world → Todos los threads pausan → Pico de latencia\n```"},{"heading":"Seastar: El Framework Shard-Per-Core","level":2,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#seastar-el-framework-shard-per-core","content":"ScyllaDB está construido sobre el framework Seastar, que originalmente fue desarrollado para networking de alto rendimiento (potenciaba aplicaciones basadas en DPDK antes de que ScyllaDB existiera). Seastar implementa una arquitectura **shard-per-core** que elimina el estado compartido por completo.\n\nEl modelo mental es así. En una máquina de 16 cores:\n\n```\nCore 0: [Shard 0] → memtable propia, cache propio, cola I/O propia, conexiones propias\nCore 1: [Shard 1] → memtable propia, cache propio, cola I/O propia, conexiones propias\nCore 2: [Shard 2] → memtable propia, cache propio, cola I/O propia, conexiones propias\n...\nCore 15: [Shard 15] → memtable propia, cache propio, cola I/O propia, conexiones propias\n```\n\nCada shard es una mini-base-de-datos completa. No hay **locks**, porque no hay nada compartido que lockear. Cuando datos necesitan moverse entre shards (que pasa durante range scans o cuando un request llega al shard equivocado), Seastar usa message passing explícito, no memoria compartida.\n\nEl reactor loop de Seastar en cada core se ve más o menos así:\n\n```cpp\nvoid reactor::run() {\n while (true) {\n // Pollear I/O completado (sin syscall si se utiliza io_uring o AIO)\n poll_io();\n\n // Correr tareas listas de la cola de este shard\n run_tasks();\n\n // Pollear red (epoll o DPDK, según config)\n poll_network();\n\n // Chequear timers\n expire_timers();\n }\n}\n```\n\nEsto es multitasking cooperativo. No hay context switches, no hay migraciones de threads, no hay overhead del scheduler. Una tarea corre hasta que voluntariamente hace yield (típicamente en un boundary de I/O), y la siguiente tarea de ese shard arranca. El caché de CPU se mantiene caliente porque el mismo core siempre procesa los mismos datos."},{"heading":"Shard-Per-Core en Práctica: Qué Significa para las Queries","level":2,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#shard-per-core-en-prctica-qu-significa-para-las-queries","content":"Cuando una query CQL llega a un nodo de ScyllaDB, el coordinador determina qué shard es dueño de la partition key usando un hash:\n\n```cpp\nunsigned shard_of(const partition_key& pk) {\n auto token = partitioner().get_token(pk);\n return token.raw() % smp::count; // simplificado\n}\n```\n\nSi el request cae en el core del shard correcto, el procesamiento es completamente local, sin comunicación cross-core, sin cache line bouncing, sin penalidades NUMA. Los drivers shard-aware que ScyllaDB provee para Java, Go, Python y Rust implementan este mismo hashing del lado del cliente:\n\n```python\n# Driver shard-aware de ScyllaDB para Python\nfrom cassandra.cluster import Cluster\nfrom cassandra.policies import TokenAwarePolicy, DCAwareRoundRobinPolicy\n\ncluster = Cluster(\n ['scylla-node-1', 'scylla-node-2', 'scylla-node-3'],\n load_balancing_policy=TokenAwarePolicy(DCAwareRoundRobinPolicy()),\n shard_aware_options=ShardAwareOptions()\n)\n```\n\nCon ruteo shard-aware, el driver abre una conexión por shard por nodo. En un cluster de 3 nodos con 16 cores cada uno, son 48 conexiones, pero cada conexión va directo al shard correcto, eliminando el overhead del hop del coordinador."},{"heading":"Comparación Real de Latencia p99","level":2,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#comparacin-real-de-latencia-p99","content":"Estos benchmarks corrieron en hardware idéntico, 3 nodos, cada uno con 16 vCPUs (c5d.4xlarge en [AWS](<https://aws.com/>)), 32GB RAM, NVMe local. Mismo schema, mismo dataset (800M particiones, ~2TB), mismo workload YCSB.\n\n```\nWorkload B (95% lectura, 5% escritura), 50K ops/seg sostenido\n\n p50 p95 p99 p99.9\nCassandra 4.1 1.2ms 4.8ms 28.3ms 312ms\nScyllaDB 5.4 0.4ms 0.9ms 1.8ms 4.2ms\n```\n\nObservar el p99.9. Ese es 312ms en Cassandra? Eso es GC. No hay nada que puedas tunear para que desaparezca, es fundamental a la arquitectura. Los 4.2ms de ScyllaDB en p99.9 es jitter del I/O scheduling, y es predecible.\n\nEl write path es aún más dramático porque la memtable de Cassandra es un ConcurrentSkipListMap compartido que se pone contendido bajo cargas write-heavy:\n\n```\nWorkload A (50% lectura, 50% escritura), 50K ops/seg sostenido\n\n p50 p95 p99 p99.9\nCassandra 4.1 1.8ms 8.2ms 45.1ms 580ms\nScyllaDB 5.4 0.3ms 0.7ms 1.4ms 3.8ms\n```"},{"heading":"La Historia de la Compactación","level":2,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#la-historia-de-la-compactacin","content":"Cassandra viene con SizeTieredCompactionStrategy (STCS) y LeveledCompactionStrategy (LCS). Las dos tienen problemas conocidos: STCS causa amplificación de espacio temporal hasta 2x, LCS causa amplificación de escritura que mata el throughput.\n\nScyllaDB introdujo Incremental Compaction Strategy (ICS), que es su default y no tiene equivalente directo en Cassandra. Opera a una granularidad sub-SSTable, en vez de leer y reescribir SSTables enteras, trabaja sobre sorted runs dentro de las SSTables:\n\n```\nCompactación Tradicional (STCS):\n SSTable_A (4GB) + SSTable_B (4GB) → SSTable_C (8GB)\n Espacio temporal necesario: 16GB (input + output)\n\nCompactación Incremental (ICS):\n SSTable_A[run_1] + SSTable_B[run_1] → merged_run_1\n SSTable_A[run_2] + SSTable_B[run_2] → merged_run_2\n Espacio temporal necesario: ~1GB (un run a la vez)\n```\n\nEsto significa que es posible correr ScyllaDB al 50% de utilización de disco en vez de necesitar 50% libre para headroom de compactación. En un disco NVMe de 2TB, eso es un terabyte extra de almacenamiento usable. Un resultado óptimo."},{"heading":"Guía de Migración: Cassandra a ScyllaDB","level":2,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#gua-de-migracin-cassandra-a-scylladb","content":"Acá va un proceso de migración probado, validado en múltiples entornos de producción. Es menos doloroso de lo esperado."},{"heading":"Paso 1: Verificación de Compatibilidad del Schema","level":3,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#paso-1-verificacin-de-compatibilidad-del-schema","content":"Exportá tu schema de Cassandra y validalo contra ScyllaDB:\n\n```bash\ncqlsh cassandra-node -e \"DESCRIBE KEYSPACE my_keyspace\" > schema.cql\n\n# ScyllaDB soporta el 99% de CQL. Principales incompatibilidades:\n# - Materialized Views: soportadas pero con garantías de consistencia diferentes\n# - SASI indexes: no soportados, utilizar la implementación de secondary index de ScyllaDB\n# - Custom types de Java: necesitan UDTs equivalentes\n```"},{"heading":"Paso 2: Streaming de SSTables","level":3,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#paso-2-streaming-de-sstables","content":"Para datasets menores a 2TB, la carga directa de SSTables es el camino más rápido:\n\n```bash\n# En cada nodo de Cassandra, tomá un snapshot\nnodetool snapshot -t migration_snap my_keyspace\n\n# Copiá SSTables a los nodos de ScyllaDB (respetando token ranges)\nsstableloader -d scylla-node-1,scylla-node-2,scylla-node-3 \\\n /var/lib/cassandra/data/my_keyspace/my_table-<uuid>/snapshots/migration_snap/\n```"},{"heading":"Paso 3: Validación","level":3,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#paso-3-validacin","content":"```python\n# Comparar conteo de filas y checksums entre clusters\nimport hashlib\nfrom cassandra.cluster import Cluster\n\ndef checksum_partition_range(session, table, token_start, token_end):\n rows = session.execute(f\"\"\"\n SELECT * FROM {table}\n WHERE token(partition_key) >= {token_start}\n AND token(partition_key) < {token_end}\n \"\"\")\n h = hashlib.sha256()\n count = 0\n for row in rows:\n h.update(str(row).encode())\n count += 1\n return count, h.hexdigest()\n```"},{"heading":"Paso 4: Cutover de Tráfico","level":3,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#paso-4-cutover-de-trfico","content":"Se recomienda fuertemente una fase de shadow-traffic donde se envía reads a ambos clusters y se compara resultados:\n\n```python\nasync def dual_read(cassandra_session, scylla_session, query, params):\n cass_future = cassandra_session.execute_async(query, params)\n scylla_future = scylla_session.execute_async(query, params)\n\n cass_result = cass_future.result()\n scylla_result = scylla_future.result()\n\n if cass_result != scylla_result:\n log.error(f\"Mismatch en query {query}: cassandra={cass_result}, scylla={scylla_result}\")\n metrics.increment(\"dual_read.mismatch\")\n\n return scylla_result # servir desde ScyllaDB cuando estés seguro\n```"},{"heading":"Diferencias Operacionales que Teners que Saber","level":2,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#diferencias-operacionales-que-teners-que-saber","content":"Algunas diferencias operacionales importantes:\n\n**Manejo de memoria**: ScyllaDB usa toda la RAM disponible por default para su row cache y no necesita tuning de heap. No pongas límites de memoria a menos que estés compartiendo la máquina (cosa que no es recomendable, ver el tip de arriba).\n\n**Priorización de workloads**: ScyllaDB soporta service levels que te dejan priorizar workloads por usuario CQL. Esto es increíble para setups multi-tenant:\n\n```sql\nCREATE SERVICE LEVEL gold WITH timeout = 500ms AND workload_type = 'interactive';\nCREATE SERVICE LEVEL batch WITH timeout = 30s AND workload_type = 'batch';\n\nALTER ROLE analytics_user WITH SERVICE LEVEL batch;\nALTER ROLE api_user WITH SERVICE LEVEL gold;\n```\n\n**Repair**: El row-level repair de ScyllaDB es significativamente más rápido que el de Cassandra. Lo que tardaba 8 horas en un cluster comparable de Cassandra termina en 45 minutos en ScyllaDB, porque solo streamea las filas que realmente difieren en vez de SSTables enteras."},{"heading":"Cuándo NO Usar ScyllaDB","level":2,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#cundo-no-usar-scylladb","content":"ScyllaDB no es la elección correcta para:\n\n- Datasets menores a 100GB donde PostgreSQL alcanza y sobra\n- Workloads que son 99% full-table scans (utilizar ClickHouse o DuckDB)\n- Equipos que necesitan el ecosistema de herramientas de Apache Cassandra sin modificación\n\nPero si se está corriendo Cassandra hoy y peleándote con tail latencies, pagando nodos extra para compensar pausas del GC, o gastando demasiado tiempo tuneando parámetros del JVM, ScyllaDB es la respuesta que aborda estos problemas directamente.\n\nLa migración no es gratis, pero los equipos consistentemente salen del otro lado corriendo menos nodos, con mejores latencias, y con significativamente menos trabajo operativo. Eso no es marketing, es la realidad de producción."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#questions","content":""},{"heading":"¿Puedo usar mis drivers de Cassandra existentes con ScyllaDB?","level":3,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#question-1","content":"Sí. ScyllaDB implementa el protocolo nativo CQL, así que cualquier driver de Cassandra funciona de una. Pero los drivers shard-aware de ScyllaDB (disponibles para Java, Go, Python, Rust y C++) rutean requests directo al shard correcto, eliminando coordinación cross-shard y reduciendo la latencia p99 entre un 30-60%."},{"heading":"¿Cómo logra ScyllaDB menor tail latency que Cassandra?","level":3,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#question-2","content":"Tres mecanismos: la arquitectura shard-per-core elimina la contención de locks, el framework Seastar usa scheduling cooperativo sin context switches, y el I/O scheduler provee fairness por shard. No hay garbage collector, no hay pausas stop-the-world, no hay safepoints del JVM."},{"heading":"¿Cuál es el esfuerzo real de migración desde un cluster de 12 nodos de Cassandra?","level":3,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#question-3","content":"Típicamente 3-6 semanas para una migración en producción incluyendo validación. El schema es casi 1:1 compatible. El mayor esfuerzo va a tuning de estrategias de compactación (la Incremental Compaction Strategy de ScyllaDB no tiene equivalente en Cassandra) y ajustar timeouts del lado del cliente porque ScyllaDB responde más rápido."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#article-tips","content":"1. **Tip:** Al migrar desde Cassandra, ejecutar ` nodetool snapshot ` y transferir SSTables directamente a ScyllaDB usando ` sstableloader `. Omitir la fase de dual-write si el dataset es menor a 2TB, no justifica la complejidad operacional.\n2. **Tip:** Configurar el flag ` --overprovisioned ` en ` false ` en producción y permitir que ScyllaDB sea dueño de toda la máquina. Compartir el servidor con otros workloads anula el modelo shard-per-core y produce picos de latencia impredecibles.\n3. **Tip:** Utilizar ` scylla-monitoring-stack ` desde el primer día. Los dashboards de Grafana incluyen métricas por shard que Cassandra no puede ofrecer, y ahorran semanas de debugging durante tormentas de compactación.\n\n- scylladb\n- cassandra\n- bases-de-datos-distribuidas\n- baja-latencia\n- seastar\n- cpp\n- nosql\n- shard-per-core\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/scylladb-cassandra-compatible-low-latency"},{"text":"AWS","url":"https://aws.com/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency"},"headline":"ScyllaDB: Arquitectura Shard-Per-Core, Benchmarks de Latencia y Migración desde Cassandra","description":"Análisis de la arquitectura shard-per-core de ScyllaDB, los internals del framework Seastar, y benchmarks reales de latencia p99 contra Apache Cassandra. Incluye guía de migración probada en producción.","datePublished":"2025-11-20","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://statics.lucioduran.com/images/lucioduran-social-card.jpg","url":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"Databases","keywords":"scylladb, cassandra, bases-de-datos-distribuidas, baja-latencia, seastar, cpp, nosql, shard-per-core","wordCount":1471,"timeRequired":"PT8M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/scylladb-cassandra-compatible-low-latency"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"ScyllaDB: Arquitectura Shard-Per-Core, Benchmarks de Latencia y Migración desde Cassandra","item":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Puedo usar mis drivers de Cassandra existentes con ScyllaDB?","acceptedAnswer":{"@type":"Answer","text":"Sí. ScyllaDB implementa el protocolo nativo CQL, así que cualquier driver de Cassandra funciona de una. Pero los drivers shard-aware de ScyllaDB (disponibles para Java, Go, Python, Rust y C++) rutean requests directo al shard correcto, eliminando coordinación cross-shard y reduciendo la latencia p99 entre un 30-60%."}},{"@type":"Question","name":"¿Cómo logra ScyllaDB menor tail latency que Cassandra?","acceptedAnswer":{"@type":"Answer","text":"Tres mecanismos: la arquitectura shard-per-core elimina la contención de locks, el framework Seastar usa scheduling cooperativo sin context switches, y el I/O scheduler provee fairness por shard. No hay garbage collector, no hay pausas stop-the-world, no hay safepoints del JVM."}},{"@type":"Question","name":"¿Cuál es el esfuerzo real de migración desde un cluster de 12 nodos de Cassandra?","acceptedAnswer":{"@type":"Answer","text":"Típicamente 3-6 semanas para una migración en producción incluyendo validación. El schema es casi 1:1 compatible. El mayor esfuerzo va a tuning de estrategias de compactación (la Incremental Compaction Strategy de ScyllaDB no tiene equivalente en Cassandra) y ajustar timeouts del lado del cliente porque ScyllaDB responde más rápido."}}]}]}],"contentHash":"0b59f53816e880ac13f90b5fe4a07facb89f407dea619ef6eb1172a02cf9968c","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/scylladb-cassandra-compatible-low-latency","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":67563,"contentBytes":12937}}
