SearchFAQMemberlist Log in
Reply to topic Page 1 of 1
[Bacula-devel] Project for strong incremental backup assuran
Author Message
Post [Bacula-devel] Project for strong incremental backup assuran 
On Wednesday 06 June 2007 10:21, Andre Noll wrote:
On 21:38, Kern Sibbald wrote:
Would bacula also have issues with deleted files. Say a file existed
during a full backup, then it was deleted. Then an incremental backup
occurred. Then a restore was done. Would the file be restored, even if
though the restore routine selected the incremental job??

If you do a full restore as is done by the restore command in Bat, yes the
file would be restored.

Six month ago we've had a total crash (ZFS) and had to restore 4T of
data from tapes. According to Murphy, our last full backup was three
months old and we thus had about 90 incremental backups.

Although the restore worked flawlessly (thanks a lot for providing
such a great piece of software), it was a major PITA for everyone to
clean up the restored data because naturally lots of files had been
removed during the previous three month. Even more annoying were all
the directories that had been renamed since the full backup because
these appeared twice in the restored tree.

So I think it would be _really_ nice to store information about
deleted files and directories in the database which would make it
possible to get rid of all deleted files and directories automatically
during restore.

The dar backup tool for example has this feature. Are there any
plans to include such a feature also in bacula?

Yes, but no one is currently working on it. There have been a number of
emails on this subject on the bacula-users' list recently.

Regards,

Kern


Thanks
Andre
--
The only person who always got his work done by Friday was Robinson Crusoe


Post [Bacula-devel] Project for strong incremental backup assuran 
Attachments: Message as HTML

On 10:40, Kern Sibbald wrote:

So I think it would be _really_ nice to store information about
deleted files and directories in the database which would make it
possible to get rid of all deleted files and directories automatically
during restore.
=20
The dar backup tool for example has this feature. Are there any
plans to include such a feature also in bacula?
=20
Yes, but no one is currently working on it. There have been a number of=
=20
emails on this subject on the bacula-users' list recently.

In February you said Robert will be working on this project. Do you
have any pointers to this work? I would be interested to look at the
strategy for implementing this and at the work that has been done so
far, if any.

Thanks
Andre
--=20
The only person who always got his work done by Friday was Robinson Crusoe

Post [Bacula-devel] Project for strong incremental backup assuran 
On Wednesday 06 June 2007 11:28, Andre Noll wrote:
On 10:40, Kern Sibbald wrote:

So I think it would be _really_ nice to store information about
deleted files and directories in the database which would make it
possible to get rid of all deleted files and directories automatically
during restore.

The dar backup tool for example has this feature. Are there any
plans to include such a feature also in bacula?

Yes, but no one is currently working on it. There have been a number of
emails on this subject on the bacula-users' list recently.

In February you said Robert will be working on this project. Do you
have any pointers to this work? I would be interested to look at the
strategy for implementing this and at the work that has been done so
far, if any.

Robert quit the project, so currently there is no one assigned to it.

I would be *extremely* happy to see someone interested in this project. If
your offer is for algorithm help, please see Algorithms below. If your offer
above includes programming (i.e. C or C++ programmer), and you are interested
in working on it, please let me know (either off list or if you wish copying
the bacula-devel list) and we can discuss the project. I recommend starting
by reading the Developer notes in the Developer's Guide that is on the web
site. It will give you a broad overview of developing for the Bacula
project.

This project and the project to store only one copy of a file (Base project)
are closely related because they both require *much* more communication
between the Dir and the FD -- essentially the Dir must send the current state
as known in the catalog to the client, which can then determine which files
to backup.

Algorithms:
This requires potentially sending a *lot* of data (i.e. millions of filenames
and attribute data), which will require hash coding the names for performance
reasons. If we want to handle up to 20 million filenames as we are starting
to see on some systems, we will probably at some point need a good file
paging algorithm.

Some years ago, I wrote hasing routines specifically for this, but they have
never been used yet, and so I am now looking at bringing them up to date --
in particular adding a Bloom filter to improve performance (I am currently
researching Bloom filters). Where I could use a bit of advice is:

Now:
- Reviewing my hash table code (particularly the hash function)
src/lib/htable.h src/lib/htable.c
- Proposing how to size a Bloom filter (n bits) and number of hash functions.
- Proposing what hash functions to use for the Bloom filter.

Later:
- Review overall strategy.

Since these two projects (de-duplication of files, tracking new and deleted
files) are quite hot topics lately, over the next week, I will write up a
sort of proposal for implementation outlining my general ideas for how to
implement them within the existing Bacula framework (i.e. without too many
modifications to the database, ...).

Thanks for your interest in this.

Best regards,

Kern

Post [Bacula-devel] Project for strong incremental backup assuran 
Attachments: Message as HTML

On 11:57, Kern Sibbald wrote:

Robert quit the project, so currently there is no one assigned to it.
=20
I would be *extremely* happy to see someone interested in this project.

I sure am interested. But I'm not sure how much time I will be able
to spend on it.

If your offer is for algorithm help, please see Algorithms below. If
your offer above includes programming (i.e. C or C++ programmer), and
you are interested in working on it, please let me know (either off
list or if you wish copying the bacula-devel list) and we can discuss
the project.

The theoretical background is what I'm interested (and probably
skilled) most, but programming in C is not a problem either. I'm not
that familiar with C++ though.

This project and the project to store only one copy of a file (Base proje=
ct)=20
are closely related because they both require *much* more communication=
=20
between the Dir and the FD -- essentially the Dir must send the current s=
tate=20
as known in the catalog to the client, which can then determine which fil=
es=20
to backup.
=20
Algorithms:
This requires potentially sending a *lot* of data (i.e. millions of filen=
ames=20
and attribute data), which will require hash coding the names for perform=
ance=20
reasons.

Agreed. So the communication between sd and fd for an incemental
backup would look like

fd -> sd: here's the list of (hashes of) filenames that
have changed. This list might also contain hashes of the
contents of these files.

sd -> fd: thanks, please send them, but omit the following ones
as I already have them.

sd -> fd: list of hashes to omit.

fd -> sd: send contents of shortened list of files

Some years ago, I wrote hasing routines specifically for this, but they h=
ave=20
never been used yet, and so I am now looking at bringing them up to date =
--=20
in particular adding a Bloom filter to improve performance (I am currentl=
y=20
researching Bloom filters).

I just read the wikipedia page on bloom filters. Looks to me like a
very promising concept. However, one obvious problem is that you'll
have to regenerate the filter whenever the ratio n/m of number of
hash bits per filename becomes so small that the false probability of
(ln 2)^(n/m) becomes too large.

Where I could use a bit of advice is:
=20
Now:
- Reviewing my hash table code (particularly the hash function) =20
src/lib/htable.h src/lib/htable.c

Will do.

- Proposing how to size a Bloom filter (n bits) and number of hash functi=
ons.

Well, these values will have to depend on the number of files that
are going to be stored in the database. If an upper bound M on
this number is known in advance as well as a small number p, the
probability with which we are willing to accept false positives,
one can easily determine n and the number k of hash functions as

n =3D M * ln (1 / p) / (ln 2)^2

k =3D ln 2 * n / M

unless I errored with the math ;)

- Proposing what hash functions to use for the Bloom filter.

I personally like SuperFastHash for its performance, see

http://www.azillionmonkeys.com/qed/hash.html=20

It's not cryptographically secure, but very fast. md5 would be another
option, but sha1 is likely too expensive for no real gain. It should
be possible to exchange the hash algorithm easily.

In any case it's enough to choose only two hash functions h1 and h2,
no matter what k is. One can then use the standard double hashing
method to produce k hash functions via

h(i, key) =3D (h1(key) + i * h2(key)) mod m, i =3D 0...k-1,

where m is chosen properly (e.g. a power of two if h2 produces only
odd numbers).

Since these two projects (de-duplication of files, tracking new and delet=
ed=20
files) are quite hot topics lately, over the next week, I will write up a=
=20
sort of proposal for implementation outlining my general ideas for how to=
=20
implement them within the existing Bacula framework (i.e. without too man=
y=20
modifications to the database, ...).

I'm looking forward to this.

Andre

--=20
The only person who always got his work done by Friday was Robinson Crusoe

Post [Bacula-devel] Project for strong incremental backup assuran 
Attachments: Message as HTML

On 15:35, Andre Noll wrote:

- Reviewing my hash table code (particularly the hash function) =20
src/lib/htable.h src/lib/htable.c
=20
Will do.

First some general remarks:

- check the return value of malloc()
- keys are restricted to strings. it would be easy to extend this
to arbitrary data by adding a "size" field.
- IMHO "%p" is prefered for printing pointer variables.

--------------------------------------- htable.h

struct hlink {
void *next; /* next hash item */

why not struct hlink *next?

char *key; /* key this item */
uint32_t hash; /* hash for this key */
};
=20
class htable : public SMARTALLOC {
hlink **table; /* hash table */
int loffset; /* link offset in item */

see below.

uint32_t num_items; /* current number of items */
uint32_t max_items; /* maximum items before growing */

see below.

uint32_t buckets; /* size of hash table */
uint32_t hash; /* temp storage */
uint32_t index; /* temp storage */
uint32_t mask; /* "remainder" mask */
uint32_t rshift; /* amount to shift down */
hlink *walkptr; /* table walk pointer */

This is only used in first() and next(). IMHO the code would be
more readable if this were local to these two methods and
mext() would take a pointer to the predecessor.

uint32_t walk_index; /* table walk index */

same here.

void hash_index(char *key); /* produce hash key,index */
void grow_table(); /* grow the table */
public:
htable(void *item, void *link, int tsize =3D 31);
~htable() { destroy(); }
void init(void *item, void *link, int tsize =3D 31);
bool insert(char *key, void *item);
void *lookup(char *key);
void *first(); /* get first item in table */
void *next(); /* get next item in table */
void destroy();
void stats(); /* print stats about the table */
uint32_t size(); /* return size of table */
};

----------------------------------------htable.c

/*
* Create hash of key, stored in hash then
* create and return the pseudo random bucket index
*/
void htable::hash_index(char *key)
{
hash =3D 0;
for (char *p=3Dkey; *p; p++) {
hash +=3D (hash << 3) + (uint32_t)*p;
}

This ist just shifting and multiplication with the bytes in key. I
would expect this hash to have quite some unneccessary collisions
when used on strings, though I'm by no means an expert in this area.

/* Multiply by large prime number, take top bits, mask for remainder */
index =3D ((hash * 1103515249) >> rshift) & mask;
Dmsg2(100, "Leave hash_index hash=3D0x%x index=3D%d\n", hash, index);
return;

The return statement is unneccesary.

void htable::init(void *item, void *link, int tsize)
{
int pwr;
tsize >>=3D 2;
for (pwr=3D0; tsize; pwr++) {
tsize >>=3D 1;
}
loffset =3D (char *)link - (char *)item;

This is a constant, known at compile time, so make it a preprocessor
define using offsetof.

mask =3D ~((~0)<<pwr); /* 3 bits =3D> table size =3D 8 */
rshift =3D 30 - pwr; /* start using bits 28, 29, 30 */
num_items =3D 0; /* number of entries in table */
buckets =3D 1<<pwr; /* hash table size -- power of tw=
o */
max_items =3D buckets * 4; /* allow average 4 entries per ch=
ain */

As max_items is always four times the number of buckets, we could
get rid of max_items. More importantly, I think it is not optimal
to store that many keys in the table because that means we'll have
in average four hits per bucket. I'd rather choose max_items _smaller_
than the number of buckets. For instance the git code which uses hash
tables extensively, grows its table whenever it is filled 66%.

table =3D (hlink **)malloc(buckets * sizeof(hlink *));

unneccessary cast.

memset(table, 0, buckets * sizeof(hlink *));

how about calloc() instead of malloc + memset?

/*
* Take each hash link and walk down the chain of items
* that hash there counting them (i.e. the hits),=20
* then report that number.

Open addressing might be better than chaining to deal with
hash collisions. In particular if items are never (or rarely)
removed from the table.

* Obiously, the more hits in a chain, the more time
* it takes to reference them. Empty chains are not so
* hot either -- as it means unused or wasted space.
*/
#define MAX_COUNT 20

Unless I'm mistaken, the code will fail badly if there are more than
MAX_COUNT keys that hash to the same value.

void htable::stats()
{
int hits[MAX_COUNT];
int max =3D 0;
int i, j;
hlink *p;
printf("\n\nNumItems=3D%d\nTotal buckets=3D%d\n", num_items, buckets);
printf("Hits/bucket: buckets\n");

Dmesg?

for (i=3D0; i < MAX_COUNT; i++) {
hits[i] =3D 0;
}

memset(hits, 0 MAXCOUNT * sizeof(int));

for (i=3D0; i<(int)buckets; i++) {
p =3D table[i];
j =3D 0;
while (p) {
p =3D (hlink *)(p->next);

cast would become unneccessary if next were struct hlink*.

void htable::grow_table()
{
Dmsg1(100, "Grow called old size =3D %d\n", buckets);
/* Setup a bigger table */
htable *big =3D (htable *)malloc(sizeof(htable));

unnecessary cast

big->loffset =3D loffset;
big->mask =3D mask<<1 | 1;
big->rshift =3D rshift - 1;
big->num_items =3D 0;
big->buckets =3D buckets * 2;
big->max_items =3D big->buckets * 4;

DRY ;)

big->table =3D (hlink **)malloc(big->buckets * sizeof(hlink *));
memset(big->table, 0, big->buckets * sizeof(hlink *));

unnecessary cast, calloc().

/*
* We walk through the old smaller tree getting items,
* but since we are overwriting the colision links, we must
* explicitly save the item->next pointer and walk each
* colision chain ourselves. We do use next() for getting
* to the next bucket.
*/
for (void *item=3Dfirst(); item; ) {
void *ni =3D ((hlink *)((char *)item+loffset))->next; /* save link=
overwritten by insert */

It might be worth to improve readability by defining

#define next_item(item) ((hlink *)((char *)((item)+(loffset))

as this calculation is needed several times.

Dmsg1(100, "Grow insert: %s\n", ((hlink *)((char *)item+loffset))->=
key);
big->insert(((hlink *)((char *)item+loffset))->key, item);
if (ni) {
item =3D (void *)((char *)ni-loffset);
} else {
walkptr =3D NULL;
item =3D next();
}
}

This rather ugly code would become unneccessary when using open
addressing instead of chaining.

void *htable::next()
{
Dmsg1(100, "Enter next: walkptr=3D0x%x\n", (unsigned)walkptr);

if (walkptr) {
walkptr =3D (hlink *)(walkptr->next);

unnecessary cast

Regards
Andre
--=20
The only person who always got his work done by Friday was Robinson Crusoe

Post [Bacula-devel] Project for strong incremental backup assuran 
Hello,

Thanks for your comments. Please see my notes below ...

On Monday 11 June 2007 10:24, Andre Noll wrote:
On 15:35, Andre Noll wrote:

- Reviewing my hash table code (particularly the hash function)
src/lib/htable.h src/lib/htable.c

Will do.

First some general remarks:

- check the return value of malloc()

Not necessary, malloc() is #defined to be bmalloc(), which aborts if it is out
of memory.

- keys are restricted to strings. it would be easy to extend this
to arbitrary data by adding a "size" field.

Yes, good idea, though I don't immediately see a need to handle anything other
than strings within Bacula.

- IMHO "%p" is prefered for printing pointer variables.

Yes, but it was not portable. We now have our own Bacula printf code which is
portable and supports %p, so I've been converting the old stuff when I am in
the file. Clearly this is a good file to attack.


--------------------------------------- htable.h


struct hlink {
void *next; /* next hash item */

why not struct hlink *next?

Good question -- I think you are right. I'll try it and see if anything
complains ...


char *key; /* key this item */
uint32_t hash; /* hash for this key */
};

class htable : public SMARTALLOC {
hlink **table; /* hash table */
int loffset; /* link offset in item */

see below.

uint32_t num_items; /* current number of items */
uint32_t max_items; /* maximum items before growing */

see below.

uint32_t buckets; /* size of hash table */
uint32_t hash; /* temp storage */
uint32_t index; /* temp storage */
uint32_t mask; /* "remainder" mask */
uint32_t rshift; /* amount to shift down */
hlink *walkptr; /* table walk pointer */

This is only used in first() and next(). IMHO the code would be
more readable if this were local to these two methods and
mext() would take a pointer to the predecessor.

I think what you are suggesting is possible, and I will take a look at it.
However, it leads to several problems:
1. walkptr and walk_index are then no longer treated the same, and IMO that
will make it harder to read the code. I.e. there are 2 pieces of information
that would have to be returned and then passed into next().
2. There will be additional code necessary to adjust the pointer on entry,
which I currently avoid by keeping walkptr in the class.
3. It complicates the "user" code in that it must pass the old pointer to the
next() routine and if it is symmetric also walk_index.


uint32_t walk_index; /* table walk index */

same here.

As with the above, that will complicate the problem by returning it to the
higher level program. The "user" program really shouldn't need to know
anything about walkptr or walk_index as they are simply the means to traverse
a hash list in linear order without using recursion (I have never seen such
code anywhere), so IMO, returning them to the higher level routine breaks the
encapsulation of the code.


void hash_index(char *key); /* produce hash key,index */
void grow_table(); /* grow the table */
public:
htable(void *item, void *link, int tsize = 31);
~htable() { destroy(); }
void init(void *item, void *link, int tsize = 31);
bool insert(char *key, void *item);
void *lookup(char *key);
void *first(); /* get first item in table */
void *next(); /* get next item in table */
void destroy();
void stats(); /* print stats about the table */
uint32_t size(); /* return size of table */
};

----------------------------------------htable.c


/*
* Create hash of key, stored in hash then
* create and return the pseudo random bucket index
*/
void htable::hash_index(char *key)
{
hash = 0;
for (char *p=key; *p; p++) {
hash += (hash << 3) + (uint32_t)*p;
}

This ist just shifting and multiplication with the bytes in key. I
would expect this hash to have quite some unneccessary collisions
when used on strings, though I'm by no means an expert in this area.

If I am not mistaken, this is a *very* common technique. The most common
algorithm shifts by 5 rather than 3, but if I remember right my tests showed
that 3 was better -- at least with the filenames on my system.


/* Multiply by large prime number, take top bits, mask for remainder */
index = ((hash * 1103515249) >> rshift) & mask;
Dmsg2(100, "Leave hash_index hash=0x%x index=%d\n", hash, index);
return;

The return statement is unneccesary.

Yes, I have removed it as it could be confusing.


void htable::init(void *item, void *link, int tsize)
{
int pwr;
tsize >>= 2;
for (pwr=0; tsize; pwr++) {
tsize >>= 1;
}
loffset = (char *)link - (char *)item;

This is a constant, known at compile time, so make it a preprocessor
define using offsetof.

Uh, what is "This is a constant"? I don't see any constant. loffset depends
on how one calls the routines. Rather than using a C++ template, which
duplicates the code for each different structure that contains a hash link,
Bacula computes the offset of the link in the structure, and thus with the
same source code can handle any number of different user structures that have
hash links. I have done the same for alist (allocated list) and dlist
(doubly linked lists). The downside of this technique is that the higher
level code must cast because it passes void pointers to the structures.

It is a bit of a tricky point, so I'm not sure the above is clear.


mask = ~((~0)<<pwr); /* 3 bits => table size = 8 */
rshift = 30 - pwr; /* start using bits 28, 29, 30 */
num_items = 0; /* number of entries in table */
buckets = 1<<pwr; /* hash table size -- power of two
*/
max_items = buckets * 4; /* allow average 4 entries per chain
*/

As max_items is always four times the number of buckets, we could
get rid of max_items.

Yes, good. I agree.

More importantly, I think it is not optimal
to store that many keys in the table because that means we'll have
in average four hits per bucket. I'd rather choose max_items _smaller_
than the number of buckets. For instance the git code which uses hash
tables extensively, grows its table whenever it is filled 66%.

I'd like to see some tests on this. I'm not convinced that the % full is a
good criterion because you could then have one bucket that has 100 items in
it which could be catastrophic.

Once I implement a bloom filter, only 15% (with the parameters that I have
chosen) of the collisions will require a search of the buckets.


table = (hlink **)malloc(buckets * sizeof(hlink *));

unneccessary cast.

In C++ code as opposed to C the cast is *required* or you get a compiler
warning (or perhaps an error -- I forget).


memset(table, 0, buckets * sizeof(hlink *));

how about calloc() instead of malloc + memset?

I'm not sure that smartall (our underlying malloc routine) implements
calloc(). I'll look at it.


/*
* Take each hash link and walk down the chain of items
* that hash there counting them (i.e. the hits),
* then report that number.

Open addressing might be better than chaining to deal with
hash collisions. In particular if items are never (or rarely)
removed from the table.

Open addressing has the big advantage that it keeps entries together which
improves CPU speed since everything is in the cache, but unless I am missing
something, open addressing is extremely expensive in terms of memory
requirements since the table has to always be the maximum size. This could
be reasonable if we know how many items are going to be added, but if we do
not, I'm not sure.

I would like to see some real performance studies before changing.


* Obiously, the more hits in a chain, the more time
* it takes to reference them. Empty chains are not so
* hot either -- as it means unused or wasted space.
*/
#define MAX_COUNT 20

Unless I'm mistaken, the code will fail badly if there are more than
MAX_COUNT keys that hash to the same value.

Quite possibly, but I think there are some mitigating factors. 1. If I
remember right the max should be 4. 2. this code is really used for debug.
3. if it fails the stats will be broken, but there will be no serious
consequences. For just obtaining stats, I didn't want to spend too much time
worrying about covering all possibilities.


void htable::stats()
{
int hits[MAX_COUNT];
int max = 0;
int i, j;
hlink *p;
printf("\n\nNumItems=%d\nTotal buckets=%d\n", num_items, buckets);
printf("Hits/bucket: buckets\n");

Dmesg?

Yes, that would clearly be better.


for (i=0; i < MAX_COUNT; i++) {
hits[i] = 0;
}

memset(hits, 0 MAXCOUNT * sizeof(int));

for (i=0; i<(int)buckets; i++) {
p = table[i];
j = 0;
while (p) {
p = (hlink *)(p->next);

cast would become unneccessary if next were struct hlink*.

Yup :-)


void htable::grow_table()
{
Dmsg1(100, "Grow called old size = %d\n", buckets);
/* Setup a bigger table */
htable *big = (htable *)malloc(sizeof(htable));

unnecessary cast

See above. Actually, if I were doing this right, I would probably use one of
those new fangled C++ casts, but I find them so ugly and the syntax so hard
to remember that I avoid them.


big->loffset = loffset;
big->mask = mask<<1 | 1;
big->rshift = rshift - 1;
big->num_items = 0;
big->buckets = buckets * 2;
big->max_items = big->buckets * 4;

DRY ;)

big->table = (hlink **)malloc(big->buckets * sizeof(hlink *));
memset(big->table, 0, big->buckets * sizeof(hlink *));

unnecessary cast, calloc().

/*
* We walk through the old smaller tree getting items,
* but since we are overwriting the colision links, we must
* explicitly save the item->next pointer and walk each
* colision chain ourselves. We do use next() for getting
* to the next bucket.
*/
for (void *item=first(); item; ) {
void *ni = ((hlink *)((char *)item+loffset))->next; /* save link
overwritten by insert */

It might be worth to improve readability by defining

#define next_item(item) ((hlink *)((char *)((item)+(loffset))

Yes, good idea. I was planning to do that as I did in dlist.c.


as this calculation is needed several times.

Dmsg1(100, "Grow insert: %s\n", ((hlink *)((char
*)item+loffset))->key);
big->insert(((hlink *)((char *)item+loffset))->key, item);
if (ni) {
item = (void *)((char *)ni-loffset);
} else {
walkptr = NULL;
item = next();
}
}

This rather ugly code would become unneccessary when using open
addressing instead of chaining.

Quite possibly, but see my remarks about open addressing above.


void *htable::next()
{
Dmsg1(100, "Enter next: walkptr=0x%x\n", (unsigned)walkptr);


if (walkptr) {
walkptr = (hlink *)(walkptr->next);

unnecessary cast

C++ :-)

Thanks for taking the time to look at this -- you are the first person who has
been interested in this kind of code. I'll go over each of your points in
detail -- especially since I will be going through this code in the near
future to bring it up to date (#defines to simplify the pointer offsets), ...

Don't hesitate to respond if some of my remarks above are incorrect.

What always amuses me when I look back at code like this is that I wrote it 4
years ago to be able to implement the base file project (de-duplication) and
the project to track files added/removed from the system, but I've been so
busy with other priority projects that I have not had time to get back and
actually use these routines.

The same happened for my red/black tree (src/lib/rblist.c/h), except in that
case, I have now actually put the code to use in the restore code giving a
513 times speed up in version 2.1.x compared to 2.0.x for 800,000 files.

Best regards,

Kern



Regards
Andre
--
The only person who always got his work done by Friday was Robinson Crusoe




[Bacula-users] exabyte autochanger From: Maria McKinley

- 2007-06-11 09:19

I recently updated to version 1.38.11-8, and I am having a hard time
getting my autochanger to work. I changed my bacula-sd.conf file so that
there is now an Autochanger:

Autochanger {
Name = Exabyte
Device = Drive-1
Changer Command = "/etc/bacula/scripts/mtx-changer %c %o %S %a %d"
Changer Device = /dev/sg0
}

Device {
Name = Drive-1
Drive Index = 0
Media Type = VXA-2
Archive Device = /dev/st0
LabelMedia = yes; # lets Bacula label unlabeled media
Random Access = Yes;
AutomaticMount = yes; # when device opened, read it
RemovableMedia = yes;
AlwaysOpen = yes;
Autochanger = yes;
Alert Command = "sh -c 'tapeinfo -f %c |grep TapeAlert|cat'"
}

This seems to work fine with btape auto command, but if I try to label
from bconsole, I get the following:

*label barcodes
Automatically selected Storage: Exabyte
Connecting to Storage daemon Exabyte at dinah:9103 ...
3306 Issuing autochanger "slots" command.
Device "Exabyte" has 0 slots.
No slots in changer to scan.

Any idea why I'm getting this?

Thanks a bunch,
Maria

Post [Bacula-devel] Project for strong incremental backup assuran 
Attachments: novosirj.vcf

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Andre Noll wrote:
On 11:57, Kern Sibbald wrote:

Robert quit the project, so currently there is no one assigned to it.

I would be *extremely* happy to see someone interested in this project.

I sure am interested. But I'm not sure how much time I will be able
to spend on it.

If your offer is for algorithm help, please see Algorithms below. If
your offer above includes programming (i.e. C or C++ programmer), and
you are interested in working on it, please let me know (either off
list or if you wish copying the bacula-devel list) and we can discuss
the project.

The theoretical background is what I'm interested (and probably
skilled) most, but programming in C is not a problem either. I'm not
that familiar with C++ though.

<snip>

Now:
- Reviewing my hash table code (particularly the hash function)
src/lib/htable.h src/lib/htable.c

Will do.

<snip>

Andre:

Not being a programmer myself (yes, I know, job title aside), I cannot
assist here. However, I am also very interested in this project and
appreciate any help you can provide moving this feature forward.

- --
---- _ _ _ _ ___ _ _ _
|Y#| | | |\/| | \ |\ | | |Ryan Novosielski - Systems Programmer III
|$&| |__| | | |__/ | \| _| |novosirj < at > um... - 973/972.0922 (2-0922)
\__/ Univ. of Med. and Dent.|IST/AST - NJMS Medical Science Bldg - C630
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFGbwtSmb+gadEcsb4RAtmeAJ97CIQD0nYWJPHnruJK2xkEbZKX+QCfVcjx
hvqWlfNcHiwICFnLgY/4uhM=
=6cKU
-----END PGP SIGNATURE-----

Post [Bacula-devel] Project for strong incremental backup assuran 
Attachments: Message as HTML

On 14:45, Kern Sibbald wrote:

- keys are restricted to strings. it would be easy to extend this
to arbitrary data by adding a "size" field.
=20
Yes, good idea, though I don't immediately see a need to handle anything =
other=20
than strings within Bacula.

Well, one could hash the _contents_ of the files as well to detect renames.

hlink *walkptr; /* table walk pointer */
=20
This is only used in first() and next(). IMHO the code would be
more readable if this were local to these two methods and
mext() would take a pointer to the predecessor.
=20
I think what you are suggesting is possible, and I will take a look at it=
=2E=20
However, it leads to several problems:
1. walkptr and walk_index are then no longer treated the same, and IMO th=
at=20
will make it harder to read the code. I.e. there are 2 pieces of informa=
tion=20
that would have to be returned and then passed into next().

Yes, that's right, so a struct containing these two pieces of
information would do the trick. Additional benefit is that this would
make the code thread safe.

2. There will be additional code necessary to adjust the pointer on entry=
,=20
which I currently avoid by keeping walkptr in the class.

I don't quite follow. Which adjustments you are seeing here? BTW:
The next() method only works as expected if there are no inserts in
between. I think this isn't an obvious fact and should be documented.

While thinking a bit more on it, it would probably be a good thing
to replace first() and next() by a new function for_each_key() that
takes a pointer to a function to be called for each entry of the hash
table. That way, nothing of the internal details needs being exposed
and walk_ptr/walk_index could still be local.

3. It complicates the "user" code in that it must pass the old pointer to=
the=20
next() routine and if it is symmetric also walk_index.

That's a of course true. And it raises the question what to do if the
"user" passes outdated information to next().

This ist just shifting and multiplication with the bytes in key. I
would expect this hash to have quite some unneccessary collisions
when used on strings, though I'm by no means an expert in this area.
=20
If I am not mistaken, this is a *very* common technique.

Yes it is, the question is whether this simple hashing method performs
better than a more sophisticated function (which burns more CPU cycles
for computing the hash but might save time afterwards because the
keys are better distributed). We'll definitely need some tests with
real world data here.

This is a constant, known at compile time, so make it a preprocessor
define using offsetof.
=20
Uh, what is "This is a constant"? I don't see any constant. loffset dep=
ends=20
on how one calls the routines. Rather than using a C++ template, which=
=20
duplicates the code for each different structure that contains a hash lin=
k,=20
Bacula computes the offset of the link in the structure, and thus with th=
e=20
same source code can handle any number of different user structures that =
have=20
hash links. I have done the same for alist (allocated list) and dlist=20
(doubly linked lists). The downside of this technique is that the higher=
=20
level code must cast because it passes void pointers to the structures.
=20
It is a bit of a tricky point, so I'm not sure the above is clear.

Thanks for the explanation, I think I understand now, please correct me
if I'm wrong. The user is expected to define a structure containing
at least two members: A pointer to an hlink structure and a pointer
to the data to be hashed.

And yes, you're right, loffset is not a constant if the htable code
aims to work with arbitrary such structures. But for _each_ such
structure it's a constant, so I think one could get rid of the casts
by using some preprocessor magic and offsetof().

For example, if the user struct is

struct my_struct {
struct my_item *item;
struct hlink *link;
...
};

struct my_struct *my_array =3D malloc(42 * sizeof(*my_array));

the user would call

HASH_INIT(my_struct, link, item, 42);

where HASH_INIT would be

#define HASH_INIT(type, link_member, item_member, size) \
hash_init(offsetof(type, link_member) - offsetof(type, item_member), size)

and hash_init could be simplified to

void htable::init(int offset, int tsize)
{
...
loffset =3D offset
...
}

No casts and type safe.

More importantly, I think it is not optimal=20
to store that many keys in the table because that means we'll have
in average four hits per bucket. I'd rather choose max_items _smaller_
than the number of buckets. For instance the git code which uses hash
tables extensively, grows its table whenever it is filled 66%.
=20
=20
I'd like to see some tests on this. I'm not convinced that the % full is=
a=20
good criterion because you could then have one bucket that has 100 items =
in=20
it which could be catastrophic.

Well, that percentage in git is just number of items/size of the array
(git uses open addressing).

Open addressing might be better than chaining to deal with
hash collisions. In particular if items are never (or rarely)
removed from the table.
=20
Open addressing has the big advantage that it keeps entries together whic=
h=20
improves CPU speed since everything is in the cache, but unless I am miss=
ing=20
something, open addressing is extremely expensive in terms of memory=20
requirements since the table has to always be the maximum size. This cou=
ld=20
be reasonable if we know how many items are going to be added, but if we =
do=20
not, I'm not sure.

I don't think it's necessarily that expensive. For instance if one has
a hash table with space for 2 * N objects, at the time the Nth object
is going to be stored, the size will be doubled. If hash collisions
occur, the first free entry in the table is used. There are better
methods for selecting a free slot.

So yes there is some waste in memory, but depending on the situation,
it might be affordable.

I would like to see some real performance studies before changing.

Me too. I'll see if I can code up something. Probably not before next
week though.

What always amuses me when I look back at code like this is that I wrote =
it 4=20
years ago to be able to implement the base file project (de-duplication) =
and=20
the project to track files added/removed from the system, but I've been s=
o=20
busy with other priority projects that I have not had time to get back an=
d=20
actually use these routines.

LOL

The same happened for my red/black tree (src/lib/rblist.c/h), except in t=
hat=20
case, I have now actually put the code to use in the restore code giving =
a=20
513 times speed up in version 2.1.x compared to 2.0.x for 800,000 files.

Yeah, rbtrees are great. I use them in one of my pet projects too. But
I took the easy route and copied the rbtree implementation of the
Linux kernel ;)

Regards
Andre
--=20
The only person who always got his work done by Friday was Robinson Crusoe

Post [Bacula-devel] Project for strong incremental backup assuran 
On Mon, 11 Jun 2007 14:45:38 +0200, Kern Sibbald said:

Hello,

Thanks for your comments. Please see my notes below ...

On Monday 11 June 2007 10:24, Andre Noll wrote:
On 15:35, Andre Noll wrote:

- Reviewing my hash table code (particularly the hash function)
src/lib/htable.h src/lib/htable.c

Will do.

First some general remarks:

- check the return value of malloc()

Not necessary, malloc() is #defined to be bmalloc(), which aborts if it is out
of memory.

- keys are restricted to strings. it would be easy to extend this
to arbitrary data by adding a "size" field.

Yes, good idea, though I don't immediately see a need to handle anything other
than strings within Bacula.

- IMHO "%p" is prefered for printing pointer variables.

Yes, but it was not portable. We now have our own Bacula printf code which is
portable and supports %p, so I've been converting the old stuff when I am in
the file. Clearly this is a good file to attack.


--------------------------------------- htable.h


struct hlink {
void *next; /* next hash item */

why not struct hlink *next?

Good question -- I think you are right. I'll try it and see if anything
complains ...


char *key; /* key this item */
uint32_t hash; /* hash for this key */
};

class htable : public SMARTALLOC {
hlink **table; /* hash table */
int loffset; /* link offset in item */

see below.

uint32_t num_items; /* current number of items */
uint32_t max_items; /* maximum items before growing */

see below.

uint32_t buckets; /* size of hash table */
uint32_t hash; /* temp storage */
uint32_t index; /* temp storage */
uint32_t mask; /* "remainder" mask */
uint32_t rshift; /* amount to shift down */
hlink *walkptr; /* table walk pointer */

This is only used in first() and next(). IMHO the code would be
more readable if this were local to these two methods and
mext() would take a pointer to the predecessor.

I think what you are suggesting is possible, and I will take a look at it.
However, it leads to several problems:
1. walkptr and walk_index are then no longer treated the same, and IMO that
will make it harder to read the code. I.e. there are 2 pieces of information
that would have to be returned and then passed into next().
2. There will be additional code necessary to adjust the pointer on entry,
which I currently avoid by keeping walkptr in the class.
3. It complicates the "user" code in that it must pass the old pointer to the
next() routine and if it is symmetric also walk_index.


uint32_t walk_index; /* table walk index */

same here.

As with the above, that will complicate the problem by returning it to the
higher level program. The "user" program really shouldn't need to know
anything about walkptr or walk_index as they are simply the means to traverse
a hash list in linear order without using recursion (I have never seen such
code anywhere), so IMO, returning them to the higher level routine breaks the
encapsulation of the code.


void hash_index(char *key); /* produce hash key,index */
void grow_table(); /* grow the table */
public:
htable(void *item, void *link, int tsize = 31);
~htable() { destroy(); }
void init(void *item, void *link, int tsize = 31);
bool insert(char *key, void *item);
void *lookup(char *key);
void *first(); /* get first item in table */
void *next(); /* get next item in table */
void destroy();
void stats(); /* print stats about the table */
uint32_t size(); /* return size of table */
};

----------------------------------------htable.c


/*
* Create hash of key, stored in hash then
* create and return the pseudo random bucket index
*/
void htable::hash_index(char *key)
{
hash = 0;
for (char *p=key; *p; p++) {
hash += (hash << 3) + (uint32_t)*p;
}

This ist just shifting and multiplication with the bytes in key. I
would expect this hash to have quite some unneccessary collisions
when used on strings, though I'm by no means an expert in this area.

If I am not mistaken, this is a *very* common technique. The most common
algorithm shifts by 5 rather than 3, but if I remember right my tests showed
that 3 was better -- at least with the filenames on my system.

I think I know why 3 is better -- it manages to encode the final 32/3 chars
instead of the final 32/5 Smile Shifting is not sufficient -- you need
rotation, otherwise the hash value will only depend on the tail of the string.



/* Multiply by large prime number, take top bits, mask for remainder */
index = ((hash * 1103515249) >> rshift) & mask;
Dmsg2(100, "Leave hash_index hash=0x%x index=%d\n", hash, index);
return;

The return statement is unneccesary.

Yes, I have removed it as it could be confusing.

OTOH, it is good documentation to help the reader understand why the hash
function doesn't return anything :-)





void htable::init(void *item, void *link, int tsize)
{
int pwr;
tsize >>= 2;
for (pwr=0; tsize; pwr++) {
tsize >>= 1;
}
loffset = (char *)link - (char *)item;

This is a constant, known at compile time, so make it a preprocessor
define using offsetof.

Uh, what is "This is a constant"? I don't see any constant. loffset depends
on how one calls the routines. Rather than using a C++ template, which
duplicates the code for each different structure that contains a hash link,
Bacula computes the offset of the link in the structure, and thus with the
same source code can handle any number of different user structures that have
hash links. I have done the same for alist (allocated list) and dlist
(doubly linked lists). The downside of this technique is that the higher
level code must cast because it passes void pointers to the structures.

It is a bit of a tricky point, so I'm not sure the above is clear.


mask = ~((~0)<<pwr); /* 3 bits => table size = 8 */
rshift = 30 - pwr; /* start using bits 28, 29, 30 */
num_items = 0; /* number of entries in table */
buckets = 1<<pwr; /* hash table size -- power of two
*/
max_items = buckets * 4; /* allow average 4 entries per chain
*/

As max_items is always four times the number of buckets, we could
get rid of max_items.

Yes, good. I agree.

More importantly, I think it is not optimal
to store that many keys in the table because that means we'll have
in average four hits per bucket. I'd rather choose max_items _smaller_
than the number of buckets. For instance the git code which uses hash
tables extensively, grows its table whenever it is filled 66%.


I'd like to see some tests on this. I'm not convinced that the % full is a
good criterion because you could then have one bucket that has 100 items in
it which could be catastrophic.

If you have 100 items in a bucket then the hash function is very broken!
Also, I suspect that the acceptable % filled for chained tables can generally
be larger than for open tables because the clashes only affect one bucket.
Moreover, the current implementation stores the hash value (not the bucket
index) in each hlink in the chain, so the scan should quickly filter out
non-matching elements without calling strcmp.

__Martin


Once I implement a bloom filter, only 15% (with the parameters that I have
chosen) of the collisions will require a search of the buckets.


table = (hlink **)malloc(buckets * sizeof(hlink *));

unneccessary cast.

In C++ code as opposed to C the cast is *required* or you get a compiler
warning (or perhaps an error -- I forget).


memset(table, 0, buckets * sizeof(hlink *));

how about calloc() instead of malloc + memset?

I'm not sure that smartall (our underlying malloc routine) implements
calloc(). I'll look at it.


/*
* Take each hash link and walk down the chain of items
* that hash there counting them (i.e. the hits),
* then report that number.

Open addressing might be better than chaining to deal with
hash collisions. In particular if items are never (or rarely)
removed from the table.

Open addressing has the big advantage that it keeps entries together which
improves CPU speed since everything is in the cache, but unless I am missing
something, open addressing is extremely expensive in terms of memory
requirements since the table has to always be the maximum size. This could
be reasonable if we know how many items are going to be added, but if we do
not, I'm not sure.

I would like to see some real performance studies before changing.


* Obiously, the more hits in a chain, the more time
* it takes to reference them. Empty chains are not so
* hot either -- as it means unused or wasted space.
*/
#define MAX_COUNT 20

Unless I'm mistaken, the code will fail badly if there are more than
MAX_COUNT keys that hash to the same value.

Quite possibly, but I think there are some mitigating factors. 1. If I
remember right the max should be 4. 2. this code is really used for debug.
3. if it fails the stats will be broken, but there will be no serious
consequences. For just obtaining stats, I didn't want to spend too much time
worrying about covering all possibilities.


void htable::stats()
{
int hits[MAX_COUNT];
int max = 0;
int i, j;
hlink *p;
printf("\n\nNumItems=%d\nTotal buckets=%d\n", num_items, buckets);
printf("Hits/bucket: buckets\n");

Dmesg?

Yes, that would clearly be better.


for (i=0; i < MAX_COUNT; i++) {
hits[i] = 0;
}

memset(hits, 0 MAXCOUNT * sizeof(int));

for (i=0; i<(int)buckets; i++) {
p = table[i];
j = 0;
while (p) {
p = (hlink *)(p->next);

cast would become unneccessary if next were struct hlink*.

Yup :-)


void htable::grow_table()
{
Dmsg1(100, "Grow called old size = %d\n", buckets);
/* Setup a bigger table */
htable *big = (htable *)malloc(sizeof(htable));

unnecessary cast

See above. Actually, if I were doing this right, I would probably use one of
those new fangled C++ casts, but I find them so ugly and the syntax so hard
to remember that I avoid them.


big->loffset = loffset;
big->mask = mask<<1 | 1;
big->rshift = rshift - 1;
big->num_items = 0;
big->buckets = buckets * 2;
big->max_items = big->buckets * 4;

DRY ;)

big->table = (hlink **)malloc(big->buckets * sizeof(hlink *));
memset(big->table, 0, big->buckets * sizeof(hlink *));

unnecessary cast, calloc().

/*
* We walk through the old smaller tree getting items,
* but since we are overwriting the colision links, we must
* explicitly save the item->next pointer and walk each
* colision chain ourselves. We do use next() for getting
* to the next bucket.
*/
for (void *item=first(); item; ) {
void *ni = ((hlink *)((char *)item+loffset))->next; /* save link
overwritten by insert */

It might be worth to improve readability by defining

#define next_item(item) ((hlink *)((char *)((item)+(loffset))

Yes, good idea. I was planning to do that as I did in dlist.c.


as this calculation is needed several times.

Dmsg1(100, "Grow insert: %s\n", ((hlink *)((char
*)item+loffset))->key);
big->insert(((hlink *)((char *)item+loffset))->key, item);
if (ni) {
item = (void *)((char *)ni-loffset);
} else {
walkptr = NULL;
item = next();
}
}

This rather ugly code would become unneccessary when using open
addressing instead of chaining.

Quite possibly, but see my remarks about open addressing above.


void *htable::next()
{
Dmsg1(100, "Enter next: walkptr=0x%x\n", (unsigned)walkptr);


if (walkptr) {
walkptr = (hlink *)(walkptr->next);

unnecessary cast

C++ :-)

Thanks for taking the time to look at this -- you are the first person who has
been interested in this kind of code. I'll go over each of your points in
detail -- especially since I will be going through this code in the near
future to bring it up to date (#defines to simplify the pointer offsets), ...

Don't hesitate to respond if some of my remarks above are incorrect.

What always amuses me when I look back at code like this is that I wrote it 4
years ago to be able to implement the base file project (de-duplication) and
the project to track files added/removed from the system, but I've been so
busy with other priority projects that I have not had time to get back and
actually use these routines.

The same happened for my red/black tree (src/lib/rblist.c/h), except in that
case, I have now actually put the code to use in the restore code giving a
513 times speed up in version 2.1.x compared to 2.0.x for 800,000 files.

Best regards,

Kern



Regards
Andre
--
The only person who always got his work done by Friday was Robinson Crusoe


-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/
_______________________________________________
Bacula-users mailing list
Bacula-users < at > li...
https://lists.sourceforge.net/lists/listinfo/bacula-users


Display posts from previous:
Reply to topic Page 1 of 1
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
  


Magic SEO URL for phpBB