I have a web project that uses an external database. I'm wanting to write the site in Nim using the jester library.
One of the gotchas of the database, which I cannot control, is that though the database itself is very fast, establishing an authenticated connection to it takes about two seconds.
So far, I've found two ways to make this work:
routes:
get "/index.html":
var db = connectToDatabase()
r = db.query("select update from today")
resp someIndexHtml(r)
That works and is fully thread safe, but delays each and every page by 2 seconds. Ouch.
var db = connectToDatabase()
routes:
get "/index.html":
r = db.query("select update from today")
resp someIndexHtml(r)
Now delay is on server startup. That is fine. The pages are nice and fast. The compiler warns me about accessing a global variable.
If two page threads access the db simultaneously it is very likely that things will break. As long as traffic is light, I will probably get away with it.
How have people worked around this problem?
Just thinking out loud: An answer could be a template of some kind, such as:
var pool = connectToDatabaseAsPool(20)
template inbound(body: untyped) {.dirty.} =
# this is run before the async call to the route
var db = pool.getOneThread()
template outbound(body: untyped) {.dirty.} =
# this is run after the async call to the route
pool.returnOneThread(db)
customRoutes(inbound, outbound):
get "/index.html":
r = db.query("select update from today")
resp someIndexHtml(r)
I imagine if I clone jester I could make a custom version of routes that does this for me. But I'd rather not go down that path.
Thoughts?
As long as you don’t compile with —threads:on then jester will run single threaded (and the compiler will only report a warning).
To fix this to work with threads you should use a threadvar, and initialise the DB connection in each thread.
From the db point of view opening a connection is resource(and time -) consuming. So it's better to use a connectionpool. The "fetch from pool/push back to pool" procs should be threadsafe. If you are unsure just write a threadsafe wrapper around it.
@dom96 you could do that but it's a good idea to limit the amount of database connections. And you need a kind of "heartbeat" to check if the connection to the database is still alive. Normally that's handled by the connectionpool.
To fix this to work with threads you should use a threadvar, and initialise the DB connection in each thread.
Ah, but that gets me back to 2 seconds delay per page.
Just for fun; started diving in. I created a new element called decorate:
decorate <name>:
beforeThread <identifier>, <type>:
# causes a COPY of identifier to be passed as a parameter to the route.
beforeRoutes:
# statements to run prior to running the code in any route
# this is different than "before:" (as found in routers) because any variables here
# are still in context for the route itself.
afterRoutes:
# statements to run immediately after running the code in any route
# used for cleanup such as saving cookies etc.
afterThread:
# in the same context as 'beforeThread'. Meant for cleanup after the async thread has closed.
The decorators are setup to be chainable. In fact, if someone wrote templates, one could add functionality to jester with stuff like:
jesterUserManagement()
jesterTrafficLogger()
Here is an example of decorate I am testing:
import jester, sequtils
type
fakePoolEntry = tuple
id: int
free: bool
var fakePool: seq[fakePoolEntry] = @[]
for i in 0 .. 9:
fakePool.add (id: i, free: true)
var tryNext = 0
proc getOne(): fakePoolEntry =
for i in 0 .. 9:
tryNext = (tryNext + 1) mod 10
if fakePool[tryNext].free:
fakePool[tryNext].free = false
result = fakePool[tryNext]
break
proc returnOne(db: fakePoolEntry) =
for i in 0 .. 9:
if fakePool[i].id == db.id:
fakePool[i].free = true
decorate helloWorld:
beforeThread db, fakePoolEntry:
var db = getOne()
beforeRoutes:
var name = "Joe"
var something = 3
afterRoutes:
echo "something = ", something
afterThread:
returnOne(db)
routes:
get "/":
something = 2
resp "hello, " & name & " pool number =" & $db.id
It kind-of-sort-of works. It does what it says, but the underlying asynchttpserver is also running threads. So the asynchttpserver complains about beforeThread accessing global memory, but not the async call to the route is okay.
Will play with it some more...
Ah, but that gets me back to 2 seconds delay per page.
That should only be the case for the first request per page.