haskell - datos - persistencia significado
¿Cuáles son las reglas sobre el acceso concurrente a una base de datos persistente? (1)
Parece que las reglas sobre el acceso concurrente no están documentadas (en el lado de Haskell) y simplemente se supone que el desarrollador está familiarizado con el backend particular que se utiliza. Para las necesidades de producción, esta es una suposición perfectamente legítima, pero para prototipos y desarrollo casuales sería bueno si los paquetes persistentes - * fueran un poco más autónomos.
Entonces, ¿cuáles son las reglas que rigen el acceso concurrente a sqlite persistente y la familia? Implícitamente, debe haber cierto grado de concurrencia permitido si tenemos grupos de conexiones, pero la creación trivial de un solo grupo de conexiones y la invocación de replicateM x $ forkIO (useThePool connectionPool)
arroja el siguiente error.
user error (SQLite3 returned ErrorBusy while attempting to perform step.)
EDITAR: Algunos códigos de ejemplo están ahora debajo.
En el siguiente código, corto 6 hilos (un número arbitrario: mi aplicación real tiene 3 hilos). Cada hilo almacena y busca constantemente un registro (un registro único del que se accede por los otros hilos, pero eso no importa), imprimiendo uno de los campos.
{-# LANGUAGE TemplateHaskell, QuasiQuotes
, TypeFamilies, FlexibleContexts, GADTs
, OverloadedStrings #-}
import Control.Concurrent (forkIO, threadDelay)
import Database.Persist
import Database.Persist.Sqlite hiding (get)
import Database.Persist.TH
import Control.Monad
import Control.Monad.IO.Class
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist|
SomeData
myId Int
myData Double
MyId myId
|]
main = withSqlitePool "TEST" 40 $ /pool -> do
runSqlPool (runMigration migrateAll) pool
mapM_ forkIO [runSqlPool (dbThread i) pool | i <- [0..5]]
threadDelay maxBound
dbThread :: Int -> SqlPersist IO ()
dbThread i = forever $ do
x <- getBy (MyId i)
insert (SomeData i (fromIntegral i))
liftIO (print x)
liftIO (threadDelay 100000) -- Just to calm down the CPU,
-- not needed for demonstrating
-- the problem
NB: los valores de 40
, TEST
y todos los registros son arbitrarios para este ejemplo. Muchos valores, incluidos los más realistas, causan el mismo comportamiento.
También tenga en cuenta que, si bien es posible que se rompa cuando anida una acción no forever
(a través de forever
) dentro de una transacción DB (iniciada por runSqlPool
), este no es el problema central. Puede invertir esas operaciones y hacer que las transacciones sean arbitrariamente pequeñas, pero aún así terminar con excepciones periódicas.
El resultado suele ser como:
$ ./so
Nothing
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.)
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.)
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.)
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.)
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.)
so: user error (SQLite3 returned ErrorConstraint while attempting to perform step.)
Algo que vale la pena señalar es que SQLite tiene problemas con el bloqueo cuando se almacena en volúmenes similares a NFS (vboxsf, NFS, SMB, mvfs, etc.) en muchos sistemas que hacen que SQLite emita ese error incluso antes de que haya abierto la base de datos. Estos volúmenes pueden implementar bloqueos de lectura / escritura fcntl () incorrectamente. ( http://www.sqlite.org/faq.html#q5 )
Suponiendo que ese no sea el problema, también vale la pena mencionar que SQLite no admite de forma nativa las "conexiones" concurrentes ( http://www.sqlite.org/faq.html#q6 ) ya que usa bloqueos de sistema de archivos para garantizar que dos escrituras no ocurra al mismo tiempo. (Consulte la sección 3.0 de http://www.sqlite.org/lockingv3.html )
Suponiendo que todo esto se conoce, también puede verificar qué versión de sqlite3 tiene disponible para su entorno, ya que algunos cambios en la forma en que se adquieren los diferentes tipos de bloqueos ocurrieron en la serie 3.x: http://www.sqlite.org/sharedcache.html
Editar: información adicional de la biblioteca persist-sqlite3 This package includes a thin sqlite3 wrapper based on the direct-sqlite package, as well as the entire C library
El envoltorio ''delgado'' me hizo decidir echarle un vistazo para ver qué tan delgado es; mirando el código, no parece que el envoltorio persistente tenga guardias contra un enunciado al fallo del grupo, excepto el guardia requerido para traducir / emitir el error e interrumpir la ejecución, aunque debo proporcionar la advertencia de que no me siento cómodo con Haskell.
Parece que tendrá que evitar que una declaración en el grupo falle y vuelva a intentar, o que limite el tamaño del grupo en la inicialización a 1 (lo que parece menos que ideal).