Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Scylloquent Laravel Package

danielhe4rt/scylloquent

Eloquent-style models and a fluent query builder for ScyllaDB, giving Laravel developers familiar syntax over wide-column NoSQL. Define models, run expressive queries, and work with Cassandra-compatible data and CQL without leaving your existing Laravel workflow.

View on GitHub
Deep Wiki
Context7

Getting Started

Scylloquent wires a Cassandra/ScyllaDB connection into Laravel's Eloquent so you can talk CQL from a familiar Model-ish surface. It is a small, early-stage package (29 stars, MIT), so treat the README and src/ as the source of truth and pin an exact version in composer.json.

Prerequisites before you touch the code

  • A running ScyllaDB (or Cassandra) node — the fastest path is docker run --rm -p 9042:9042 scylladb/scylla and then docker exec -it <id> cqlsh.
  • PHP 8.1+ and the DataStax PHP driver (ext-cassandra, or the ScyllaDB-maintained fork). Without the extension installed the connection resolver has nothing to talk to, and the failure mode is usually a confusing "connection not configured" error rather than a missing-extension error.
  • A Laravel app where you have already decided Scylla is the right store for a specific workload — not a drop-in replacement for MySQL.

Minimal steps

composer require danielhe4rt/scylloquent
php artisan vendor:publish --tag=scylloquent-config   # check tag name in README

Then register a connection in config/database.php alongside your existing ones:

'scylla' => [
    'driver'   => 'scylladb',        // confirm the driver key in src/
    'hosts'    => ['127.0.0.1:9042'],
    'keyspace' => 'app',
    'consistency' => \Cassandra::CONSISTENCY_LOCAL_QUORUM,
],

Create the keyspace and table with CQL, not with Laravel migrations:

CREATE KEYSPACE IF NOT EXISTS app
  WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};

CREATE TABLE app.events (
  tenant_id  uuid,
  bucket     date,
  event_id   timeuuid,
  type       text,
  payload    text,
  PRIMARY KEY ((tenant_id, bucket), event_id)
) WITH CLUSTERING ORDER BY (event_id DESC);

Where to look first

  1. README.md for the connection name, model base class, and any artisan commands.
  2. src/ for the connection resolver and the model class you are expected to extend — this tells you which Eloquent features were actually implemented versus inherited and left broken.
  3. The package's tests/ for canonical usage; with a package this size, the tests are often better documentation than the README.

Best first use case: a high-write, append-only table keyed by tenant + time bucket (audit log, event stream, sensor readings, per-user notification feed). Scylla's value is linear write throughput and partition-local reads; pick a workload where every query already knows its partition key and you will get a good result on day one.

Implementation Patterns

Model definition. Expect to declare the primary key explicitly rather than relying on $primaryKey/$incrementing. Cassandra has no auto-increment, so use uuid/timeuuid client-side:

use Scylloquent\Model;              // exact namespace: check src/

class Event extends Model
{
    protected $connection = 'scylla';
    protected $table      = 'events';

    protected $primaryKey = ['tenant_id', 'bucket', 'event_id']; // composite
    public    $incrementing = false;
    protected $keyType    = 'string';

    protected $fillable = ['tenant_id', 'bucket', 'event_id', 'type', 'payload'];
}

Design queries around partitions, not around the model. The healthiest pattern is a thin model plus a repository/service that only exposes partition-scoped methods:

class EventRepository
{
    public function recentForTenant(string $tenantId, \DateTimeImmutable $day, int $limit = 50)
    {
        return Event::query()
            ->where('tenant_id', $tenantId)
            ->where('bucket', $day->format('Y-m-d'))
            ->orderBy('event_id', 'desc')   // must match CLUSTERING ORDER
            ->limit($limit)
            ->get();
    }
}

Anything that isn't partition-scoped should not exist in this repository — if a product requirement needs it, add a second table instead.

Denormalize deliberately. This is the single biggest workflow change. One write fans out to N tables, each shaped for one read pattern:

$this->events->insert($event);                    // by tenant + day
$this->tenantFeed->insert($event);                // by tenant, newest first
$this->byType->insert($event);                    // by type + day

Keep the fan-out in a single job or service method so it is greppable and easy to backfill. ScyllaDB BATCH statements are supported and are a good fit here — but remember a batch is not a transaction: it is atomic only within a single partition, and cross-partition batches are an anti-pattern.

Run it beside MySQL, not instead of it. The pragmatic production shape is: relational DB owns users, billing, and anything needing joins or multi-row transactions; Scylla owns the firehose tables. Two connections, two model base classes, clear naming (App\Models\... vs App\Models\Scylla\...), and an explicit rule about which one is authoritative. Denormalized copies into Scylla are derived data and must be rebuildable.

Write path through the queue. For high-volume ingestion, dispatch() the write to a job rather than writing inline in a request. It keeps request latency off the Scylla driver, gives you retry semantics, and makes backfills a queue operation rather than a script.

Time bucketing. Use a bucket date (or month) column in the partition key for time-series tables. Unbounded partitions grow until compaction becomes a problem and large partitions start hurting the coordinator; per-day or per-month buckets keep partitions bounded and make retention a DROP/TTL decision instead of a delete scan.

Schema changes. Treat CQL DDL as code, kept in a database/cql/ folder and applied by a small artisan command or a cqlsh -f step in deploy. Laravel migrations have no place here. Remember that ALTER TABLE ... ADD is cheap and safe, DROP COLUMN is not, and you cannot change a primary key in place — that requires a new table plus a backfill.

Gotchas and Tips

  • WHERE on non-key columns fails. Cassandra will reject or demand ALLOW FILTERING, which is almost always a design smell. If you find yourself reaching for it, you need another table.
  • with(), whereHas(), belongsTo do not work. Relationships are a relational concept. Any code path that touches them will either throw or silently misbehave. Guard against it by never letting Scylla models be passed to generic code that assumes Eloquent's full contract (Blade components, Nova, API resources that lazy-load).
  • save() on a partial model will null out columns. Scylla writes are upserts of the full row for the columns you send. A model loaded with select('a','b') then saved may overwrite other columns with defaults. Prefer explicit update() with a known column set, or a dedicated insert/upsert path.
  • No OFFSET pagination, and count() is expensive. Use the driver's paging state (fetchSize + paging token) for cursors. Never build ?page=N UI on top of Scylla without a token-based backing.
  • Timestamps and timezones. Cassandra timestamp is UTC-normalized and has millisecond precision; Eloquent's datetime casts and created_at conventions will round-trip but can surprise you when comparing against MySQL rows. Store an explicit bucket date and keep the fine-grained timestamp as timeuuid or timestamp and be consistent.
  • TTL is your garbage collector. Set default_time_to_live on the table for log-like data instead of writing delete jobs. Per-write TTL is also available if the driver exposes it — check src/ for a ttl() builder method before hand-rolling one.
  • Consistency levels matter, and the defaults aren't yours. LOCAL_QUORUM reads/writes are the usual production default; ONE is fast but will show you stale data after a write, which looks like an ORM bug and isn't. Make the level configurable per connection.
  • Debugging: cqlsh is your php artisan tinker. Reproduce the query by hand there first, look at system_traces.sessions for slow queries, and enable the driver's query logging temporarily. If a model "returns nothing", the partition key is wrong 90% of the time — usually a type mismatch (string vs uuid, DateTime vs date string).
  • Empty result vs missing table. The driver tends to surface schema errors late, on execute rather than on model definition. A missing keyspace produces an exception that doesn't mention the model, so check the connection name on the model first.
  • Extension points are thin — plan to fork or wrap. With a package this small, expect to add your own base model, a ScyllaQueryBuilder macro layer, and possibly a custom connection resolver. Keeping those in app/ (not patching vendor/) is what makes the next composer update survivable.
  • Pin the version and read the diff on upgrade. Pre-1.0 packages with 29 stars change public API between minor versions. A composer.lock plus a smoke test that exercises one insert and one partition read will save you an afternoon.
  • Budget for the "it's not Eloquent" conversation. The most common failure is a teammate writing User::where('email', $x)->first() against a Scylla model because it looks like Eloquent. Document the constraint at the top of the model file, or name the classes so the distinction is obvious (ScyllaEventRepository, not Event).
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums