2012-01-20 24 views
19

Ho una webapp Flask, SQLAlchemy che utilizza un singolo server mysql. Voglio espandere l'installazione del database per avere un server slave di sola lettura in modo tale che possa diffondere le letture tra master e slave continuando a scrivere sul server master db.read slave, setup master read-write

Ho visto poche opzioni e credo di non poterlo fare con SQLAlchemy. Invece sto pensando di creare 2 handle di database nella mia webapp, uno per server db master e slave. Quindi, utilizzando un semplice valore casuale, utilizzare l'handle db master/slave per le operazioni "SELECT".

Tuttavia non sono sicuro se questo è il modo giusto per utilizzare SQLAlchemy. Qualche suggerimento/consigli su come tirarlo fuori? Grazie in anticipo.

risposta

26

Ho un esempio di come farlo sul mio blog allo http://techspot.zzzeek.org/2012/01/11/django-style-database-routers-in-sqlalchemy/. Fondamentalmente è possibile migliorare la Session in modo che scelga da master o slave su una base query-per-query. Un potenziale problema con questo approccio è che se si ha una transazione che chiama sei query, si potrebbe finire per usare entrambi gli slave in una richiesta ... ma stiamo solo cercando di imitare la caratteristica di Django :)

A un po 'approccio meno magica che stabilisce anche l'ambito di utilizzo più esplicitamente che ho usato è un decoratore in vista callable (qualunque cosa si chiamano in Flask), in questo modo:

@with_slave 
def my_view(...): 
    # ... 

with_slave avrebbe fatto qualcosa di simile, presumendo che tu abbia una sessione e alcuni motori impostati:

master = create_engine("some DB") 
slave = create_engine("some other DB") 
Session = scoped_session(sessionmaker(bind=master)) 

def with_slave(fn): 
    def go(*arg, **kw): 
     s = Session(bind=slave) 
     return fn(*arg, **kw) 
    return go 

L'idea è che chiamare Session(bind=slave) invochi il registro per ottenere l'oggetto Session effettivo per il thread corrente, creandolo se non esiste, tuttavia dal momento che stiamo passando un argomento, scoped_session asserirà che la Sessione che stiamo fare qui è sicuramente nuovo di zecca.

Lo si punta sullo "slave" per tutti gli SQL successivi. Quindi, quando la richiesta è finita, assicurati che l'app Flask chiami lo Session.remove() per cancellare il registro per quel thread. Quando il registro viene successivamente utilizzato sullo stesso thread, sarà una nuova sessione legata al "master".

o una sua variante, si desidera utilizzare lo "schiavo" solo per quella chiamata, questo è "più sicuro" in quanto ripristina qualsiasi legano esistente ritornare alla sessione:

def with_slave(fn): 
    def go(*arg, **kw): 
     s = Session() 
     oldbind = s.bind 
     s.bind = slave 
     try: 
      return fn(*arg, **kw) 
     finally: 
      s.bind = oldbind 
    return go 

Per ognuno di questi decoratori puoi invertire le cose, avere la Sessione legata a uno "schiavo" in cui il decoratore lo mette in "master" per le operazioni di scrittura. Se si voleva uno slave casuale in quel caso, se Flask avesse un qualche tipo di evento "request begin" si poteva configurarlo in quel punto.

+2

Thnx zzzeek Questo aiuta molto. Complimenti a tutto il fantastico lavoro su sqlalchemy. –

+0

Rad commento, esempi di codice ottimo anche! Sarebbe bello se sqlalchemy avesse un modo per fare analisi delle query e instradare automaticamente, ma in un mondo in cui una query potrebbe causare una tabella tmp o un'altra operazione di scrittura come risultato di ciò che presumibilmente normalmente dovrebbe essere letto solo che richiederebbe qualcosa come la richiesta il piano di query dal back-end prima di inviare la query e sarebbe più problematico di quanto varrebbe nella maggior parte dei casi. –

+1

abbiamo l'opzione "query analysis", sebbene richieda di scrivere da sé l'analisi. Il sistema orizzontale di sharding illustra un esempio di questo tipo di tecnica, vedi http://docs.sqlalchemy.org/en/rel_0_7/orm/extensions/horizontal_shard.html. – zzzeek

0

Oppure, possiamo provare un altro modo. Ad esempio possiamo dichiarare due classi diverse con tutti gli attributi di istanza uguali ma l'attributo di classe __bind__ è diverso. Quindi possiamo usare la classe rw per leggere/scrivere e la classe r da leggere solo. :)

Penso che questo modo sia più semplice e affidabile. :)

Dichiariamo due modelli DB perché possiamo avere tabelle in due db diversi con gli stessi nomi. In questo modo possiamo anche bypassare l'errore "extend_existing" quando due modelli con lo stesso __tablename__.

Ecco un esempio:

app = Flask(__name__) 
app.config['SQLALCHEMY_BINDS'] = {'rw': 'rw', 'r': 'r'} 
db = SQLAlchemy(app) 
db.Model_RW = db.make_declarative_base() 

class A(db.Model): 
    __tablename__ = 'common' 
    __bind_key__ = 'r' 

class A(db.Model_RW): 
    __tablename__ = 'common' 
    __bind_key__ = 'rw'  
+0

puoi migliorare la tua risposta fornendo un esempio di creazione, definizione e utilizzo di due database con diversi accessi in lettura e scrittura –

Problemi correlati