You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add sqlite3 as another possible backing store for the dbm module #100414
Right now we support ndbm and gnu.dbm which might or might not be part of a given build. The fallback is the super slow dumbdbm. Not the sqlite3 is part of the standard build, we can do better.
The module docstring says:
Future versions may change the order in which implementations are
tested for existence, and add interfaces to other dbm-like
implementations.
The future is now. Let's provide a fast, stable, robust, always available alternative.
This can be done will pure python calls to the existing sqlite3 module, or there can be a C extension that calls the Sqlite3 C API directly.
This would automatically be available to the shelve module, giving us a high quality, persistent key-value store.
@rhettinger, would you like to continue the discussion here or on #48033? There is no use in keeping two identical issues open; let's close one of them.
Using SQLite backend can be very-very helpful if I would like to view or change the database manually. So if the speed of SqliteDict is enough, I'd prefer use that with JSON encoding (not pickle).
SQLite may be not the best choice storing binary data. Although it could be a good option if we can change shelve module to allow to use json encoding also, and not only have the option to set pickle protocol version.
pysos is also pretty fast and have the ability to review / change the database if the size of the database is small enough to load into an editor. (For example using it as a persistent state backend for small amount of data.)
The biggest problem with dbm.dumb (at least for me) is that it is not fail safe, see the example below. Moreover, it is not mentioned in the documentation. If you kill the Python process, the database will be corrupted every time. (This is because the key file is updated only on adding and deleting keys, and on close.)
importdbm.dumbimportshelveimportitertoolsdatabase=dbm.dumb.open("test", "c")
shelf=shelve.Shelf(database)
if"key"inshelf:
print("The stored value is: "+shelf["key"])
input("Press ENTER to continue")
delshelf["key"]
try:
forindexinitertools.count():
print(f"Persist value {index} to database")
shelf["key"] =f"value {index} "+ ("x"*index)
except:
print("Exiting gracefully. Try killing the Python process.")
Performance measurements
Sorry. in Windows I do not have dbm.gnu.
Copied from Dobatymo/lmdb-python-dbm#2 (comment)
The numbers are seconds required to do the mentioned amount of work.
@rhettinger, would you like to continue the discussion here
If you want to run with this, do whatever makes sense for you. The other issue is mostly stale and likely needs a fresh start. A new first draft is likely just an afternoon project. If you don't want to run with it, let me know and I'll work on it.
I don't know that there needs to be much discussion because we have collections.abc.MutableMapping to structure the implementation and because the APIs for shelve and dbm are already established. This is a clearly desirable buildout that addresses the known issues with dumbdbm. With Sqlite3, we get down to a single file in a standard format, we get ACID guarantees, concurrency and compaction, and we get a dbm that will be present in every build.
If you're interested, here's some code that can be used as a starting point:
importsqlite3fromcontextlibimportsuppressfromcollections.abcimportMutableMappingBUILD_TABLE="CREATE TABLE Dict (key TEXT NOT NULL, value BLOB NOT NULL, PRIMARY KEY (key));"GET_SIZE="SELECT COUNT (*) FROM Dict;"LOOKUP_KEY="SELECT value FROM Dict WHERE key = ?;"STORE_KV="REPLACE INTO Dict (key, value) VALUES (?, ?);"DELETE_KEY="DELETE FROM Dict WHERE key = ?;"ITER_KEYS="SELECT key FROM Dict;"classSQLiteDict(MutableMapping):
def__init__(self, dbname=':memory:'):
self.conn=sqlite3.connect(dbname, isolation_level=None)
self.conn.execute('PRAGMA journal_mode = wal')
withsuppress(sqlite3.OperationalError):
self.conn.execute(BUILD_TABLE)
def__len__(self):
returnself.conn.execute(GET_SIZE).fetchone()[0]
def__getitem__(self, key):
value_tuple=self.conn.execute(LOOKUP_KEY, (key,)).fetchone()
ifvalue_tupleisNone:
raiseKeyError(key)
returnvalue_tuple[0]
def__setitem__(self, key, value):
self.conn.execute(STORE_KV, (key, value));
self.conn.commit()
def__delitem__(self, key):
ifkeynotinself:
raiseKeyError(key)
self.conn.execute(DELETE_KEY, (key,));
self.conn.commit()
def__iter__(self):
curs=self.conn.execute(ITER_KEYS)
while (value_tuple:=curs.fetchone()):
yieldvalue_tuple[0]
curs.close()
defclose(self):
self.conn.close()
self.conn=Nonedef__enter__(self):
returnselfdef__exit__(self, *args):
self.close()
Other thoughts:
If we wanted, we could implement dict-like tracking of key insertion order but the API doesn't seem to require it.
The idea is to leave the above class as a simple as possible and to let a separate DB_Sqllite class build around it to provide readonly logic and anything else specific to the dbm API. That would nicely decouple the core dictionary logic from the needs of dbm.
To make this support concurrency, the mutating methods need to be pause and wait if the db is locked.
This seems to have broken the beaker library: bbangert/beaker#242 . Its test suite, which passes fine on Python 3.12, fails on Python 3.13 in dbm-based tests, with errors about sqlite3 thread violations. Isn't it a bit bad if code using dbm which worked fine with the previous default backend fails with the new one?
of course, I guess it's possible beaker is just not handling threads correctly, but the other backends weren't smart enough to notice, and beaker's tests aren't good enough to catch it either. I'm not sure which is the case.
Activity
erlend-aasland commentedon Dec 21, 2022
See also #48033
erlend-aasland commentedon Dec 21, 2022
IIRC, I've got a fairly up-to-date branch for this, based on your previous efforts, Raymond.
erlend-aasland commentedon Dec 29, 2022
@rhettinger, would you like to continue the discussion here or on #48033? There is no use in keeping two identical issues open; let's close one of them.
presidento commentedon Dec 31, 2022
Here are my 50 cents about this.
TL;DR
dbm.dumbis not slower thanSqliteDictdbm.dumbis not fail safe, so try to avoid using it until it is fixedIn details
dbm.dumbis not so slow. According to my measurements the existing SqliteDict can be as fast astdbm.dumb, but only with some tweaks (using WAL mode). The measurements with code are in this pull request: Reorganize the code of benchmark Dobatymo/lmdb-python-dbm#2shelvemodule to allow to use json encoding also, and not only have the option to set pickle protocol version.The biggest problem with
dbm.dumb(at least for me) is that it is not fail safe, see the example below. Moreover, it is not mentioned in the documentation. If you kill the Python process, the database will be corrupted every time. (This is because the key file is updated only on adding and deleting keys, and on close.)Performance measurements
Sorry. in Windows I do not have dbm.gnu.
Copied from Dobatymo/lmdb-python-dbm#2 (comment)
The numbers are seconds required to do the mentioned amount of work.
write
batch
read
combined
rhettinger commentedon Dec 31, 2022
If you want to run with this, do whatever makes sense for you. The other issue is mostly stale and likely needs a fresh start. A new first draft is likely just an afternoon project. If you don't want to run with it, let me know and I'll work on it.
I don't know that there needs to be much discussion because we have
collections.abc.MutableMappingto structure the implementation and because the APIs for shelve and dbm are already established. This is a clearly desirable buildout that addresses the known issues withdumbdbm. With Sqlite3, we get down to a single file in a standard format, we get ACID guarantees, concurrency and compaction, and we get a dbm that will be present in every build.If you're interested, here's some code that can be used as a starting point:
Other thoughts:
dbmAPI. That would nicely decouple the core dictionary logic from the needs ofdbm.erlend-aasland commentedon Jan 2, 2023
I prefer closing the old issue; we definitely need a fresh start here. I'll take care of it.
I'll give it a shot; it'll be a nice chance for me to get to know the shelve and dbm modules better.
Thanks!
7 remaining items
gh-100414: Add SQLite backend to dbm (#114481)
pythongh-100414: Make dbm.sqlite3 the preferred dbm backend
pythongh-100414: Skip test_dbm_sqlite3 if sqlite3 is unavailable
pythongh-100414: Skip test_dbm_sqlite3 if sqlite3 is unavailable
gh-100414: Skip test_dbm_sqlite3 if sqlite3 is unavailable (#115449)
gh-100414: Make dbm.sqlite3 the preferred dbm backend (#115447)
pythongh-100414: Add SQLite backend to dbm (python#114481)
pythongh-100414: Skip test_dbm_sqlite3 if sqlite3 is unavailable (pyt…
pythongh-100414: Make dbm.sqlite3 the preferred dbm backend (python#1…
AdamWill commentedon Jun 21, 2024
This seems to have broken the beaker library: bbangert/beaker#242 . Its test suite, which passes fine on Python 3.12, fails on Python 3.13 in dbm-based tests, with errors about sqlite3 thread violations. Isn't it a bit bad if code using dbm which worked fine with the previous default backend fails with the new one?
AdamWill commentedon Jun 21, 2024
of course, I guess it's possible beaker is just not handling threads correctly, but the other backends weren't smart enough to notice, and beaker's tests aren't good enough to catch it either. I'm not sure which is the case.